左递归

在电脑科学里面，左递归是一种递归的特殊状况。

在上下文无关文法内里的说法，若一个非终结符号（non-terminal）`r`有任何直接的文法规则或者透过多个文法规则，推导出的句型（sentential form）其中最左边的符号 又会出现`r`，则我们说这个非终结符号`r`是左递归的。

使用类似的方式我们可以定义出某文法本身是左递归的。

## 定义

"一个文法是左递归的，若我们可以找出其中存在某非终结符号A，最终会推导出来的句型(sentential form)里面包含以自己为最左符号(left-symbol)的句型"

### 直接左递归

直接左递归（Immediate left recursion）以下面的句型规则出现：

$$A \to A\alpha \mid \beta$$

这里$\alpha$跟$\beta$代表不同的非终结符号跟终结符号组成的序列，并且$\beta$不一定要包含$A$。举例来说，以下规则

$$\mathit{Expr} \to \mathit{Expr} + \mathit{Term}$$

就是一个直接左递归的例子。这规则的递归下降分析器(recursive descent parser)可能会像这样：

```text
function Expr()
{  
    Expr();  match('+');  Term();
}
```

然后这个递归下降分析器在尝试去解析包含此规则的文法时，会陷入一个无穷的递归。

### 间接左递归

间接左递归(indirect left recursion)最简单的形式如下：

$$A \to B\alpha \mid C$$

$$B \to A\beta \mid D,$$

这规则可能产生 $A \Rightarrow B\alpha \Rightarrow A\beta\alpha \Rightarrow \ldots$ 这种生成。

简单的说，间接左递归就是，并非在一条规则内完成左递归，而是在许多条规则之后，于产生的句子最左边出现了一开始的非终结符号。

更一般化的说法，对非终结符号$A_0, A_1, \ldots, A_n$，间接左递归被定义为以下的型态：

$$A_0 \to A_1\alpha_1 \mid \ldots$$

$$A_1 \to A_2\alpha_2 \mid \ldots$$

$$\cdots$$

$$A_n \to A_0\alpha_{n+1} \mid \ldots$$

这里的$\alpha_1, \alpha_2, \ldots, \alpha_n$都是一堆终端与非终结符号的序列。

## 在由上而下语法分析(top-down parsing)里容纳左递归

一个包含左递归的形式文法不能以简易的 递归下降分析器进行语法分析，除非将文法转变为weakly equivalent 的右递归形式 (相对的，在LALR分析器里面则比较偏好左递归，因为比起右递归来说会使用比较少的堆叠)；然而，比较复杂的由上而下(top-down)语法分析器里面可以借由使用缩减(by use of curtailment)来实做一般的上下文无关文法。在2006年, Frost 和 Hafiz 提出一个算法，可以容纳包含直接左递归生成规则的 模糊文法(ambiguous grammers)。在2007年，Frost，Hafiz和Callaghan 将此算法延伸为一个完整的，可以适用并在多项式时间内处理直接或间接左递归，而且可以为高度模糊文法接近指数数目的分析树，产生小一些多项式空间的表示法。这些人后来在Haskell程式语言里面以这个算法实做了一个的分析器组合(parser combinator)的集合。

## 移除左递归

### 移除直接左递归

一个一般化后移除直接左递归的算法如下所述。这个方法已经有过许多的改进，包括Robert C. Moore所撰写，名为"Removing Left Recursion from Context-Free Grammars"的改进。

对于每个规则如下

$$A \rightarrow A\alpha_1\,|\,\ldots\,|\,A\alpha_n\,|\,\beta_1\,|\,\ldots\,|\,\beta_m$$

(注意这里：

- A 是一个有左递归的非终端符号
- $\alpha$ 是一个终端与非终端符号的序列，而且不为空字串 ($\alpha \ne \epsilon$)
- $\beta$ 是一个不以A开头的，以终端与非终端符号组成的序列)

将A的规则改成以下规则：

$$A \rightarrow \beta_1A^\prime\, |\, \ldots\, |\, \beta_mA^\prime$$

然后对新创造出来非终端符号的规则

$$A^\prime \rightarrow \epsilon\, |\, \alpha_1A^\prime\, |\, \ldots\, |\, \alpha_nA^\prime$$

