(1)上下文无关文法
定义:上下文无关文法是一个四元组G={N,T,P,S},其中
- N是非终结符的有限集合,这里默认为大写(比如“E”)
- T是终结符的有限集合,并且T和N交集为空。这里默认为小写(比如“id”)
- P是产生式的有限集合。产生式形如A->α,其中A在N中
- S是非终结符,被称为文法的开始符号
举例:N={S,动词,名词},T={羊,水,吃,草,喝},开始符号S,
P: S->名词 动词 名词
名词->羊
| 草
| 水
动词->吃
| 喝
(2)推导
定义:给定文法G,从G的开始符号S开始,用产生式的右部替换左侧的非终结符。不断重复此过程,直到不出现非终结符为止。最终的串称为句子。
举例:S->名词 动词 名词
->名词 吃 名词
->名词 吃 草
->羊 吃 草
最左推导:每次总选择最左侧的符号进行替换。比如:S->名词 动词 名词 ==》S->羊 动词 名词 ==》S->羊 吃 名词....
最右推导:同理,选择最右侧的符号进行替换。比如:S->名词 动词 名词 ==》S->名词 动词 水 ==》S->名词 喝 水....
(3) 分析树
分析树的父节点对应产生式的右部,子节点对应产生式的左部。一颗分析树从左到右的叶子节点是这颗分析树生成的结果
(4) 二义性与二义性的削除
定义:若文法G对同一个句子产生不止一颗分析树,那么G是二义的。造成二义性的原因是文法中缺少对优先级和结合性的规定。
二义性的削除:
(1) 改写二义文法为非二义文法。这样会使得推导的步骤增加,分析树的高度增高。例如:
(2) 为文法符号规定优先级和结合性使得分析过程中只能产生一颗分析树。这样便于理解,分析效率高。
(5) 自顶向下分析
语法分析:给定文法G和句子s,回答s是否能从G推导出来?
算法思想:从G的开始符号出发去推导,推出某个句子t,比较t和s。如果t==s,回答“是”,如果不存在t==s,则回答“否”。
例:对于上面那个“羊吃草”的例子。给“羊喝水”作为s,推出“羊吃草”“羊喝草”“羊吃草”后得到“羊喝水”,故回答“是”
这个算法效率较低,需要回溯。实际上我们需要线性时间的算法。
我们用前看符号避免回溯。
(6) 递归向下分析
算法思想:每一个非终结符构造一个分析函数,用前看符号指导产生式规则的选择
这个文法具有左递归的特点(下文介绍削除左递归),但是通过观察性质后,得到代码:
(7) 预测分析法
需要一张预测分析表。驱动器根据当前输入和栈顶内容不停查表。预测分析表M[A,a]中,所有非终结符构成行下标(包括特殊的结束标志#),终结符构成列下标。
- 匹配终结符:若栈顶与当前输入的终结符相等(且不是#),则分析器pop出栈顶符号,输入指针后移
- (展开非终结符。栈顶符号是非终结符X,当前指向终结符a,查表M[X,a],用M[X,a]的内容替换X。
- 若栈顶与当前输入都是#则分析成功并结束。
- 报告出错,调用错误恢复例程。
栈顶为E,指针指向id,查表得M[E,id]=E->TE',把E替换为TE'(压栈,所以注意栈内其实是E'T)
之后栈顶为T,指针指向id,查表得M[T,id]=T->FT',把T替换为FT' (注意顺序),之后步骤类似
如何构造预测分析表?需要两个集合FIRST集&FOLLOW集。
- 如果α是任意的文法符号串,则我们定义FIRST(α)是从α推导出的串的开始符号的结终符集合。
- 设A是一个非终结符,我们定义FOLLOW(A)是包含所有在句型中紧跟在A后面的终结符a的集合。
如何构造FIRST集?FIRST集是自底而上的构造的(从远离文法开始符号S的非终结符开始)
- 直接收取:对形如A->a…的产生式(其中a是终结符),把a收入到First(A)中
- 反复传送:对形入A->B…的产生式(其中B是非终结符),应把First(B)中的全部内容传送到First(A)中
- 连续使用以上两条规则,直到每一个FIRST集不再改变为止
如何构造FOLLOW集?FOLLOW集是自顶向下构造的(从文法的开始符号开始)
- 把#加入到FOLLOW(S)中。S是开始符号,#是结束标记。
- 直接收取:注意产生式右部的每一个形如“…Aa…”的组合(其中a是终结符),把a直接收入到Follow(A)中。
- 直接收取:对形如“…AB…”(B是非终结符)的组合,把First(B)除ε直接收入到Follow(A)中。
- 反复传送:对形如A->…B的产生式(其中B是非终结符),应把Follow(A)中的全部内容传送到Follow(B)中。(或A->…BC且First(C)包含ε,则把First(C)除ε直接收入到Follow(B)中,并把Follow(A)中的全部内容传送到Follow(B)中)
现在来构造预测分析表。
- 对于文法中的每个产生式A→???,执行第以下两步
- 对FIRST(???)中的每个终结符a,将A→???写进M[A, a]中。
- 若ε在FIRST(???)中,则对FOLLOW(A) 的每个终结符b(包括#),加入???到M[A,b]中
- 将M中没定义的表项均置为error
有的文法有二义性,预测分析表中一个表项会有多个条目
(8) 消除左递归(左递归:形如A->Aa)
1.削除直接左递归,可以采用这样的方式。
2. 消除文法的左递归
把间接左递归文法改写为直接左递归文法,然后用消除直接左递归的方法改写文法。例如:
3.提取左因子
P.S.当一个文法既有左递归又有左因子时,一般先削除左递归。
(8)LL(1)文法
递归下降子程序法和预测分析法都只能处理LL(1)文法。
当且仅当为文法G构造的预测分析表中不含多重定义的条目时G才能称为LL(1)文法。
G是LL(1)文法,当且仅当G的任何两个不同的产生式A→α|β满足下面的条件:
- 不存在这样的终结符a,使得a和β导出的串都以a开始。
- α和β最多有一个能导出空串。
- 如果β经过推导能得到ε,那么α不能导出以FOLLOW(A)中的终结符开始的任何串。
(9) 移动归约分析法(一种自底向上分析法)
可以把该过程看成是把输人串w收缩到文法开始符号的过程。在每一步归约中,如果一个子串和某个产生式的右部匹配,则用该产生式的左部符号代替该子串。
如果每步都能恰当地选择子串,我们就可以得到最右推导的逆过程。
设αβγ是文法G的一个句型,若存在S经过推导都能得到αAγ,A经过推导后能得到β,则称β是句型αβγ相对于A的短语。
特别的,若有A->β,则称β是句型αβγ相对于A->β的直接短语。一个句型的最左直接短语被称为句柄
通过剪掉句柄十分简单,但是需要解决两个问题:(1)确定句柄 (2)如何选择正确的产生式进行归约
现在用一个栈记住归约句柄的前缀,用一个分析表确定何时栈顶形成句柄,以及形成句柄后选择哪一个产生式进行归约
在移进-归约分析模式里,改变格局变化的动作有以下四种:
(1) 移进(shift)把当前输入的下一个字符放进栈
(2) 归约(reduce)句柄已形成,用何时的产生式左部代替句柄
(3) 接受(accept)宣告分析成功
(4) 报错(error)发现语法错误
输入序列abbcde
(10) LR分析
LR分析的核心是LR分析表&LR分析器。
LR表:s表示当前状态,a表示终结符,A表示非终结符,则action[s,a]表示栈顶为s,输入状态为a时应该进行的下一动作,goto[s,A]表示栈顶s和非终结符A时的下一状态转移
- si表示把当前输人符号和状态i压进栈
- rj表示按第j个产生式归约。
- acc表示接受,空白表示出错。
如何构造LR分析表? 先考虑这个:
文法G的LR(0)项目(简称项目)是在G的产生式右部的某处加点的产生式。
例如,产生式A→XYZ可以生成如下四个项目:
产生式A→∈只生成一个项目A→.。
直观地,项目表示:在语法分析过程中的某一时刻,我们已经看见了一个产生式所能推出的字符串的多大部分。
比如A→X.YZ中,我们已经看到了由X推出的字符串,期待看见由YZ推出的字符串.
现在我们来构造DFA。
- 以第一张图的为例,先把E->·E写出来,由于“·”后为E,把E的产生式,即E->·E+T和E->·T都补上。由于“·”后面有T,把T->·T*F和T->·F也加入....直到形成I₀
- 之后,假设当前在I0时刻,给它读入一个E,只有E->·E和E->·E+T期待读入E。此时“·”后移,产生I1的内容。同理,回到I0,读入T,只有E->·T和E->·T*F期待读入T,故“·”后移,产生I2内容。在右图中可以理解为I0读入E后变为I1的状态
反复计算,直到没有新状态加入为止。
现在可以构造LR文法表了。
以这个文法为例,改变它的形式并构造:
- 在I0时刻受到E的刺激,转换为状态I1,故表中第0行第E列填为1;I0时刻若受到T的刺激转为I2状态,故第0行第T列填2....其他类似
- 在I0时刻,受到id的刺激,转换为状态I4,故表中第0行第id列填为s4。其他类似
- 观察知I1时E'->E·时已经回到最开始了,故第1行第#列填接受
- 观察知I2时,已经有E->T·了,可以用E->T(2)归约它。故第2行第-/#列可以写r2,若E->T·*F,此时并不能用E->T(2)规约他,在接受*后它转为I7,故第2行第*列填s7。其他类似
(11) LR(1)分析。
移进-归约法会产生冲突。构造LR(1)DFA的方式与上文大致相同,但增加一个lookahead。
以这个文法为例,首先把它改写成右边的形式
- 首先写出以下内容。我们默认第一个是S'->·S……后面是终结符#。这个逗号后面的“#”,“=”之类的就是我们的lookahead
- 紧接着,有S->L=R和S->R。在上图中S后面什么都没有,所以我们补上:
- 在上图中,“·”后面的L和R也暴露了。根据L->*R / L->id补充以下项目。注意下图红圈圈住的L后面的“=”,故补上的两个后面要把"#"改成"="
- 因为R也暴露了,利用R->L补上这条。因为R后面空空如也,所以仍然是“#”
- 注意到这里又产生了L,由L可以再有L->·*R和L->·id。注意到上图中R->·L后面的L空空如也,故产生的L->·*R和L->·id的lookahead都是“#”,像下图那样追加到"="的后面
这样I0就构造好了。与上文类似的,给I0以S,L,R的刺激,产生I0,I1,I2...得到下表
需要注意的是,项目A->· α0α1α2.....αn,A->α0 · α1α2.....αn,A->α0α1 · α2.....αn,....一直到A->α0α1α2.....αn · 都有相同的lookaheads
(12) LALR(1)
通过观察上图,注意到I4和I10,I7和I11,I5和I13,I8和I12除了lookahead以外是相同的。这样的项目是同心的。我们可以把这样的项目合并。之后得到一个这样的DFA
- LR(1)DFA中不会发生的移进-归约冲突,LALR(1) DFA中也不会发生
- LALR(1)会出现LR(1)中没有的归约-归约冲突