自顶向下语法分析
语法分析基础
编译的第一步是词法分析,第二步是语法分析。词法分析产生的结果是标识符,第二步是判断源程序文法是否合法。例如if{...}
就是不合法的文法,但是他产生的标识符都是正确的。
工作原理: 通过上下文无关文法产生的文法式,识别输入字符串是否是一个句子。从左至右每次读入一个字符进行规则匹配。
问题及解决
左递归
例如
规则为P->Pa |
消除递归的方法:
- 消除直接左递归
经过这样的转换之后产生的语言没有改变,但是消除了直接的左递归
再将它一般化得:
通过上面的方法消除了直接左递归,但是还可能经过若干次转换之后又形成了左递归,例如
将A代入就形成了左递归
消除左递归的一般算法:
条件: 不含回路(P=>P),不含以
- 将文法中所有规则按任意一种顺序排列成
按此顺序执行 - sqf
for i=1 to n do
for k=1 to i-1 do //i-1而不是n
将Pi中包含Pk的规则全部代入
消除Pi中的左递归 - 化简前面得到的文法,去除那些由起始符号永远无法到达的非终结符产生的规则
代入时Q代入的是经过第一次循环产生的R而不是最开始的R。化简时将从S无法到达的规则删除
回溯
如上图,如果此时读入i可以同时进入两个状态,需要读入更多的字符才可以判断具体是哪个状态,这样需要占据大量资源并且容易导致程序错误。
解决方法:
其中
通过反复提取左因子,我们可以将所有非终结符的所有First集变为两两不相交,但是回引入大量的新字符.
First(
如果A->
LL(1)
LL(1)标识从左向右推导,每次只看一个字符。
要构造不带回溯的自顶向下分析的文法(LL(1)文法)条件:
- 文法不含左递归(验证方法为使用消除左递归算法运行一遍,如果和原来相比没有变化则不含左递归)
- 每一个非终结符的各个产生式的First集两两不想交,即A->
, First( - 对于每个非终结符A,若它的First包含
,则First(A) Follow(A) =
使用LL(1)进行匹配的模式为:
- 如果要使用非终结符A进行匹配,输入符号为a,A的所有产生式为
- 若a
,则将 压入栈中进行下一步分析 - 如果没有任何一个First匹配成功,但是存在
,则使用Follow(A)进行判断,如果Follow(A)中有a,则将A弹出栈(将A弹出然后再将 压入) - 如果都没匹配上,这说明有语法错误
构造First集算法
应用如下规则,直到First不再增大为止:
- 如果X
(终结符集合),则First(X) = {X} - 如果
(非终结符集合),且有产生式X->a…,则将a假如First(X)中,如果X-> 也是一个产生式,则将 加入First(X)中 - 如果X->
,首先将First( ) - { }添加进First(x),如果First( )中包含 ,则将First( ) - { )添加进First(X)中。依次类推,如果推导最后First( )也包含 ,则将 添加进 中
构造Follow集算法
Follow是非终结符后随的终结符号的集合
构造规则:
- 对于开始符号S,将#(结束符)添加到Follow(S)中
- 若A->aB
是一个产生式( 可以是终结符也可以是非终结符),将First( ) - { )添加到Follow(B)中 - 若A->
B是一个产生式,或A-> B 而 ),则将Follow(A)添加到Follow(B)中
对于第二条规则很好理解,如果B后面是一个终结符,那么符合定义。如果是非终结符,那么First(A)就是第一个符号。而对于第二个则是由于它是这个式子中的最后一个字符,因此B的Follow集就是A的Follow集
例如
分析表的构造方法
分析表就是将根据First集和Follow集构造一张表判断读入当前字符后下一个状态是什么。
构造方法:
- 对于每个A->
(如A->Ba, A->c可以,A->Bc | a不能,它属于两条规则)执行如下步骤- 对于每个a
),将A-> 填入M[A, a]位置- 若
,则对于任何b ,将 填入M[A, b]中 - 对于所有无定义的M[A, a]标上出错标志
- 若
- 对于每个a
例: