什么是消除直接左递归 - 编译原理解析

什么是消除直接左递归 - 编译原理解析

引言

在编译原理的语法分析中,文法(Grammar)是定义编程语言结构的基础。然而,某些文法形式会给解析器带来麻烦,尤其是左递归(Left Recursion)。本文将聚焦于"消除直接左递归",讲解它的定义、为什么需要消除,以及如何实现这一过程。

什么是左递归?

左递归是指文法中某个非终结符的产生式直接或间接地以自身作为第一个符号。例如:

css 复制代码
A -> A α | β
  • A 是一个非终结符。
  • αβ 是符号串(可以包含终结符和非终结符)。
  • A -> A α 是一个左递归产生式,因为推导时 A 立即又回到了 A

直接左递归 vs 间接左递归

  • 直接左递归 :非终结符直接以自身开头,如 A -> A α | β

  • 间接左递归 :通过其他非终结符间接回到自身,如:

    css 复制代码
    A -> B α
    B -> A β

本文主要讨论直接左递归的消除,间接左递归的处理稍复杂,通常需要额外的步骤。

为什么需要消除直接左递归?

在语法分析中,尤其是自上而下分析(Top-Down Parsing)(如递归下降分析或 LL 分析),直接左递归会导致解析器陷入无限循环。例如:

r 复制代码
E -> E + T | T

解析 E 时:

  1. 选择 E -> E + T
  2. 再次推导 E,又选择 E -> E + T
  3. 无限重复,无法终止。

这种无限递归会使解析器无法正常工作,因此需要消除直接左递归,以确保解析器能够从左到右逐步处理输入。

如何消除直接左递归?

消除直接左递归的基本方法是将左递归文法改写为等价的非左递归文法。假设文法形式为:

css 复制代码
A -> A α1 | A α2 | ... | A αn | β1 | β2 | ... | βm

其中:

  • αi 是以 A 开头的部分(左递归部分)。
  • βj 是不以 A 开头的部分(非左递归部分)。

改写步骤

  1. 引入一个新的非终结符 A'(读作"A撇"),用于处理递归部分。
  2. 将原产生式拆分为两部分:
    • A -> β1 A' | β2 A' | ... | βm A'(以非左递归部分开头,后面接 A')。
    • A' -> α1 A' | α2 A' | ... | αn A' | ε(处理所有递归部分,ε 表示空串,作为终止条件)。

示例

考虑文法:

r 复制代码
E -> E + T | T
  • E 是非终结符。
  • E + T 是左递归部分(α = + T)。
  • T 是非左递归部分(β = T)。
改写过程
  1. 引入新非终结符 E'

  2. 改写为:

    r 复制代码
    E -> T E'
    E' -> + T E' | ε
解释
  • E -> T E':表达式以一个基本项 T 开始,后面接 E' 处理可能的加法。
  • E' -> + T E' | εE' 表示"表达式尾部",可以是 + T 加上更多的尾部(递归),也可以是空串(结束)。

对于输入 T + T

  1. E -> T E',匹配第一个 T
  2. E' -> + T E',匹配 + T
  3. E' -> ε,结束。

这样,解析器从左到右逐步推导,不会出现无限递归。

更复杂的例子

文法:

css 复制代码
A -> A α | A β | γ | δ
  • 左递归部分:A αA βαβ 是不同的尾部)。
  • 非左递归部分:γδ

改写为:

css 复制代码
A -> γ A' | δ A'
A' -> α A' | β A' | ε

例如:

css 复制代码
S -> S a | S b | c | d

改写为:

bash 复制代码
S -> c S' | d S'
S' -> a S' | b S' | ε

输入 c a b

  1. S -> c S',匹配 c
  2. S' -> a S',匹配 a
  3. S' -> b S',匹配 b
  4. S' -> ε,结束。

消除直接左递归的意义

  • 解析器兼容性:使文法适用于自上而下分析,避免无限递归。
  • 等价性:改写后的文法生成的语言与原语言完全相同。
  • 清晰性:将递归部分分离,便于理解和实现。

注意事项

  • 消除直接左递归只适用于自上而下分析,自底向上分析(如 LR 分析)通常能直接处理左递归。
  • 如果文法中存在间接左递归,需先将其转化为直接左递归,再进行消除。

总结

消除直接左递归是编译原理中优化文法的重要技术,通过引入新非终结符并重写产生式,可以将左递归文法转化为等价的非左递归形式。这一过程解决了自上而下解析器的无限递归问题,使语法分析更加高效和可行。如果您对编译器设计感兴趣,不妨尝试手动改写一个左递归文法,体验这一过程的奥妙!

相关推荐
Asthenia04128 小时前
Spring AOP 和 Aware:在Bean实例化后-调用BeanPostProcessor开始工作!在初始化方法执行之前!
后端
Asthenia04129 小时前
什么是自上而下分析 - 编译原理剖析
后端
Asthenia041210 小时前
什么是语法分析 - 编译原理基础
后端
Asthenia041210 小时前
理解词法分析与LEX:编译器的守门人
后端
uhakadotcom10 小时前
视频直播与视频点播:基础知识与应用场景
后端·面试·架构
Asthenia041211 小时前
Spring扩展点与工具类获取容器Bean-基于ApplicationContextAware实现非IOC容器中调用IOC的Bean
后端
bobz96511 小时前
ovs patch port 对比 veth pair
后端
Asthenia041211 小时前
Java受检异常与非受检异常分析
后端