if-else的reduce-shift冲突

来源:互联网 发布:windows 10怎么用 编辑:程序博客网 时间:2024/04/30 23:29

     众所周知,语法分析的文法有两种,LL文法和LR文法,LL文法中最通用的是

LL(1)文法,LR中最通用的是LALR文法,分别适用于自顶向下和自底向上的

语法分析。LL(1)文法的话,一目了然,就是先取一个token,看能用哪个产生式

来产生当前的非终结符,只要你看得出,语法分析器绝对看得出。你要看不出,

语法分析器也绝对看不出。而LALR分析的话,就很复杂了。自底向上的规约,

加上大量的产生式(这个是全局的,不像LL就在一个非终结符里搞)。最关键的

是,LALR不是LR(0),也不是LR(1),它不会对每个产生式都看看允许的后

缀有哪些,也不会完全不看。至今我仍搞不清楚,LALR是对每个非终结符有一个

后缀列表呢,还是更细些?(所以我的LALR分析器总会给我带来惊喜)


   不清楚不要紧,你的语法分析器清楚,它弄不懂了就会报错。所以真正关键的

问题在于你会改错。本文的作者就遇到这样的情况,那是一个经典的if-else的

reduce-shift冲突(其经典之处就在于只要你实现一种语言,几乎必有if-else,于

是必有此冲突)。我们先不去管它,要会修改冲突,就要先了解冲突的起源。我

们就从此开始。

 

       自底向上语法分析器的工作原理是这样的:它从外界(词法分析器)取得一个

token,把它压入符号栈,然后看匹配哪些产生式。一些产生式会说,好,这个

token就在我的产生式路径上,就保持这样shift好了。另一些产生式更绝,这个

token不但在它的产生式路径上,更恰好是它的最后一个匹配项,它要求把符号

栈里匹配它产生式右边的符号全部出栈,并把其左边的非终结符入栈,然后循环

匹配下去。如果没有一个产生式说这个token在它的产生式路径上,那么不好意思,

被分析的程序出错了。我们用伪代码的方式来表示这一过程。

      input(Symbol token)

{

      push_stack(token);

      if(no_production_agree_this())

           error("not match one production");

      production=production_reduced();

      if(production!=0){

          pop_stack(production.right);

          input(production.left);

      }

}

 

    事实上,这只是LR(0)语法分析器的工作方式。如果同时出现两个production

都能reduce,或者一个要reduce一个要shift的情况,那就是出现冲突了,LR(0)

文法所不能胜任也。LALR语法分析器比LR(0)聪明,何以见得?LALR分析器在

有产生式要规约的时候,不会立即进行,而是再从外面取一个token。跟要规约的

产生式的左端非终结符的后缀比较。如果token属于其后缀列表,表示可以规约到

这个非终结符。如果token不属于其后缀列表,表示这个非终结符后面不能跟这个

token,你规约到它,不是找错吗?于是被否决了。很多的LR(0)冲突就是这样

被消除的。如果是reduce-reduce冲突,就看下一个token在哪个非终结符的后缀

列表里,就用哪个非终结符所在的产生式进行规约。如果是reduce-shift冲突,就

看token是否在要规约的非终结符的后缀列表里,如果不在,就shift,如果在,就

仍是一个冲突。

   一般来说,shift-reduce的冲突并不多,更常见的是reduce-reduce的冲突。因为

token一般都是先被reduce到非终结符,然后才参与shift的。但if-else冲突是一个

特例,我们就来具体看一下它。

   

    statement          ::=

                              IF LPAREN expression RPAREN statement ELSE statement

                   |          IF LPAREN expression RPAREN statement

                   |          other

                   ;

 

    上面这个例子就是if-else冲突。当语法分析器匹配到IF LPAREN expression

RPAREN statement后,如果下一个token是ELSE,它就遇到一个reduce-shift

冲突。第一个产生式想它shift下去,而第二个式子则希望能reduce,正好ELSE

被证明在statement的后缀中(第一个式子中的RPAREN statement ELSE).

即使实际用不到这种情况(不用else),语法分析器也会强制考虑这一

问题,毕竟它要生成表的。语法分析和词法分析的不同就在于,词法分析总会试图

