为什么要消除左递归

为什么要消除左递归

学习编译原理中的递归下降分析时,老师讲到了消除文法的左递归,但是关于为什么要消除左递归 似乎没有解释的很清楚(其实是自己上课走神了23333)。查阅了龙书中的相关章节之后我似乎有了一些理解。龙书中给出了自顶向下分析语法分析器对应的伪代码:

//The procedure of a top-down  parser
	void A(){
	
	str =choose_production(A);
	// 为非终结符A选择一个产生式A -->str, str是一个由终结符和非终结符构成的串
	
	for(i = 0 ;i<len(str);i++){
		if(str[i] is a non-terminal) 		//如果str[i] 是非终结符
			call the procedure of str[i];  // 调用该非终结符对应的函数
		else {  						// 否则str[i]就是一个终结符
			if(str[i] ==read())    // 读入字符和该终结符匹配
				read the next inputed character; // 读下一个字符
			else
				report error;             //发生错误
  	
			}
			
	}
	 
	  
	}

根据上面的伪代码,我们就可以从直观上理解为什么需要消除左递归。不妨假设我们有文法:
A → A a ∣ b (1) A \rightarrow Aa|b \tag{1} AAab(1)

那么参照龙书给出的伪代码我们可以知道,在调用处理非终结符A的函数时,如果选择了A->Aa这个产生式,A的产生式体的第一个字符还是A。根据伪代码我们需要继续递归调用处理A的函数。问题的关键就是在递归下降分析中,我们每次只允许向后看1个输入字符,这就导致了choose_production的能力非常有限,计算机对于怎么选择产生式就变得非常迷茫,人一迷茫就难免犯错,计算机也是一样。如果它一直选择A->Aa,在没有读入任何输入符号时,就形成了无穷的递归调用。

我们可以通过一个例子来说明自顶向下分析中,计算机对于左递归的迷茫。人类可以轻而易举地看出,文法(1)能够生成baaaaaa这种类型的句子:只要调用A -->Aa若干次再调用一次A->b即可。但是我们的计算机就不一样了,它每次只能看到一个输入符号,所以在算法刚开始运行的时候,它看到了b。那么这个时候它在choose_production()中应该选择哪一个产生式呢?

  • 如果ta选择了A ->b,好,当前字符匹配上了,但是后面那一串a咋办?报错!所以它选择了A->b就会导致它认为该句子不是被文法(1)生成的。
  • 如果ta选择了A->Aa,那么选完产生式了,就该对产生式体的各个符号继续分析了。然后你猜它看到的第一个产生式体中的符号是啥?还是A!那咋办嘛,继续递归调用A对应的过程A啊!最致命的是,每次递归调用A的时候,看到的输入字符还是b!而我们的choose_production的决策总是基于它看到的当前的输入符号的,所以如果我们的 choose_production没有抽风的话,它每次选择的产生式都是A->Aa!看到这里你应该可以发现,在没有读入任何符号的情况下,A会陷入对自身的无穷无尽的递归调用!

综合上面两种情况,我们发现左递归给我们的自顶向下的parser造成了极大的困扰,所以消除左递归也就成为了理所应当的事情了。至于如何消除左递归,就是纯粹的体力活了。对于这种体力活我有三个字和大家分享:

奥利给!!!

p.s. 本人在校CS弱鸡一枚,如果文章有错误和不当之处还请大佬不吝赐教,但是求轻喷好嘛QAQ。也欢迎各位CS的小伙伴们一起交流,共同进步!

参考文献
[1]Alfred V.Aho ,Monica S.Lam,Ravi Sethi ,etc.编译原理[M].机械工业出版社:北京,2009:139.

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值