这个新创造出来的符号常被称为"尾巴"(tail)，或者"rest"(剩余)

举例，考虑以下规则

$$Expr \rightarrow Expr\,+\,Expr\,|\,Int\,|\,String$$

我们可以改写为

$$Expr \rightarrow Int\,ExprRest\,|\,String\,ExprRest$$

$$ExprRest \rightarrow \epsilon\,|\,+\,Expr\,ExprRest$$

然后最后一个规则可以缩短改写为

$$ExprRest \rightarrow \epsilon\,|\,+\,Expr$$

来避免掉左递归的出现

### 移除间接左递归

如果文法内不存在 $\epsilon$(代表空字串)的生成 (不存在$A \rightarrow \ldots | \epsilon | \ldots$这样的规则)，而且不是循环(cyclic)的文法(对所有非终端符号A，不存在像是$A \Rightarrow \ldots \Rightarrow A$这种形式的规则)，以下这个一般化的算法可以用来去除文法的间接左递归：

将所有非终端符号以某个固定的顺序$A_1, \ldots A_n$排列

从 i = 1 到 n {

从 j = 1 到 i – 1 {

- 设$A_j$的生成规则为

$$A_j \rightarrow \delta_1 | \ldots | \delta_k$$

- 将所有规则 $A_i \rightarrow A_j \gamma$换成

$$A_i \rightarrow \delta_1\gamma | \ldots | \delta_k\gamma$$

- 移除$A_i$规则中的直接左递归

## 陷阱

上面的转换使用右递归的文法来避免掉左递归的出现；但是这样会改变规则的结合律。左递归会创造出向左的结合律；但是右递归则会创造出向右的结合律。

范例 :

一开始我们拿到以下文法：

$$Expr \rightarrow Expr\,+\,Term\,|\,Term$$

$$Term \rightarrow Term\,*\,Factor\,|\,Factor$$

$$Factor \rightarrow (Expr)\,|\,Int$$

在我们使用上面的转换方式来移除掉左递归之后，我们取得了以下文法：

$$Expr \rightarrow Term\ Expr'$$

$$Expr' \rightarrow {} + Term\ Expr'\,|\,\epsilon$$

$$Term \rightarrow Factor\ Term'$$

$$Term' \rightarrow {} * Factor\ Term'\,|\,\epsilon$$

$$Factor \rightarrow (Expr)\,|\,Int$$

我们将字串 'a + a + a'用一个LALR分析器(这种分析器可以处理左递归的文法)使用原先的文法来分析，会得到下面的分析树(parse tree)：

```
                            Expr
                         /   |   \
                       Expr   +   Term
                     /  |  \        \
                   Expr  + Term      Factor
                    |      |       |
                   Term    Factor    Int
                    |      |
                  Factor    Int
                    |
                   Int  
```

整个分析树是往左边长，代表在这里的规则，'+'这个符号是左结合(left associative)的，或者说这规则代表(a + a) + a。

但是我们改变了文法之后，那这个分析树会变成：

```
                            Expr ---
                           /        \
                         Term      Expr' --
                          |       /  |     \ 
                        Factor   +  Term   Expr' ------
                          |          |      |  \       \
                         Int       Factor   +  Term   Expr'
                                     |           |      |
                                    Int        Factor   <math>\epsilon</math>
                                                 |
                                                Int
```

我们可以看出这棵树现在是往右边成长，意思上代表了a + ( a + a)。我们将'+'的结合律改变了, 变成是右结合的规则。在处理加法的文法时这不是什么问题，但是如果我们现在处理的是减法，这就会变成是很严重的问题。

问题的关键在于有很多常用的算术规则要求左结合的规则。我们有几种解决办法： (a) 将规则重新改为左递归，(b) 使用更多的非终端符号来改写规则，以强迫文法合乎正确的结合(c) 如果使用YACC 或者Bison，他们有所谓算符宣告(operator declarations)， %left, %right and %nonassoc，这一些算符可以告诉语法分析器产生程式(parser generator)应该遵从哪一种结合。

## 外部链接

- CMU lecture on left-recursion
- Practical Considerations for LALR(1) Grammars
- X-SAIGA - eXecutable SpecificAtIons of GrAmmars