Archipelago

Lec 4语法分析 II — 自顶向下解析(Top-Down Parsing)

讲义完成更新于 2026-09-16

Lec 4 语法分析 II — 自顶向下解析(Top-Down Parsing)

本讲:递归下降解析器——把解析器写成一组相互递归的过程,结构与文法同构

TL;DR

  • 自顶向下解析从开始符号构造最左推导,反复执行展开非终结符、匹配终结符与接受输入三个动作。
  • 朴素回溯成本高,左递归还会导致无限递归;实现前必须先改造文法。
  • 预测分析用当前 token 选择产生式,递归下降则把这个策略实现为「每个非终结符一个函数」的可调试代码。

1. 定位与起点

语言规范两层:词法(正则表达式)、语法(文法)。本讲实现递归下降解析器 (recursive descent parser)。起点:词法分析已产出 token 序列,每个 token 有类型(对应终结符)与值(如 Int 549、if、AddOp +)。

本讲文法(已分优先级、左递归形式):

Start → Expr
Expr → Expr + Term | Expr - Term | Term
Term → Term * Int  | Term / Int  | Int

2. 基本方法:三个动作

定义(自顶向下解析的三动作)

从 Start 出发构建最左推导 (leftmost derivation):

  • 最左符号是非终结符 → 选一产生式应用(扩展);

  • 最左符号是终结符 → 与输入匹配(消耗输入);

  • 所有终结符匹配成功 → 接受。

关键难点:为每个非终结符选对产生式。解析器生成语法树的先序遍历(先父后子、兄弟从左到右)。

例

例题(解析 2-2*2,节选)

Start → Expr → Expr - Term → Term - Term → Int - Term
匹配 2 → 2 - Term → 展开 Term → Term*Int → Int*Int
匹配 2、2 → 解析完成

3. 策略问题:选哪条产生式?

经典策略与机制分离。机制是三动作,策略是选产生式。

3.1 回溯(Backtracking)

把它当搜索问题:每个选择点试下一候选,失败则回退换一个。三要素:搜索空间(语法树)、搜索算法(解析算法)、目标(输入的语法树)。

例

例题(回溯解析 2-2*2) 先试 Expr → Expr + Term,展到 Int + Term,匹配 2 后下一 token 是 -,无法匹配 + → 回溯;改用 Expr → Expr - Term 成功。

3.2 致命问题:左递归

定理(左递归 + 自顶向下 = 死循环)

形如 Term→Term∗Num\text{Term} \to \text{Term} * \text{Num} 的产生式,展开最左非终结符会无限展 Term → Term → … 而不消耗输入。对策:解析时改造文法消除左递归。


4. 消除左递归

定理(左递归消除)

对 A→Aα∣βA \to A\alpha \mid \beta(α,β\alpha,\beta 不以 A 开头),等价改写引入新非终结符 R: A→βR,R→αR,R→εA \to \beta R, \quad R \to \alpha R, \quad R \to \varepsilon "向左长"的树变为"向右长"的树,语言不变。

实例:

原:  Term → Term*Int | Term/Int | Int
新:  Term → Int Term'
     Term' → * Int Term' | / Int Term' | ε

代价:文法不如原直观;好处:消除直接无限递归,并为预测分析铺路。


5. 预测分析(Predictive Parsing)

定义(预测分析)

回溯的替代:向前看 (lookahead) 输入流的下一个 token,据此直接决定用哪条产生式。本课用一个 token 前看。

消左递归后的文法:

Start → Expr
Expr  → Term Expr'         Term  → Int Term'
Expr' → + Term Expr' | - Term Expr' | ε
Term' → * Int Term' | / Int Term' | ε

选择点:当前在 Term',看下一 token——* 用 Term' → * Int Term',/ 用 Term' → / Int Term',否则用 Term' → ε。


6. 递归下降解析器(手写代码)

定义(递归下降的构造规则)

每个非终结符 NT 对应一个过程;过程检查当前输入符号 T,若 T∈First(βk)T \in \text{First}(\beta_k) 则应用第 k 条产生式、消耗其终结符、对其非终结符递归调用。当前符号存于全局 token;过程返回成功/失败(或所解析的子树)。

Boolean Term()
    if (token = Int n) token = NextToken(); return TermPrime()
    else return false
Boolean TermPrime()
    if (token = *) token = NextToken();
        if (token = Int n) token = NextToken(); return TermPrime()
        else return false
    else if (token = /) ...   // 同上
    else return true          // Term' → ε

程序结构与文法同构:每非终结符一函数,每条产生式一分支,每个非终结符出现一次递归调用。(First 集的系统计算见 L5。)


7. 本讲小结

  • 自顶向下 = 构最左推导,三动作:扩展非终结符 / 匹配终结符 / 接受。
  • 朴素回溯遇左递归死循环 → 先消左递归(引入 R → αR | ε)。
  • 预测分析用一个 token 前看免回溯;递归下降 = 预测分析 + 手写,每非终结符一过程。
  • 下一讲:系统计算 First 集与 derives-ε,处理公共前缀,构建语法树。