为什么在某些情况下我不能使用令牌作为优先级标记

时间:2012-06-10 22:30:11

标签: parsing bison yacc operator-precedence

假设此代码有效:

left '*'
left '+'

expr: expr '+' expr
    | expr '*' expr
    ;

我想定义另一个优先级标记,如:

left MULTIPLY
left PLUS

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;

然而,这实际上并没有效果。

我认为这两种形式应该是等价的,但是,它们不是。

这不是实际问题。我只是想知道这种现象的原因和原理。

感谢。

3 个答案:

答案 0 :(得分:3)

你说你并没有试图解决一个具体的实际问题。从你的问题来看,我对你如何使用优先级标记感到有点困惑。

我认为你会发现你不需要经常使用优先级标记。阅读器通常更简单,更清晰,重写语法,以便明确说明优先级。为了得到乘法和除以加法和减法的优先级,你可以做这样的事情(例子改编自John Levine, lex& yacc 2 / e,1992):

%token NAME NUMBER

%%

stmt : NAME '=' expr
     | expr
     ;

expr : expr '+' term
     | expr '-' term
     | term
     ;

term : term '*' factor
     | term '/' factor
     | factor
     ;

factor : '(' expr ')'
       | '-' factor
       | NUMBER
       ;

在您的示例中,PLUSMULTIPLY不是真正的令牌;您无法与'+''*'互换使用它们。莱文称他们为伪代币。他们将您的作品链接回您使用%left%nonassoc声明定义的优先顺序列表。他给出了这个例子,说明即使' - '标记的优先级较低,你可以使用%prec给出一元减去高的优先级:

%token NAME NUMBER
%left '-' '+'
%left '*' '/'
%nonassoc UMINUS

%%

stmt : NAME '=' expr
     | expr
     ;

expr : expr '+' expr
     | expr '-' expr
     | expr '*' expr
     | expr '/' expr
     | '-' expr %prec UMINUS
     | '(' expr ')'
     | NUMBER
     ;

总结一下,我建议遵循我的第一个代码示例的模式,而不是第二个;使语法明确。

答案 1 :(得分:2)

Yacc优先级规则并不是关于表达式的优先级,尽管它们可以用于表达式。相反,它们是一种明确解决转移/减少冲突(并且只能转移/减少冲突)的方法。

了解它的工作原理需要了解shift / reduce(自下而上)解析的工作原理。基本思想是从输入中读取令牌符号并将这些令牌推送(“移位”)到堆栈上。当堆栈顶部的符号与语法中某些规则的右侧匹配时,您可以“缩小”规则,从堆栈中弹出符号并将其替换为规则左侧的单个符号。您重复此过程,转移标记并减少规则,直到您已读取整个输入并将其缩减为单个开始符号实例,此时您已成功解析整个输入。

上面的基本问题(以及解析器生成器正在解决的问题)是知道何时减少规则与何时移动令牌(如果两者都可能)。解析器生成器(yacc或bison)构建一个状态机,用于跟踪哪些符号已被移位,因此知道当前可能存在哪些“部分匹配”规则,并将移位限制为可以匹配更多此类规则的令牌。如果所讨论的语法不是LALR(1),则这不起作用,因此在这种情况下,yacc / bsion报告会改变/减少或减少/减少冲突。

该优先规则用于解决shift减少冲突的方法是为语法中的某些标记和规则分配优先级。每当要移位的令牌和要减少的规则之间存在移位/减少冲突,并且两者都具有优先权时,它将执行具有更高优先级的任何一个。如果他们具有相同的优先权,那么它会查看与优先级相关联的%left / %right / %nonassoc标记 - %left表示减少,%right表示移位,%nonassoc表示不执行任何操作,并将其视为语法错误。

唯一棘手的问题是令牌和规则如何获得优先权。令牌从他们所在的%left / %right / %nonassoc指令中获取,这也是设置顺序。规则优先于%prec指令,或来自右侧最右侧的终端。所以当你有:

%left '*'
%left '+'

expr: expr '+' expr
    | expr '*' expr
    ;

您正在使用'*'指令设置'+'%left的优先级,并且这两个规则优先于这些令牌。

当你有:

%left MULTIPLY
%left PLUS

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;

您正在设置令牌MULTIPLYPLUS的优先级,然后明确设置规则以具有这些优先级。但是,您没有为令牌'*''+'设置任何优先顺序。因此,当两个规则之一与'*''+'之间发生转换/减少冲突时,优先级无法解决它,因为令牌没有优先权。

答案 2 :(得分:0)

Shift-reduce冲突是尝试减少生产与转移令牌和迁移到嵌套状态之间的冲突。当Bison解决冲突时,它不会比较两个规则并选择其中一个 - 它要比较一个要减少的规则和要在其他规则中移动的令牌。如果您有两个要转移的规则,这可能会更清楚:

expr: expr '+' expr
    | expr '*' expr
    | expr '*' '*' expr

这一点令人困惑的原因是Bison给予“reduce”规则优先级的方式是将它与一个令牌(默认情况下规则中的最后一个终端或来自prec声明的令牌)相关联然后它使用优先级表将该标记与您尝试移动的标记进行比较。基本上,预先声明只对冲突的“减少”部分有意义,并且不计入轮班部分。

查看此内容的一种方法是使用以下语法

command: IF '(' expr ')' command               %prec NOELSE
       : IF '(' expr ')' command ELSE command

在此语法中,您需要在缩小第一个规则或移动ELSE令牌之间进行选择。您可以通过优先使用')'标记和ELSE标记,或者使用预先声明并为NOELSE而不是')'赋予优先级。如果你试图给第二个预先声明,它将被忽略,Bison将继续尝试在优先级表中寻找ELSE标记的优先级。