第四章:文法中的递归以及消除方法

在介绍递归文法之前,首先介绍一下递归下降分析器及其原理,然后分析右递归是如何处理的,再来分析左递归和间接左递归。

递归下降分析器

自顶向下语法分析的目的是为输入串寻找最左推导,或者说,从根节点(文法开始符号)开始,自上而下,从左到右地为输入字符串建立一棵分析树,并以预先确定的顺序创建分析树的节点。这种自顶向下分析的一般形式,称之为递归下降分析法。“下降”表示自顶向下,“递归”表示可能会调用自身。

在递归下降分析器中,最简单的一种,就是 LL(1) 递归下降分析器。我们尝试来构造一个简单的满足 LL(1) 的递归下降语法分析器。语法结构如下

stat : assign_stat
	 | ifstat
	 | return_stat
	 ;
assign_stat : ID '=' expr ;
ifstat : IF expr THEN stat ;
return_stat : RETURN expr ;
expr : ID '<' NUM
	 | NUM
	 ;

当我们解析 if x < 0 then x = 0 这句语句时,会构造成如图 4-1 所示的解析树

(图 4-1)

解析树里面含有语句的所有语法结构的信息。而解析就是将线性的词法单元序列组成带有结构的解析树。

语法解析器能检查句子的结构是否符合语法规范(语言实际上就是合法句子的集合)。为了验证句子是否合法,解析器必须识别句子的解析树。不过解析器实际上并不需要构造出形式上的树型结构,只要识别出各种句子结构和相关的词法单元即可。解析器不必构造出具体的解析树,只要为解析树中的指定子结构编写专用的函数,就能从解析函数的调用序列中隐式的得到解析树的信息。比如函数 f(),在匹配其子节点时会调用相应的函数,而需要匹配词法单元时会调用 match() 的辅助函数。顺着这条思路,可以为 return x+1 这条语句编写如下的识别函数

void stat() {
    returnstat(); }
void returnstat() {
    match("return"); expr();}
void expr() {
    match("x"); match("+"); match("1"); }

match() 函数将输入流里的词法单元与传入它的参数进行比较,然后将输入指针往前移动。但是对于 stat 语法分支,情况就会变得复杂,因为需要考虑到三种不同的情况,

void stat() {
   
	if (超前扫描词法单元是 return) returnstat();
	else if (超前扫描词法单元是 ID) assignstat();
	else if (超前扫描词法单元是 IF) ifstat();
	else 错误处理
}

这种自顶向下的语法解析器,从解析树的顶部开始,一直向下处理,直到叶子节点。

LL(1) 算法

LL(1)语法解析器,是一种相对来说最简单的自顶向下的语法解析器,两个 L 都表示 left-to-right,第一个 L 表示解析器按照从左到右的顺序解析输入内容,第二个 L 表示下降解析时也是按照从左到右的顺序遍历子节点。1 代表决定语法解析器的每一步动作时向前扫描一个词法单元。

正如上面构造出的简单的递归下降分析器,在 stat 中,如果是 ifstat 子句,我们会调用 ifstat() 函数,定义如下

void ifstat() {
   
	match("if");
	expr();
	match("then");
	stat();
}

在 ifstat 中,匹配到 then 后,后面还是一个 stat 子句,调用 stat 函数进行匹配。这里就是一个右递归。在 ifstat 匹配完成后,调用 stat 匹配 then 后面的部分。递归下降分析器通过调用自身,是可以处理右递归的。

左递归

对于形如

expr -> expr + term

这样的产生式,右部的最左符号与产生式左部的非终结符相同,这样的产生式即为左递归产生式。假定 expr 对应的过程要使用这个产生式,因为右部是由 expr 开始的,expr 过程会被递归调用,导致无限循环。只有右部终结符与超前扫描符号匹配时,超前扫描符号才会改变。这个产生式中,右部是以非终结符 expr 开始的,输入符号在递归调用期间没有机会改变,所以导致无限循环。

左递归如何消除

如果 文法 1 具有一个非终结符 A 使得对某个字符串 α \alpha α 存在推导
A    ⟹    A α (文法1) A \implies A\alpha \tag {文法1} AAα(1)
则称 文法1 是左递归的。

考虑下面 文法2
A    ⟹    B | a | C B D (1) A \implies \text{B | a | C B D} \tag 1 AB | a | C B D(1)
B    ⟹    C | b (2) B \implies \text{C | b} \tag 2 BC | b(2)
C    ⟹    A | c (3) C \implies \text{A | c} \tag 3 CA | c(3)
D    ⟹    d (4) D \implies d \tag 4 Dd(4)
由该文法能产生诸如 a, cbd 等的串。能够发现,该文法不是直接左递归的,因为并没有直接的类似于文法1一样的产生式。但是,文法 G 确实是左递归的,因为我们可以通过推导,获得
A    ⟹    A | c | b | a | C B D A \implies \text{A | c | b | a | C B D} AA | c | b | a | C B D
这就是左递归文法了。再来分析一下这个文法,计算一下文法的 FIRST 和 FOLLOW 集合。

在计算 FIRST 和 FOLLOW 集合之前,首先介绍一下这两个集合。

如果 α \alpha α 是任意的文法符号串,则我们定义 F I R S T ( α ) FIRST(\alpha) FIRST(α) 是从 α \alpha α 推导出的串的开始符号的终结符集合,即
F I R S T ( α ) = { a ∣ α    ⟹    a ⋯ , a 是 终 结 符 } FIRST(\alpha) = \{a | \alpha \implies a \cdots,a 是终结符 \} FIRST(α)={ aαaa}
如果
α    ⟹    ϵ \alpha \implies \epsilon αϵ
ϵ \epsilon ϵ 也属于 F I R S T ( α ) FIRST(\alpha) FIRST(α) 。运用如下规则,来计算文法符号 X 的 FIRST(X) 集合,直到没有终结符或者 ϵ \epsilon ϵ 可加到某个 FIRST 集合为止

  • 如果 X 是终结符,则 FIRST(X) 是 {X}
  • 如果 X    ⟹    ϵ X \implies \epsilon Xϵ 是一个产生式,则将 ϵ \epsilon ϵ 加入到 FIRST(X) 中
  • 如果 X 是非终结符,且 $ X \implies Y_1Y_2Y_3…Y_n$ 是一个产生式,则
    • F I R S T ( Y i ) FIRST(Y_i) FIRST(Yi) 中所有符号在 FIRST(X) 中
    • 若对于某个 i,a 属于 F I R S T ( Y i ) FIRST(Y_i) FIRST(Yi) ϵ \epsilon ϵ 属于 F I R S T ( Y 1 ) FIRST(Y_1) FIRST(Y1) ,……。 F I R S T ( Y i − 1 ) FIRST(Y_{i-1}) FIRST(Yi1) ,即 Y 1 . . . Y i − 1    ⟹    ϵ Y_1...Y{i-1} \implies \epsilon
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫步旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值