<编译原理> 左递归消除算法

本文介绍消除文法左递归的算法,并输出新文法产生式。


消除左递归在语法分析阶段是比较重要的一个过程,尤其在自顶向下的分析过程中,编译器会尝试各个推导式,如果存在左递归,那么推导过程将会用永无止境。

比较显式的左递归我们称之为直接左递归,比如说

其中\alpha_i表示非空的表达式,\beta_j表示不以非终结符A开头的表达式。

那么我们知道对于非终结符A来说,最终只能以\beta_1\beta_2来结束,否则依然会含有非终结符A,因此我们可以将文法直接改为

然而并非所有的左递归都这么显而易见,还是会存在很多间接左递归

那么我们就需要一个通用的算法来消除所有的直接左递归和间接左递归。


⫸ Algorithm

算法也不难理解,大致流程如下:

  1. 将所有非终结符先进行整理编号,即所有非终结符转换为A_1,A_2,\cdots,A_n

  2. 对每一个A_i,我们将其所有满足i>j的产生式A_i\rightarrow A_j\alpha替换为A_{i} \rightarrow \beta_{1} \alpha\left|\beta_{2} \alpha\right| \cdots \mid \beta_{k} \alpha,其中A_{j} \rightarrow \beta_{1}\left|\beta_{2}\right| \cdots \mid \beta_{k}

  3. A_1开始至A_n,依次执行步骤2,每完成一个A_i的替换就消除当前该A_i的所有直接左递归

该算法的伪代码如下图所示:

我们可以注意到,每一个A_i完成替换后,那么A_i所有产生式右边的第一个符号要么是终结符,要么是编号j>i的非终结符A_j,在消除此时产生式中的直接左递归后,就只剩下终结符和编号j>i非终结符A_j打头的右部了。这样一来,最终的文法一方面不可能有直接左递归,另一方面不可能有间接左递归(因为只能小编号的非终结符推出大编号的非终结符)。


C ☺ D E

from copy import deepcopy

n = eval(input('请输入文法产生式的个数:'))
print('请输入文法产生式:')
gen = dict()
left = dict()
num = 0
for i in range(n):
    g = input().replace(' ', '')
    assert g[1:3] == '->'
    gen.setdefault(g[0], [[], []])
    left[g[0]] = num
    num += 1
    start = 3
    for i in range(3, len(g)):
        if g[i] == '|' or i == len(g) - 1:
            i += (1 if i == len(g) - 1 else 0)
            assert start < i
            if g[0].isupper():
                gen[g[0]][0].append(g[start:i])
            elif g[0].islower():
                gen[g[0]][1].append(g[start:i])
            start = i + 1

# 将没有出现在产生式左部的非终结符当作终结符
for key in gen:
    nonterminal = deepcopy(gen[key][0])
    for item in nonterminal:
        if item[0] not in left.keys():
            gen[key][0].remove(item)
            gen[key][1].append(item)
    assert gen[key][1] != []

# 替换Ai->Ajβ (j<i)
more_gen = dict()
for key in gen:
    rmv = []
    for item in gen[key][0]:
        if left[item[0]] < left[key]:
            rmv.append(item)
            for k in gen[item[0]][1]:
                gen[key][1].append(k + item[1:])
            for k in gen[item[0]][0]:
                gen[key][0].append(k + item[1:])
    for r in rmv:
        gen[key][0].remove(r)

   # 消除直接左递归
    nonterminal = deepcopy(gen[key][0])
    flag = True
    terminal = deepcopy(gen[key][1])
    for item in nonterminal:
        if item[0] == key:
            sym = '[' + key + '\']'
            more_gen.setdefault(sym, []).extend([item[1:], (item[1:]) + sym])
            if flag:
                for k in terminal:
                    gen[key][1].append(k + sym)
            gen[key][0].remove(item)
            flag = False

for key in gen:
    gen[key][0].extend(gen[key][1])
    gen[key] = gen[key][0]
gen.update(more_gen)

print("\nAfter Remove Left Recursion:")
for key in gen:
    print(key + ' -> ', end='')
    print(*gen[key], sep=' | ')

