以下为参考课件与《编译技术》(张莉等著)的个人整理,若有错误欢迎指出
第四章 语法分析(基础篇)
一、概述
1、语法分析功能:从单词符号串中识别出语法成分,并进行语法检查
2、两大类方法
自顶向下分析法 | 自底向上分析法 | |
---|---|---|
思路 | 从识别符号开始推出句子 | 从句子规约到识别符号 |
主要问题 | 左递归(无法处理)、回溯问题(带预测的) | 句柄的识别问题 |
主要方法 | 递归子程序法、LL分析法 | 算符优先分析法、LR分析法 |
本文基础篇 仅介绍自顶向下分析法的递归子程序法
二、自顶向下分析
1、特点
带预测、需要回溯因此效率低
自顶向下分析方法面临着无法处理左递归(不断调用自身、死循环)和回溯的问题,因此下面讨论解决方法
2、消除直接左递归的方法
(1)方法一:使用扩充的BNF改写文法,如 E : : = E + T ∣ T ⟹ E : : = T { T } E::=E+T|T\quad \Longrightarrow E::=T\{T\} E::=E+T∣T⟹E::=T{T}
规则
-
提取因子
注意:最长匹配规则 E : : = x ∣ x y ⟹ 改 E : : = x ( y ∣ ε ) , 不 要 ( ε ∣ y ) E::=x|xy\quad \overset改\Longrightarrow E::=x(y|\varepsilon) ,不要(\varepsilon|y) E::=x∣xy⟹改E::=x(y∣ε),不要(ε∣y)
-
U ∷ = x ∣ y ∣ … … ∣ z ∣ U v ⟹ 改 U : : = ( x ∣ y ∣ … … ) { v } U∷ =x|y|……|z|Uv \quad \stackrel改 \Longrightarrow U::=(x|y|……)\{v\} U::=x∣y∣……∣z∣Uv⟹改U::=(x∣y∣……){v}
个别情况不太推荐 { } \{\} {}的写法(原因同下文“回溯”中提到的空语句的情况),更推荐此处使用方法二
(2)方法二:左递归改成右递归
若 P ∷ = P α ∣ β P∷ =Pα| β P::=Pα∣β,则可改写为 P ∷ = β P ’ , P ’ ∷ = α P ’ ∣ ε P ∷ = β P’,P’ ∷ = αP’| \varepsilon P::=βP’,P’::=αP’∣ε
(3)小结:消除一般左递归的算法
步骤:
-
把文法的非终结符整理成一种顺序,使后面的规则中仅包含前面规则左部的非终结符,如:
A 1 : : = δ 1 ∣ δ 2 ∣ … … δ k A 2 : : = A 1 r … … A 3 : : = A 2 u ∣ A 1 v … . . \begin{aligned} A1 &::= δ 1|δ 2|……δ k\\ A2 &::= A1 r……\\ A3 &::= A2u | A1v…..\\ \end{aligned} A1A2A3::=δ1∣δ2∣……δk::=A1r……::=A2u∣A1v….. -
从上往下,依次用上面的规则消去当前规则中的非终结符。这个过程中既可以压缩文法,也可以检查出直接/间接的左递归(用上面提到的方法删除)。
3、回溯
(1)对于规则 U : = α 1 ∣ α 2 ∣ . . . , U ∈ V n U:=\alpha_1|\alpha_2|...,U \in V_n U:=α1∣α2∣...,U∈Vn,定义(每个选择所推出的终结符号串的)首符号集: F I R S T ( α i ) = { a ∣ α i ⟹ ∗ a . . . , a ∈ V t } FIRST(\alpha_i)=\{a|\alpha_i \stackrel*\Longrightarrow a..., a\in V_t\} FIRST(αi)={a∣αi⟹∗a...,a∈Vt}。
(2)不带回溯文法的充分必要条件
对于每一个非终结符A的任意两条规则 A : : = α ∣ β A::=\alpha|\beta A::=α∣β,下列条件成立:
-
非左递归
-
F I R S T ( α i ) ∩ F I R S T ( α j ) = Φ , i ≠ j FIRST(\alpha_i)\cap FIRST(\alpha_j) =\Phi, i \neq j FIRST(αi)∩FIRST(αj)=Φ,i=j
也就是每条支路推导出来的字符串无交集,这样就不用试探
-
若 β ⟹ ε \beta \Longrightarrow\varepsilon β⟹ε, 则 F I R S T ( α ) ∩ F O L L O W ( A ) = Φ FIRST(\alpha) ∩ FOLLOW(A) = \Phi FIRST(α)∩FOLLOW(A)=Φ
A的后继符号集合 F O L L O W ( A ) = { a ∣ Z ⟹ ∗ . . . A a . . . , a ∈ V t } FOLLOW(A)=\{a|Z\stackrel*\Longrightarrow...Aa...,a\in V_t\} FOLLOW(A)={a∣Z⟹∗...Aa...,a∈Vt}(特别地,若 Z ⟹ ∗ . . . A Z\stackrel*\Longrightarrow...A Z⟹∗...A,则 ε ∈ F O L L O W ( A ) \varepsilon \in FOLLOW(A) ε∈FOLLOW(A))
这里强调的是推出空语句的影响。如果能推出空语句,那还要求这条之后能推出的所有符号不能与首符号集相交,不然就无法确定到底是这里取到这个符号,还是后续推导得到的。
举一个具体例子:
如 U : : = T a , T : : = a ∣ ε U::=Ta,T::=a|\varepsilon U::=Ta,T::=a∣ε,如果要对句子 a a a进行自顶向下的检查,根据递归子程序法(最左),就不知道 T T T这里到底取哪一个了,因此也会带来回溯。
(3)消除回溯的方法
-
改写文法:反复提取左因子(这样就能实现首字符集的交为空)
-
超前扫描(其实就是多看几个符号,但不处理,所以也有一点回溯)
三、递归子程序法
对应最左推导
具体做法:针对每一个非终极符都编写一个分析程序,每个分析程序根据规则的右部去匹配输入串
约定:在调用子程序前先读好下一个词并把其类别码(这个由词法分析程序得到,详细可看上一篇文章)存放在sym,程序结束前也要先读好
《编译技术》P88-90有具体例子,基本步骤就是先对文法进行处理(消除左递归和回溯),然后对每一个非终结符编写子程序即可(取词,按照规则右部,是非终结符就调用子程序,是终结符就判断类别码)。