简介
Parsing方法分类:
Universal: Cocke-Younger-Kasami Algorithm and Earley's Algorithm.
Top-down: LL(k).
Bottom-up: LR(k).
错误恢复模式:
Panic-Mode Recovery:
Phrase-Level Recovery:
Error Production:
Global Correction:
上下文无关文法:
终结符(terminal):组成串的基本符号。
非终结符(nonterminal):表示串的集合的语法变量,给出了语法分析和翻译的关键。
开始符号(start symbol):在一个文法中,一个nonterminal被指定为开始符号。
产生式(production):一条语法产生式,包含一个head/left side,转换符号->/::=,和body/right side。
推导过程:
最左推导(leftmost derivation):总选择每个句型的最左非终结符号展开。
最右推导(rightmost derivation):总选择每个句型的最右非终结符号展开,有时也被称为规范推导(canonical derivation)。
算法1:消除左递归
消除立即左递归:
对于 A -> Aα1 | Aα2 | ... | Aαm | β1 | β2 | ... | βn,将这些A产生式替换为:
A -> β1A' | β2A' | ... | βnA'
A' -> α1A' | α2A' | ... | αmA' | ε
消除循环左递归:
输入:没有环(A->A)或ε(A->ε)产生式的文法G。
输出:一个等价的无左递归文法G',但G'可能具有ε产生式。
方法:
消除循环左递归算法: 按照某个顺序将非终结符号排列为 A1, A2, ..., An. for i = (1 to n) { for j = (1 to i-1) { 将每个形如 Ai -> Ajγ 的产生式替换为产生式组 Ai -> δ1γ | δ2γ | ... | δ3γ, 其中 Aj -> δ1 | δ2 | ... | δk 是所有的 Aj 产生式; } 消除 Ai 产生式之间的立即左递归; }
算法2:提取左因子
目的:推迟产生式选择。
输入:语法规则G。
输出:等价的提取左因子语法G'。
方法:对于每个非终结符A,找出最长的公共前缀 α != ε。对于所有 A -> αβ1 | αβ1 | ... | αβn | γ,替换为:
A -> αA' | γ
A' -> β1 | β1 | ... | βn
重复这样的行为直到没有同一个非终止符的两个产生式含有公共前缀。
Top-Down Recursive-Descent Parsing - LL(k)
递归下降parser典型过程:
void A() { Choose an A-production, A ::= X1X2...Xk; for i = (i to k) { if (Xi is a nonterminal) call procedure Xi(); else if (Xi equals the current input symbol a) advance the input to the next symbol; else call error recovery procedure; } }
FIRST和FOLLOW集
FIRST(α):α是任意一个语法符号,该集合是由α推导出来的句子的第一个终止符的集合。
计算时,不断应用下列规则,直至没有新的终结符号或ε可以被加入到任何FIRST集为止:
1) 如果X是一个终止符,则FIRST(X) = {X}。
2) 如果X是一个非终止符,且存在产生式 X ::= Y1Y2...Yk (k ≥ 1)。如果FIRST(Yi)包含a,并且FIRST(Y1), ... FIRST(Yi-1)都可以推导出ε,那就把a加入到FIRST(X)中。如果所有FIRST(Yi)都包含ε,那就把ε加入FIRST(X)中。
3) 如果存在 X ::= ε,那就把ε加入FIRST(X)中。
FOLLOW(A):A是一个非终止符,该集合是可以紧跟在A的句型右边的终止符的集合。
计算时,不断应用下列规则,直至没有新的终结符号被加入到任何FOLLOW集为止:
1) 如果S是开始符号,则把$添加至FOLLOW(S)中,$是输入串结束标记。
2) 如果存在产生式 A ::= αBβ,那么FIRST(β)中除了ε之外所有符号都在FOLLOW(B)中。
3) 如果存在产生式 A ::= αB,或存在产生式 A ::= αBβ 且 FIRST(β) 包含ε,那么FOLLOW(B)包含FOLLOW(A)中所有符号。
LL(1)文法
第一个L表示自左而右扫描(Left to right),第二个L表示产生最左推导(Leftmost Derivation),1表示每次向前看一个字符。
有两个限制,对于G的任意两个不同产生式 A ::= α | β:
1) FIRST(α)和FIRST(β)须为不想交集。
2) 如果FIRST(α)包含ε,那么FIRST(α)和FOLLOW(A)须为不想交集,对β同理。
算法3:构造预测分析表
介绍:将文法G的FIRST和FOLLOW集合的信息整合至一个预测分析表M[A, a]中,使得parser根据预测分析表选择相应的产生式。其中A是一个非终结符号,a是一个终结符号或$。
输入:文法G。
输出:预测分析表M。
方法:对于每个文法的每个产生式A ::= α, 进行如下处理:
1) 对于FIRST(α)中的每个终结符号α,将 A ::= α 加入到M[A, a]中。
2) 如果FIRST(α)包含ε,那么对于FOLLOW(A)中的每个终结符号b,将 A ::= α 加入到M[A, b]中。
如果FIRST(α)包含ε,且FOLLOW(A)包含$,将 A ::= α 添加到M[A, $]中。
3) 完成操作后对于空条目,设置为error。
算法4:表驱动预测语法分析器
输入:一个串w,文法G的预测分析表M。
输出:如果w在L(G)中,则输出w的一个最左推导,否则给出一个错误指示。
方法:最初,paser的configuration如下:输入缓冲区为w$,栈顶是G的开始符号S,它下面是$。
预测分析算法: 设置ip指向w的第一个符号, 其中ip是输入指针, 令X为栈顶符号; while (X != $) { // 即栈不为空 if (X == *ip) { stack.pop(); ++ip; } else if (X是一个终结符号) error(); else if (M[X, a] == error) error(); else if (M[X, a] == X ::= Y1Y2...Yk) { 输出产生式 X ::= Y1Y2...Yk; stack.pop(); stack.push(Yk, Yk-1, ..., Y1); } X == stack.top(); }
Bottom-Up Parsing - LR(k)
第一个L表示自左而右扫描(Left to right),第二个R表示反向(Reverse)构造一个最右推导序列,而不是代表最右推导(Rightmost derivation)。k表示每次向前看k个字符。
自底向上的parsing通过移动-规约方法得以实现。对输入串从左到右扫描,扫描过程中自底向上进行语法分析,就可以反向构造一个最右推导。“句柄”是和某个产生式体匹配的子串,对句柄的规约代表了相应最右推导的反向步骤。正式而言,如果有 S => αAw => αβw,那么紧跟α的产生式 A ::= β 是αβw的一个句柄。其中w一定只包含终结符号。
移动-规约语法分析用一个栈来保存文法符号,并用一个缓冲区来存放将要进行语法分析的其余符号。句柄在被识别之前总是出现在栈的顶部。parsing的过程就是如下configuration变化的过程:
栈$,缓冲区w$ => 栈$S,缓冲区$。
移动-规约包含如下四个动作:
移入(shift):将下一个输入符号从缓冲区移入栈。
规约(reduce):将栈顶出现的句柄替换为相应产生式的非终结符号。
接受(accept):宣布语法分析过程成功。
报错(error):报告语法错误并调用错误恢复例程。
SLR - Simple LR Parsing
项(item):一个文法G的LR(0)项屎G的一个产生式再加上一个位于他的body某处的点,例如,产生式 A ::= XYZ 产生了四个项:
A ::= ·XYZ, A ::= X·YZ, A ::= XY·Z, A ::= XYZ·
产生式 A ::= ε 只包含一个项目:A ::= ·
项集的表示:可以通过一个序对表示项集。第一个数字表示产生式编号,第二个整数是点的位置。
规范LR(0)项集族 - canonical LR(0) collection
这样一组集合提供了构建一个DFA的基础,可以做出parse的决定。构建这样一个集合需要如下部分:
增广文法(augmented grammar):如果G是一个以S为开始符号的文法,则G的增广文法G'就是G中加上新的开始符号S'和S' ::= S而得到的文法。新的产生式用于判断何时规约结束。
闭包函数(CLOSURE):如果I是文法G的一个项集,那么CLOSURE(I)就是根据如下规则得到的项集合:
1) 一开始将I中各个项目加入到CLOSURE(I)中。
2) 如果在CLOSURE(I)包含 A ::= α·Bβ,且 B ::= γ,且 B ::= ·γ 不在CLOSURE(I)中,那么就将其加入。不断应用这个规则,直到没有新的项目可以加入为止。
SetOfItems CLOSURE(I) { J = I; repeat { for (J中的每一个项 A ::= α·Bβ) for (G的每个产生式 B ::= γ) if (项 B ::= ·γ 不在J中) 将 B ::= ·γ 加入J中; } return J; }
可以将项目分为如下两类:所有非内核项可以由内核项求闭包而得出。
1) 内核项(kernel item):包括初始项 S' ::= ·S 和所有点不在最左端的项。
2) 非内核项(Nonkernel item):除了 S' ::= ·S 所有点在最左端的项。
转移函数(GOTO):转换函数GOTO(I, X),其中I是一个项集而X是一个文法符号。GOTO(I, X)被定义为I中所有形如[A ::= α·Xβ]的项所对应的项[A ::= αX·β]的集合的闭包。直观而言,GOTO(I, X)描述了当输入为X时离开状态I的转换。
计算规范LR(0)项集族C: void items(G') { C = {CLOSURE({[S' ::= ·S]})}; repeat { for (C中的每个项集I) for (每个文法符号X) if (GOTO(I,X)非空且不在C中) 将GOTO(I,X)加入C中; } until 在某一轮中没有新的项集被加入到C中; }
LR语法分析算法
LR语法分析表:包含两部分,语法分析动作函数ACTION和转换函数GOTO。
ACTION:有两个参数:状态i和终结符号a(或者是终结符号$)。ACTION[i, a]取值可以有下列四种形式:
1) shift j:其中j是一个状态。parser把输入符号a移入栈中,通过状态j来表示a。
2) reduce A::=β:把栈顶的β规约为A。
3) accept。
4) error。
GOTO:如果GOTO[Ii, A] = Ij,那么GOTO也把状态i和一个非终结符号A映射到状态j。
LR Parser的格局(configuration):包括栈和余下的输入。
(s0s1...sm, aiai+1...an$),该格局表示如下最右句型:X1X2...Xmaiai+1...an。
LR Parser的行为:当决定下一个动作时,首先查询条目ACTION[sm, ai],对于ACTION的四种动作,有四种如下格局变化:
1) 如果ACTION[sm, ai] = shift s,则进行一次移入动作,将下一状态s移入栈,进入格局:(s0s1...sms, ai+1...an$)。
2) 如果ACTION[sm, ai] = reduce A::=β,则进行一次规约,进入格局:(s0s1...sm-|β|s, aiai+1...an$),其中s = GOTO[sm-|β|, A]。
3) 如果ACTION[sm, ai] = accept,则分析完成。
4) 如果ACTION[sm, ai] = error,则调用错误恢复例程。
LR语法分析算法:
输入:一个输入串w和一个LR语法分析表,该表描述了文法G的ACTION和GOTO函数。
输出:如果w在L(G)中,则输出w的自底向上语法分析过程中的规约步骤,否则给出错误提示。
方法:最初,语法分析器栈中初始状态为s0,输入缓冲区中内容为w$,然后根据分析表执行动作。
LR Parsing Program: a = buffer[0]; while(1) { s = stack.top(); if (ACTION[s, a] == shift j) { stack.push(j); ++a; } else if (ACTION[s, a] == reduce A::=β) { stack.pop() |β| times; stack.push(GOTO[stack.top(), A]); print A::=β; } else if (ACTION[s, a] == acc) break; else call error.recovery(); }
构造SLR语法分析表:
输入:一个增广文法G'。
输出:G'的SLR语法分析表函数ACTION和GOTO。
方法:
1) 构造G'的规范LR(0)项集族C = {I0, I1, ..., In}
2) 根据Ii构造得到状态i,状态i的语法分析动作如下决定:
a) 如果[A ::= α·aβ]在Ii中,且GOTO(Ii, a) = Ij,那么将ACTION[i, a]设置为"shift j",这里a必须是终结符号。
b) 如果[A ::= α·]在Ii中,那么对于FOLLOW(A)中的所有a,将ACTION[i, a]设置为"reduce A ::= α",这里A不等于S'。
c) 如果[S' ::= S·]在Ii中,那么将ACTION[i, $]设置为"接受"。
任意一步发生冲突,则该文法不是SLR(1)的文法。
3) 状态i对于各个非终结符号A的GOTO转换使用如下规则构造:如果GOTO[Ii, A] = Ij,则GOTO[i, A] = j。
4) 规则(2)和(3)所有未定义条目设置为报错。
5) 初始状态就是根据[S' ::= ·S]构造得到的状态。
LR(1)构造方法:
首先构造CC集,其中CLOSURE和GOTO有轻微修改,使得parser具备了向前看的能力。
SetOfItems CLOSURE(I) { repeat { for (each item [A::=α·Bβ, a] in I) for (each production B::=γ in G') for (each terminal b in FIRST(βa)) I = I ∪ {[B::=·γ, a]}; } until no more items can be inserted into I; return I; } SetOfItems GOTO(I, X) { J = ø; for (each item [A::=α·Xβ, a] in I) J = J ∪ [A::=αX·β, a]; return CLOSURE(J); } void items(G') { C = CLOSURE({[S'::=·S, $]}); repeat { for (each item sets I in C) for (each grammar symbol X) if (GOTO[I, X] != ø && not included in C) C = C ∪ {GOTO[I, X]}; } until no more item sets can be inserted into C; }
构造LR(1)语法分析表:
输入:一个增广文法G'。
输出:G'的规范LR语法分析表ACTION和GOTO。
方法:
1) 构造G'的LR(1)CC = {I1, I2, ..., In};
2) parser的状态i根据Ii构造得到,状态i的分析动作规定如下:
a) 如果[A ::= α·aβ, b]在Ii中,且GOTO(Ii, a) = Ij,那么令ACTION[i, a]为"shift j",这里a必须为一个终结符号。
b) 如果[A ::= α·, a]在Ii中,且A != S',那么令ACTION[i, a]为"reduce A ::= α"。
c) 如果[S' ::= S]在Ii中,那么令ACTION[i, $]为"acc"。
产生任何冲突则该文法不是LR(1)文法,报告错误并退出。
3) 状态i对于非终结符号的转换基于如下规则:
如果GOTO(Ii, A) = Ij,那么GOTO[i, A] = j。
4) 所谓未填充条目设置为error。