如何用C++变体实现Flex与Bison对接并解决编译错误?
问题解决:Bison/Flex使用variant语义值时的头文件循环依赖错误
问题背景
将StackOverflow某高赞回答适配为使用variant而非std::string作为语义值,修改了以下内容:
- 在
fooParser.yy中,将%define api.value.type {std::string}改为%define api.value.type variant,并为HELLO、WORLD令牌指定<std::string>类型 - 在
FooLexer.hh中,将yylex的参数从std::string *const yylval改为yy::parser::value_type *const yylval
修改后出现两类编译错误:
- 未引入
fooParser.tab.hh时,编译器提示‘yy’未声明 - 引入
fooParser.tab.hh后,新增错误:
fooParser.tab.hh:666:21: error: expected ‘)’ before ‘&’ token 666 | parser (FooLexer &lexer_yyarg); | ~ ^~ | ) fooParser.tab.hh:1099:5: error: ‘FooLexer’ does not name a type 1099 | FooLexer &lexer; | ^~~~~~~~
错误原因
这是头文件循环依赖导致的:
FooLexer.hh包含fooParser.tab.hh,而fooParser.tab.hh因%parse-param {FooLexer &lexer}需要FooLexer的定义,但此时FooLexer类还未被声明(因为FooLexer.hh先包含了fooParser.tab.hh)- 同时
fooParser.yy的%code requires块中包含了FooLexer.hh,进一步加剧了循环依赖
解决步骤
- 解除头文件循环依赖:在
FooLexer.hh中移除对fooParser.tab.hh的直接包含,改用前向声明yy::parser - 调整Parser的前向声明:在
fooParser.yy的%code requires块中仅保留FooLexer的前向声明,不要包含FooLexer.hh - 在Lexer实现中引入Parser头文件:在
fooLexer.ll的代码块中包含fooParser.tab.hh,确保编译时能解析yy::parser::value_type
修改后的完整代码
FooLexer.hh
#pragma once #include <string> #if ! defined(yyFlexLexerOnce) #include <FlexLexer.h> #endif // 前向声明yy::parser,避免直接包含头文件 namespace yy { class parser; } class FooLexer : public yyFlexLexer { public: int yylex(yy::parser::value_type *const yylval); };
fooParser.yy
%require "3.2" %language "c++" %code requires { #include <string> // 仅前向声明FooLexer,不要包含头文件 class FooLexer; } %define api.value.type variant %parse-param {FooLexer &lexer} %header %code { #define yylex lexer.yylex // 在parser的实现代码中包含FooLexer头文件 #include "FooLexer.hh" } %token <std::string> HELLO %token <std::string> WORLD %% hello_world: HELLO WORLD '!' { std::cout << "Goodbye " << $WORLD << '!' << std::endl; }; %% void yy::parser::error(const std::string &message) { std::cerr << "Error: " << message << std::endl; }
fooLexer.ll
%{ #include "FooLexer.hh" #include "fooParser.tab.hh" #undef YY_DECL #define YY_DECL int FooLexer::yylex(yy::parser::value_type *const yylval) %} %option c++ noyywrap %option yyclass="FooLexer" %% [[:space:]] ; Hello { return yy::parser::token::HELLO; } [[:alpha:]]+ { *yylval = std::string(yytext, yytext + yyleng); return yy::parser::token::WORLD; } . { return yytext[0]; }
main.cc
#include "FooLexer.hh" #include "fooParser.tab.hh" int main() { FooLexer lexer; yy::parser parser(lexer); return parser(); }
Makefile
.RECIPEPREFIX = > prog: main.o fooParser.tab.o lex.yy.o > g++ $^ -o $@ main.o: main.cc FooLexer.hh fooParser.tab.hh > g++ -c $< -o $@ lex.yy.o: lex.yy.cc FooLexer.hh fooParser.tab.hh > g++ -c $< -o $@ fooParser.tab.o: fooParser.tab.cc FooLexer.hh > g++ -c $< -o $@ lex.yy.cc: fooLexer.ll > flex $< fooParser.tab.hh fooParser.tab.cc fooParser.output: fooParser.yy > bison $< .PHONY: clean clean: > rm -f prog main.o lex.* fooParser.tab.* stack.hh
验证编译
执行make命令即可正常编译生成可执行文件,测试输入Hello World!会输出Goodbye World!
内容的提问来源于stack exchange,提问作者Jakob E.
相关产品推荐
相关产品推荐

