形式陈述
LL(1) 预测分析从左向右读取 token,按最左推导展开上下文无关文法 公理库 上下文无关文法 Context-free grammar · CFG 每条产生式左侧是单个非终结符的生成系统。 ,并且只用一个尚未消费的 token 选择下一条产生式。“1”限制的是选择时看的未来输入数量,不是只保存一个历史状态。
先计算Nullable、FIRST 与 FOLLOW 公理库 文法的 Nullable、FIRST 与 FOLLOW Nullable FIRST and FOLLOW · First and follow sets 以最小不动点计算文法的可空性、可能首符号及后继符号,供预测与归约表使用。 。沿用 FIRST 不含 ε 的约定,对每条 A → α 定义
否 则 SELECT ( A → α ) = FIRST ( α ) ∪ { FOLLOW ( A ) , α ⇒ ∗ ε , ∅ , 否则 . 若 a 属于这个集合,就把产生式放入表格 M [ A , a ] 。同一格有两条不同产生式表示冲突;空格表示当前非终结符与输入不能合法接续。对已删除不可达规则的文法,所有表格至多一条规则,就是这里采用的 LL(1) 条件。
分析器的栈 公理库 栈 Stack · LIFO stack 以栈顶为唯一更新端、按后进先出规则组织元素的抽象数据类型;接口语义与具体表示的成本分别规定。 保存尚待匹配的符号。本页把栈顶写在左边,初始为 S $ ,输入末尾也加 $ :
栈顶是非终结符 A :查询当前输入 a 的表格,用其右部替换 A ;右部左端仍在栈顶
栈顶是终结符:必须与当前 token 相同,然后同时弹栈、前进输入
栈顶与输入都到 $ :接受;遇到空表格或终结符不匹配则拒绝
选了空产生式时只弹掉非终结符,不能消费一个“空 token”。若底层数组把栈顶放在右端,就要反序压入右部,才能仍先处理左端。
直觉
解析栈是一份尚未完成的语法树 公理库 语法树 Parse tree · Derivation tree 用有序树记录产生式层次,以完整例子区分推导顺序、文法歧义、优先级及抽象语法树。 前沿。展开非终结符会把一个待办项目换成几个更小项目;匹配终结符才真正跨过输入。已经消费的前缀与栈中的未完成部分合在一起,始终是从开始符号能推出的句型。
假设已经消费 u ,忽略结束标记后的栈为 γ 。核心不变量是 S ⇒ ∗ u γ ,且算法始终展开 γ 最左的非终结符。接受时 γ 为空,所以 S ⇒ ∗ u ,给出可靠性。无冲突的 SELECT 集再保证:若剩余输入确能由当前栈生成,合法推导所用的首条规则必在当前表格中,因此算法不会选错。
图片加载失败
例子与边界
一张完整的小表
考虑只含加法和括号的表达式:
E → T X , X → + T X ∣ ε , T → id ∣ ( E ) . X 表示一个项之后尚可继续的加法尾部。FIRST(E )=FIRST(T )={ id , ( } ,FIRST(X )={ + } ,且只有 X 可空。FOLLOW(E )=FOLLOW(X )={ ) , $ } ,FOLLOW(T )={ + , ) , $ } 。
所有表项如下,“—”是拒绝:
栈顶
id
(
+
)
$
E
E → T X
E → T X
—
—
—
X
—
—
X → + T X
X → ε
X → ε
T
T → id
T → ( E )
—
—
—
对 id+(id),完整执行过程为:
栈,左端为顶
剩余输入
动作
E $
id+(id)$
展开 E → T X
T X $
id+(id)$
展开 T → id
idX $
id+(id)$
匹配 id
X $
+(id)$
展开 X → + T X
+ T X $
+(id)$
匹配 +
T X $
(id)$
展开 T → ( E )
( E ) X $
(id)$
匹配 (
E ) X $
id)$
展开 E → T X
T X ) X $
id)$
展开 T → id
idX ) X $
id)$
匹配 id
X ) X $
)$
用空规则弹掉内层 X
) X $
)$
匹配 )
X $
$
用空规则弹掉外层 X
$
$
接受
输入 id+) 在匹配 + 后,栈顶是 T 而当前 token 是 );M [ T , ) ] 为空,故拒绝。不能为了让解析继续而随意把 T 当作空串,因为它并不可空。
左因子提取解决的是决策时机
文法 S → a A ∣ a B 的两条规则都以 a 开头,所以 LL(1) 表在 [ S , a ] 冲突。改为 S → a C , C → A ∣ B ,把决定推迟到消费 a 之后;这次能否区分,要检查 SELECT ( C → A ) 与 SELECT ( C → B ) 是否不交;可空备选还会把 FOLLOW(C ) 纳入自己的 SELECT 集。只有两者都不可空时,FIRST(A ) 与 FIRST(B ) 不交才直接给出所需条件。若 SELECT 集仍有交集,提取公共前缀本身不会创造不存在的信息。
左递归 E → E + T ∣ T 若直接变成递归下降函数,会在消费输入前再次调用自身。可改写成上面的尾部形式,但语义动作应另行保证期望的结合性:用尾列表从左向右折叠可以构成左结合 AST;仅因文法改成右递归,就直接构造右结合运算树,会改变减法等运算的意思。
一个文法不是 LL(1),不表示它有歧义,更不表示该语言没有其他 LL(1) 文法。冲突报告说明这张表无法唯一决定,不是语言歧义的通用判定器。
推论与应用
固定文法、预先建好表后,成功解析时每一步对应解析树上的展开或一个终结符匹配。对没有无效空推导环的 LL(1) 文法,树的大小随输入长度线性增长,因此解析时间为 O ( n ) ,栈空间最坏为 O ( n ) ;若把文法也视为输入,构表和每条长右部的压栈成本应另计。
本单元的主要路线先学这张表如何预测,再去看LR(0) 公理库 LR(0) 项目自动机与移进归约 LR(0) parsing · LR(0) item automaton 用项目自动机总结可行前缀,并以状态栈执行不依赖向前看的移进归约。 怎样等到右部已经出现才归约。Earley 公理库 Earley 解析算法 Earley parsing · Earley algorithm 用带起点的部分产生式及预测、扫描、完成三种规则进行通用CFG识别。 保留多种待选推导,PEG 公理库 解析表达式文法 PEG Parsing expression grammar · PEG 以有序选择、序列和不消费前看定义确定前缀识别结果的文法。 则直接给备选规则一个优先次序;它们对“出现多个选择”的处理含义不同。
单元任务:给解析器一份可验收的交付
使用本页文法,完成四项工作:算出三个集合和全部非空表项;解析 id+(id+id) 并构造 AST;找出 id+) 的拒绝点;说明若把 X → ε 填进 M [ X , id ] 会发生什么。
解答的集合与表就是本页所列结果。合法输入外层展开为 T X ,左 T 产生第一个 id,X 产生 + T X;右 T 走括号规则,内层 E 再形成一次加法。按前述左折叠构造得到 Add(Id, Add(Id, Id));内层括号决定第二个 Add 必须作为右子树。两个尾部 X 分别在 ) 与 $ 前消失,任何一步都没有消费空串。
非法输入的拒绝点是已经读过 id+、当前 [ T , ) ] 的空格。把空规则误填进 [ X , id ] ,会使分析器在合法语言之外提前结束当前表达式;在完整独立解析中,之后还会因多余 id 与 $ 不匹配而拒绝,但在嵌入式解析或错误恢复中可能错误切分输入。因此“最终仍报错”不能作为错误表项无害的理由。
验收时逐条核对:表项有 SELECT 依据;终结符匹配与展开分开;AST 保留括号决定的分组;错误位置来自第一条失效动作。进阶任务是在规范 LR(1) 公理库 规范 LR(1) 分析 Canonical LR(1) parsing 在项目中保存局部右上下文,以一个向前看token区分归约的LR构表方法。 页解释一个 SLR 冲突,并在LALR 公理库 LALR 状态合并 LALR parsing · Lookahead LR parsing 合并同核心LR(1)状态以压缩解析表,并检查合并产生的归约冲突。 页检查压缩状态是否保留这些区别。
参考资料
Stanford CS143, Lecture 7: Top-Down Parsing ,slides 2–14、31–32:预测栈与构表;本页使用独立的加法/括号例子
Aho, Lam, Sethi, Ullman, Compilers , 2nd ed., 2007,§4.4:LL(1) 表、左递归消除与左因子提取