语法分析概述
功能
根据文法规则,从源程序单词符号串中识别出语法成分,并进行语法检查。
基本任务
识别符号串S是否为某语法成分。
两种分析思路
- 自顶向下分析法
- 主要难题:消除左递归、避免回溯
- 主要方法:递归下降法(递归子程序法)、LL分析法
- 自底向上分析法
- 主要难题:句柄的识别
- 主要方法:算符优先分析法、LR分析法
自顶向下分析法
思想
从识别符号Z出发,不断地尝试用推导规则向下推导直至推导出给定的符号串S。
待解决的问题
可以想见,这样的分析可能面临两个问题。
- 左递归问题。如果文法有左递归的非终结符U,那么在尝试向下推导时,可能会由有U推导出U,一直得不到终结符。注意,直接左递归和间接左递归都会导致这种无限循环问题。
- 回溯问题。如果一个推导公式左部的非终结符对应右部的多个选择,则自顶向下分析时需要逐个尝试,如果发现尝试错误则需要退回,对其他选择进行尝试。其缺点是效率低下,严重浪费计算资源。回溯往往是工程上的灾难,在设计编译器时务必避免。
为了解决这两个问题,需要对文法进行一些改写,以适应自顶向下的分析过程。
左递归问题的解决
-
方法一:使用扩展BNF范式对左递归文法进行改写。为此提出两条规则:
-
规则一(提因子):如果有规则形如
U : : = x y ∣ x w ∣ . . . ∣ x z U::=xy|xw|...|xz U::=xy∣xw∣...∣xz
则将其改写为
U : : = x ( y ∣ w ∣ . . . ∣ z ) U::=x(y|w|...|z) U::=x(y∣w∣...∣z)
该规则有助于后续消除左递归。注意:若规则形如 U : : = x ∣ x y U::=x|xy U::=x∣xy,应该提取为 U : : = x ( y ∣ ϵ ) U::=x(y|\epsilon) U::=x(y∣ϵ),而不是 U : : = x ( ϵ ∣ y ) U::=x(\epsilon|y) U::=x(ϵ∣y)。这里的 ϵ \epsilon ϵ表示能够匹配后续任何字符。 -
规则二:转化为扩展BNF范式。形如
U : : = x ∣ y ∣ . . . ∣ z ∣ U v U::=x|y|...|z|Uv U::=x∣y∣...∣z∣Uv
的左递归规则,将其改写为
U : : = ( x ∣ y ∣ . . . ∣ z ) { v } U::=(x|y|...|z)\{v\} U::=(x∣y∣...∣z){v}
依次使用这两条规则(规则一可能无需使用),就能将左递归文法转化为扩展BNF范式。
-
-
方法二:将左递归文法改写为右递归文法。其规则为:
- 规则三:新增中间非终结符。若有规则:
P : : = P a ∣ b P::=Pa|b P::=Pa∣b
则改写为:
P : : = b P ′ P::=bP' P::=bP′
P ′ : : = a P ′ ∣ ϵ P'::=aP'|\epsilon P′::=aP′∣ϵ
通过新增中间非终结符,消除了左递归。
- 规则三:新增中间非终结符。若有规则:
回溯问题的判断
为了判断规则是否可能产生回溯问题,提出
F
I
R
S
T
FIRST
FIRST集的概念(还会在后续LL(1)法中详述)。对规则
U
:
:
=
α
1
∣
α
2
∣
.
.
.
∣
α
n
U::=\alpha_1|\alpha_2|...|\alpha_n
U::=α1∣α2∣...∣αn
定义
F
I
R
S
T
(
α
i
)
FIRST(\alpha_i)
FIRST(αi)为通过
α
i
\alpha_i
αi可能推出的串首终结符集合。形式化表述为:
F
I
R
S
T
(
α
i
)
=
{
a
∣
α
i
⇒
∗
a
.
.
.
,
a
∈
V
t
}
FIRST(\alpha_i)=\{a|\alpha_i\stackrel{*}{\Rightarrow} a...,a\in V_t\}
FIRST(αi)={a∣αi⇒∗a...,a∈Vt}
如果一条规则是不产生回溯的,那么它应该保证:
F
I
R
S
T
(
α
1
)
∩
F
I
R
S
T
(
α
2
)
∩
.
.
.
∩
F
I
R
S
T
(
α
n
)
=
∅
FIRST(\alpha_1)\cap FIRST(\alpha_2)\cap...\cap FIRST(\alpha_n)=\emptyset
FIRST(α1)∩FIRST(α2)∩...∩FIRST(αn)=∅
或者说对任意
1
≤
i
≠
j
≤
n
1\leq i\neq j\leq n
1≤i=j≤n,有:
F
I
R
S
T
(
α
i
)
∩
F
I
R
S
T
(
α
j
)
=
∅
FIRST(\alpha_i)\cap FIRST(\alpha_j)=\emptyset
FIRST(αi)∩FIRST(αj)=∅
回溯问题的解决
如果通过
F
I
R
S
T
FIRST
FIRST集判断确实可能产生回溯问题。其解决办法同样是通过提取左因子。若规则为:
U
:
:
=
X
V
∣
X
W
U::=XV|XW
U::=XV∣XW
则提取公因子得:
U
:
:
=
X
(
V
∣
W
)
U::=X(V|W)
U::=X(V∣W)
其中
X
X
X可能是公共字符,也可能是公共字符串。
一种实用的自顶向下分析法——递归下降法(递归子程序法)
对文法的每一个非终结符
U
U
U,都编写一个分析子程序。递归下降的分析过程就是自顶向下逐级地调用编写好的子程序进行分析。当有多个非终结符选项时,根据当前输入符号在哪个非终结符的
F
I
R
S
T
FIRST
FIRST集中,就能判断对应哪个非终结符,并调用相应的子程序。因此,递归下降法是预测的和面向目标的。
多个右部选项的选择:
- 形如 U : : = α 1 ∣ a α 2 U::=\alpha_1|a\alpha_2 U::=α1∣aα2时,简便起见,可先判断当前字符是不是 a a a,否则再判断是否在 F I R S T ( α 1 ) FIRST(\alpha1) FIRST(α1)中,否则报错;
- 形如 U : : = α 1 ∣ α 2 U::=\alpha_1|\alpha_2 U::=α1∣α2时,若当前字符在 F I R S T ( α 1 / α 2 ) FIRST(\alpha_1/\alpha_2) FIRST(α1/α2)中,则调用 α 1 / α 2 \alpha_1/\alpha_2 α1/α2的分析子程序,否则报错。
为什么递归下降法带有“递归”二字?因为虽然消除了左递归,但可能还有非左递归(右递归和自嵌入性递归),解析非终结符 U U U的子程序可能还要调用解析非终结符 U U U的子程序,就形成了递归。
例子:
对于文法
G
[
Z
]
G[Z]
G[Z]:
Z
:
:
=
′
(
′
U
′
)
′
∣
a
U
b
Z::='('U')'|aUb
Z::=′(′U′)′∣aUb
U
:
:
=
d
Z
∣
U
d
∣
e
U::=dZ|Ud|e
U::=dZ∣Ud∣e
第二条规则显然是左递归的,先改写文法消除左递归:
Z
:
:
=
′
(
′
U
′
)
′
∣
a
U
b
Z::='('U')'|aUb
Z::=′(′U′)′∣aUb
U
:
:
=
(
d
Z
∣
e
)
{
d
}
U::=(dZ|e)\{d\}
U::=(dZ∣e){d}
再判断是否有回溯:
- 第一条: F I R S T ( ′ ( ′ U ′ ) ′ ) = { ′ ( ′ } , F I R S T ( a U b ) = { a } FIRST('('U')')=\{'('\}, FIRST(aUb)=\{a\} FIRST(′(′U′)′)={′(′},FIRST(aUb)={a}
- 第二条: F I R S T ( d Z ) = { d } , F I R S T ( e ) = { e } FIRST(dZ)=\{d\}, FIRST(e)=\{e\} FIRST(dZ)={d},FIRST(e)={e}
第一条和第二条规则的各
F
I
R
S
T
FIRST
FIRST集均无交集,因此是无回溯的。
基于改造后的文法,构造递归下降分析程序如下:
递归下降法的优缺点:
- 优点:原理简单、便于手工实现、灵活、时空效率高;
- 缺点:递归下降程序难于自动生成。
参考书籍:张莉、史晓华、杨海燕、金茂忠《编译技术》