手写递归下降解析器更可控、调试直观,适合简单脚本语言的教学或嵌入式轻量场景;而antlr、bison等生成器会引入构建依赖、语法文件和代码生成流程,掩盖核心逻辑,且运行时开销大、错误恢复复杂、对新手不友好。

为什么不用现成的解析器生成器(如ANTLR、Bison)
因为目标是“简单”脚本语言,不是工业级语言;用生成器会引入构建依赖、语法文件、代码生成流程,反而掩盖核心逻辑。手写递归下降解析器更可控,调试直观,适合教学或嵌入式轻量场景。
-
ANTLR生成 C++ 代码体积大,运行时需libantlr4-runtime,静态链接麻烦 -
Bison需要yacc工具链,错误恢复机制复杂,对新手不友好 - 手写解析器能直接暴露词法/语法边界——比如
1+2*3怎么处理优先级,if (x) y=1;怎么匹配else
词法分析阶段:如何安全切分 token 而不被注释/字符串干扰
关键不是“怎么 split”,而是按字符流顺序扫描,状态机驱动。常见坑是把 // 当成两个独立 /,或在字符串里提前结束引号。
- 维护当前状态:
STATE_CODE、STATE_STRING、STATE_COMMENT,遇到"或//切换状态 -
"hello \"world"中的\"是转义,不能作为字符串结束——需检查前一个字符是否为\且未被转义 - 数字字面量要区分
123(int)、123.0(double)、0xFF(hex),建议统一用std::stod+ 前缀判断 - 保留关键字(如
if、while)必须在标识符识别后查表,避免把ifdef误判为if
递归下降解析:如何让 parseExpression() 正确处理运算符优先级
不要写一个 giant switch 处理所有操作符。优先级本质是函数调用层级:低优先级操作符调用高优先级函数。
- 从最低优先级开始:先写
parseAssignment()→ 调用parseLogicalOr() -
parseLogicalOr()→ 调用parseLogicalAnd()→ … →parsePrimary()(原子表达式:数字、变量、括号) - 每个函数负责自己层级的左结合操作符,例如
parseAdditive()循环读取+和-,但不碰* - 遇到
1 + 2 * 3时,parseAdditive()先拿到1,看到+,调用parseMultiplicative()解析2 * 3,再相加——天然满足优先级
AST 执行与变量作用域:为什么局部作用域不能只用 std::map<:string double></:string>
简单脚本常需嵌套作用域(如函数内定义变量),而单层 map 无法支持变量遮蔽(shadowing)和快速回收。
- 用栈式作用域:
std::vector<:unordered_map double>></:unordered_map>,push_back()新 scope,pop_back()退出 - 查找变量时从栈顶往下搜,第一个匹配即生效;赋值始终写入栈顶 map
- 函数调用时复制参数到新 scope,但注意:若支持闭包,需捕获外层变量引用,此时不能仅存
double,得用std::shared_ptr<value></value> - 别忘了
return语句需中断当前执行流——返回值存在临时变量里,上层节点检查是否收到RETURN_SIGNAL
真正难的不是解析,是错误提示位置(行号列号)和内存管理一致性。比如 parsePrimary() 报错说 “expected ‘(’”,但实际输入是 func 123,这时要回退 token 并定位到 123 开始处——token 流必须支持 peek() 和 consume() 分离。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











