flex生成scanner类与bison parser类通信关键在yy_decl重定义和variant语义值统一;c++模式下需启用api.pure full与api.value.type variant,使yylex成为成员函数并安全传递token编号与payload。

flex 生成的 Scanner 类怎么和 bison 的 Parser 类通信
关键在 YY_DECL 和语义值类型统一。默认 C 模式下,yylex() 返回 int,通过全局 yylval 传 token 值;C++ 模式必须改掉这个设计——否则对象生命周期和线程安全全崩。你需要在 %define api.pure full + %define api.value.type variant 下启用 C++ variant 语义值,并显式重定义 YY_DECL 为成员函数签名,比如:int yylex(semantic_type* yylval, location_type* yylloc)。这样 Scanner 对象就能把 token 编号和 payload(如 std::string 或 double)一起塞进 *yylval,再由 Parser 在归约时取用。
bison 的 %locations 和 %error-verbose 怎么真正定位错误行号
光开 %locations 不够,Scanner 必须主动更新位置信息。flex 里不能只靠 yylineno——它在多行匹配或回退时不准。正确做法是:在规则段每条匹配动作里调用 yylloc->step();(如果用了 %option yylineno),或更稳的方式是手动维护 location_type 成员,在换行符、字符串字面量起始/结束处显式调用 yylloc->lines(yylloc->lines() + 1) 和 yylloc->step()。配合 %error-verbose,yyerror 就能拿到带 begin/end 的 location_type,直接输出 error: 3:17: expected ',' before '}' 这种格式。
AST 节点怎么在归约动作里安全构造而不泄漏内存
别在 %{ %} 代码块里裸 new。bison 归约栈生命周期短,且可能因错误恢复反复析构。推荐两种方式:
• 用 std::unique_ptr 包裹 AST 节点,归约动作中直接 return std::make_unique<binaryexpr>(std::move(), , std::move())</binaryexpr>;
• 更工程的做法是预分配内存池(如 llvm::BumpPtrAllocator),所有节点从池中 Allocate(),整个 AST 构建完再一次性释放,避免频繁 malloc 和析构开销;
• 绝对不要把 raw pointer 存进 semantic_type variant,否则 variant 析构时不会 delete,必然泄漏。
模板嵌套 Foo<bar>></bar> 为什么 lexer 会把 >> 当成右移运算符
这不是 bison 能解决的问题,是 lexer 缺乏上下文导致的。flex 默认状态机无法区分“模板尖括号”和“右移运算符”。必须引入启动条件(start conditions):定义 %x IN_TEMPLATE,当 lexer 遇到 且当前在类型上下文中(比如刚识别完标识符、刚进入类声明体),就切换到 <code>IN_TEMPLATE 状态;在此状态下,连续两个 > 触发 TEMPLATE_CLOSE token,而非 RIGHT_SHIFT。这个状态切换逻辑必须由 parser 通过接口(如 scanner.push_state(IN_TEMPLATE))驱动,lexer 和 parser 之间要形成轻量状态同步协议。
最常被忽略的是:C++ skeleton 下的 location_type 默认不包含列偏移(column),只存行号和字节偏移,但人类调试时真正需要的是列号。得自己扩展 location_type,并在 flex 动作里手动计算当前列——尤其注意 tab 字符要按 4 或 8 位展开,否则报错位置会偏移。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