➷ Tips

  1. 读入输入时先除去所有空格,并规定终结符和非终结符仅能为单个字母,小写字母代表终结符,大写字母代表非终结符

  2. 所有产生式用一个字典存储,每个产生式左边的非终结符作为键值,每个键值对应一个列表,列表里有两个子列表(见3)

  3. 对每一个产生式的右部进行分类,非终结符打头的一类,终结符打头的一类

  4. 这里将没有出现在产生式左部的非终结符当作终结符,从而无需对这些非终结符进行编号

    ⚠️ Python中可迭代对象的直接赋值是引用,复制赋值则需要deepcopy()


Outcome

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 编译原理语法分析是编译器的关键步骤之一,用于检查源码是否符合文法规则,判断语义是否正确,生成中间代码等等。其中递归下降分析法是常用的语法分析方法之一。具体说,递归下降分析法从语法的开始符号开始,通过递归调用函数来实现对语法规则的分析。在解析过程中,程序使用递归下降算法按照语法的层次进行分解,将复杂的问题分解为简单的子问题,每个子问题对应一个子程序,依次调用子程序就可以完成对整个文法的语法分析。 在Java编译器的实现中,javac采用了递归下降分析法。Java语言的语法规则十分复杂,包括各种语句、表达式、变量类型等等,因此编写一个符合Java语法的递归下降分析程序也非常复杂。总的来说,javac的语法分析程序包括以下几个部分:词法分析器、语法分析器、符号表等。 词法分析器用于将源程序转化为词法单元(Token),然后传给语法分析器进行分析。语法分析器则根据Java语法规则,利用递归下降分析法来对Token序列进行分析,采用自顶向下的分析方法,逐步分析语法规则,直到最终将整个程序解析完成。在语法分析过程中,还需要使用符号表来确定变量类型,计算表达式的值等等,完成语义分析的任务。 总的来说,递归下降分析法是一种常用的语法分析方法,适用于多种编程语言的编译器实现,可以通过编译原理的学习和练习,更好地掌握这一方法,并开发出高质量的编译器。 ### 回答2: 编译原理语法分析是指将源程序转换为一种内部表示形式的过程,在此过程中,需要对源程序进行语法分析,也就是检查源程序是否符合语法规则。递归下降分析是一种常用的语法分析方法,它是基于自顶向下的语法推导方法实现的。 在进行递归下降分析时,我们需要将源程序按照相应的语法规则进行分解,然后递归分析每个生成式对应的子串,最终得到源程序的语法树。递归下降分析的一个优点是它能够直接产生语法树,因此更容易进行语义分析和代码生成等后续处理。 在Java编译器的实现中,javac使用了递归下降分析方法对源代码进行语法分析。在分析过程中,它将Java源代码分解为若干个符号,然后按照Java语法规则进行递归下降分析,最终得到语法树。与其他编译器相比,javac的递归下降分析方法具有较高的可读性和可维护性,因为它的代码与Java语法规则紧密相关。 同时,在语法分析的过程中,javac还涉及到词法分析、语法错误处理、符号表管理等方面的问题,这些都是编译原理的基础知识点,对于Java编译器的实现非常重要。 ### 回答3: 编译原理语法分析是编译器的一个重要组成部分,其作用是对程序源代码进行语法分析,将其转换为可以执行的目标代码。其中,递归下降分析法是一种常用的语法分析方法。 在递归下降分析法中,编译器会根据语法规则对源代码进行递归分析,以确定其语法正确性,并生成对应的语法树。该方法的基本思路是把一个复合语法规则拆分成多个较简单的子规则,然后通过递归调用不同的子规则来完成分析过程。这种方法可以较为高效地进行语法分析,但需要编写大量的递归函数来完成分析工作。 对于Java编译器而言,语法分析是其中一个非常重要的部分,因为Java中有严格的语法规则需要遵循。在语法分析中,Java编译器可以使用递归下降分析法进行处理,通过遍历源代码来分析其中的语法结构,生成对应的语法树,最终将其转化为可执行的目标代码。同时,在使用递归下降分析法时,Java编译器需要对不同的语法规则进行拆分和分析,并通过栈的方式来完成函数递归调用,以实现整个语法分析过程。 总之,递归下降分析法是编译原理语法分析中的一个重要方法,可以用于解析各种编程语言中的语法结构。在Java编译器中,递归下降分析法也是其中一个重要的语法分析方法,在语法分析过程中起着关键性的作用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值