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

本文详细介绍了消除文法左递归的重要性及其算法,包括直接和间接左递归的处理。通过给出的算法流程和伪代码,展示了如何将左递归转化为非递归形式。此外,还提供了一个Python实现的例子,用于实际操作文法产生式的左递归消除过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


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

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

其中\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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值