寻找最长的匹配,而语法分析不会,所以你不要指望语法分析器会最长匹配到

ELSE后面去。如果你够走运,你的语法分析器会返回一个警告,并默认采用shift

的方式。但大多数语法分析器不会纵容你的。

    我们看它的症结所在。statement太贪婪了,又想有没有ELSE的情况,又想有

ELSE的情况。而且又不清楚这个ELSE是现在匹配,还是留给上级。我们当然想

就近匹配,于是作出如下规定:一个if语句不能shift进ELSE,除非它的内层statement已经有了ELSE。很容易把它改成如下式子:

 

     statement       ::=

                            IF LPAREN  expression RPAREN statement1 ELSE statement

                    |       IF LPAREN  expression RPAREN statement2

                    |       other

                    ;

   其中statement1代表内层已经有ELSE的情况,而statement2代表内层没有ELSE

的情况。

 

   statement1        ::=

                             IF LPAREN  expression RPAREN statement1 ELSE statement

                   |         other

                   ;

   statement2         ::=

                             IF LPAREN  expression RPAREN  statement2

                   |         other

                   ;

 

    我们不能说if语句中不允许包含除if之外的语句,所以other在statement1和

statement2中都有出现。但并不是说外层没有ELSE,内层就不允许有ELSE,

所以statement2还要继续修改。

 

    statement2      ::=

                            IF LPAREN  expression RPAREN  statement1 ELSE statement

                   |        IF LPAREN  expression RPAREN  statement2

                   |         other

                   ;

 

    这样竟发现statement2和statement完全相同,想想也是,statement代表了

一个独立的语句,又怎会允许ELSE跟在其后。而if中内嵌的后面跟ELSE的语句

已经被独立出来,变成statement1了。这样整个文法就是:

     statement       ::=

                            IF LPAREN  expression RPAREN statement1 ELSE statement

                    |       IF LPAREN  expression RPAREN statement

                    |       other

                    ;

 

    statement1        ::=

                             IF LPAREN  expression RPAREN statement1 ELSE statement

                   |         other

                   ;

    后面可跟else的情况,就去找statement1好了。虽然形式上独立了,但是我们还

是很担心规约的情况,万一该规约到statement的规约到statement1去了,岂不是

变成了reduce-reduce冲突。这个不必担心,为了严格区分statement和statement1

的规约,我们保证:statement1的后缀只能是ELSE,而statement的后缀中绝对

没有ELSE。LALR分析器只需看一下,就明白该规约到谁了。这下高枕无忧了。

  

   别忙,事实是残酷的,LALR分析器说不行,statement1:==IF LPAREN  expression RPAREN statement1 ELSE statement 永远规约不到。为什么?

很简单,statement后缀中不可能有ELSE,statement1后缀只有ELSE。如果上面

的产生式中最后的statement规约了,那后缀不是ELSE,则statement1不会规约。

反之则statement不可能规约。所以要改一下。

   statement       ::=

                            IF LPAREN  expression RPAREN statement1 ELSE statement

                    |       IF LPAREN  expression RPAREN statement

                    |       other

                    ;

 

    statement1        ::=

                           IF LPAREN  expression RPAREN statement1 ELSE statement1

                   |         other

                   ;

 

  不要小看这个1(statement1),太重要了。是不是这样就行了,当然不。这取决

于other的情况,statement已经跟ELSE彻底决裂了,如果other中有以statement

结尾的,也要把它们分拆,并写到statement和statement1中。比如

     other            ::=

                        WHILE  LPAREN expression RPAREN statement

                 ;

 

  整个文法就要改成:

   

      statement       ::=

                            IF LPAREN  expression RPAREN statement1 ELSE statement

                    |       IF LPAREN  expression RPAREN statement

                    |       WHILE  LPAREN expression RPAREN statement

                    ;

 

    statement1        ::=

                           IF LPAREN  expression RPAREN statement1 ELSE statement1

                   |       WHILE  LPAREN expression RPAREN statement1

                   ;

 

   这样的statement和statement1规约清晰,绝对分得清。再有错误,就要考虑换

一个LALR的分析器了,哈哈!

 

 


 

 



 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


原创粉丝点击