代表性面试主题

编程面试:如何设计能错误恢复的表达式解析器?

编程题困难
OfferCC 编辑团队发布 更新

题干

请设计一个解析算术表达式和变量声明的小型解析器。输入可能包含多个语法错误,但解析器仍需报告位置、继续解析后续语句,并为 IDE 提供部分 AST。

题干与适用场景

请设计一个解析算术表达式和变量声明的小型解析器。输入可能包含多个语法错误,但解析器仍需报告位置、继续解析后续语句,并为 IDE 提供部分 AST。

题目考察词法、语法、错误恢复和数据结构边界。Bison 文档建议在同步点丢弃输入并继续解析;IDE 场景还要求错误节点、稳定范围和不因首个错误终止整份输入。

面试官考察点

候选人应先定义 token 和语法,再说明递归下降或 LR 方案;能否区分语法错误与词法错误;能否选择安全同步点、抑制级联错误、保留部分 AST,并测试嵌套括号、缺失分隔符和未闭字符串。

30 秒回答框架

“我把输入分成 lexer、parser 和诊断层。Parser 维护 token 游标和源范围,遇到错误先记录期望与实际 token,再跳到分号、右括号或文件尾等同步点,插入 ErrorNode 后继续。恢复期间抑制重复诊断,成功消费若干 token 后恢复报告。AST 节点保留缺失范围,调用方可选择严格模式或容错模式。”

分步骤深入解答

第一步:定义 token 和语法

词法器输出种类、文本、起止偏移和行列号;识别标识符、数字、运算符、括号、分号和非法字符。语法明确优先级与结合性,避免把错误恢复逻辑散落到每个表达式函数。

第二步:选择解析器结构

递归下降易读,适合小语法;使用 precedence climbing 处理表达式优先级。若语法复杂,可用生成器和明确的 error strategy。无论实现方式,都要让 parser 能查询 lookahead、回退有限 token,并携带源范围。

第三步:分离词法错误与语法错误

非法字符或未闭字符串属于词法错误,lexer 应产生错误 token 并继续扫描;缺少操作数、右括号或分号属于语法错误,由 parser 按上下文报告。不要把所有问题都包装成“Unexpected token”。

第四步:选择同步点

语句级同步点通常是分号、右大括号或文件尾;表达式内部可同步到逗号、右括号或运算符边界。跳过 token 时必须保证游标前进,否则同一错误会无限循环。

第五步:构造部分 AST

错误位置保留在节点范围内;缺失子节点用 MissingNode,无法恢复的片段用 ErrorNode 包住原 token。下游格式化、语法高亮和补全应能处理这些节点,而不能假设树永远完整。

第六步:抑制级联诊断

一次恢复可能引发多个表面错误。记录最近同步点和成功消费计数,在恢复后至少消费若干 token 再输出下一条诊断;同时限制单个文件的错误数量,避免日志和 UI 被淹没。

第七步:支持增量解析

编辑器输入变化时,只重解析受影响的 token 区间和相邻语法上下文,复用未变子树。节点范围和 token ID 要稳定,缓存失效必须以父节点边界和语法状态为准,不能只按字符位置猜测。

第八步:测试与性能

测试正确输入、单个错误、多个错误、嵌套错误、长字符串和极大表达式。断言错误位置、数量、AST 错误节点和终止条件;对长输入测量线性扫描、递归深度、内存和最坏恢复开销。

设计取舍与边界

严格失败还是继续解析

编译器批处理可在诊断后继续以提升反馈;配置校验可能在首个错误后立即拒绝。用模式开关控制,但共享 lexer、token 和诊断结构,避免两套实现漂移。

同步点多还是少

同步点多能缩短错误影响范围,却可能跳过本可恢复的 token;同步点少能保留更多上下文,却容易产生级联错误。按语句和分隔符设计,并用语料测试。

递归下降还是生成器

递归下降便于解释和定制错误信息;生成器适合大型、稳定语法。错误恢复策略应是显式接口,不能依赖生成器默认行为而缺少测试。

失败演练与演进计划

缺少右括号

输入下一条语句,确认 parser 在分号或文件尾同步,输出一条缺失括号错误并保留后续语句。

非法字符和未闭字符串

确认 lexer 产生错误 token 后继续到行尾或字符串结束,parser 不因单个字符卡住。

连续错误风暴

构造每个 token 都非法的输入,确认游标始终前进、错误数量受限且 CPU 不呈平方增长。

常见误区与追问

误区一:遇到第一个错误就返回 null

追问:IDE 如何继续高亮和补全?应返回带范围的 ErrorNode 或 MissingNode。

误区二:恢复时不推进游标

追问:如何证明不会在同一 token 无限循环?

误区三:所有错误都在 parser 报告

追问:未闭字符串和非法字符的诊断责任属于 lexer 还是 parser?

误区四:忽略级联错误抑制

追问:一个缺分号为什么不能打印十条重复错误?

误区五:增量解析只按字符偏移缓存

追问:插入一个括号后,哪些父节点和语法状态必须失效?

延伸追问与参考答案

为什么要保留错误节点?

格式化、补全和高亮仍需要结构与范围;错误节点让下游显式处理不完整输入,而不是崩溃。

如何保证恢复终止?

每次恢复必须消费 token 或到达 EOF,并设置最大错误数和最大递归深度。

如何验证错误质量?

用包含多个独立错误的语料断言位置、数量、后续 AST 和运行时间,而非只测试第一个错误。

公开来源

同类题目

资源

产品对比

每份对比均基于官方资料,并标注复核日期。

查看完整对比

相关面试工具

用 Screenshot 处理算法题

截图题目后,按顺序看约束、解法、代码、边界条件和复杂度。

查看工具