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

Parsing 为什么在某些情况下我不能使用标记作为优先标记,parsing,bison,yacc,operator-precedence,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 ; 但这实际上并不有效 我想这两种形式应该是等价的,但是它们不是 这不是实际问题。我只想知道这种现象的原因和原理 谢谢。你说你不

假设此代码有效:

left '*'
left '+'

expr: expr '+' expr
    | expr '*' expr
    ;
我想定义另一个优先标记,如:

left MULTIPLY
left PLUS

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;
但这实际上并不有效

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

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


谢谢。

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

我想你会发现你不需要经常使用优先标记。对读者来说,重写语法通常更简单、更清晰,这样就可以明确地说明优先顺序。要赋予乘和除比加和减更高的优先级,您可以执行类似以下示例的操作,该示例改编自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
       ;
在您的示例中,加号和乘法不是真正的标记;不能将它们与“+”和“*”互换使用。莱文称之为伪代币。它们用于将您的产品链接回您使用%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
     ;

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

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

我想你会发现你不需要经常使用优先标记。对读者来说,重写语法通常更简单、更清晰,这样就可以明确地说明优先顺序。要赋予乘和除比加和减更高的优先级,您可以执行类似以下示例的操作,该示例改编自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
       ;
在您的示例中,加号和乘法不是真正的标记;不能将它们与“+”和“*”互换使用。莱文称之为伪代币。它们用于将您的产品链接回您使用%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
     ;

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

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

expr: expr '+' expr
    | expr '*' expr
    | expr '*' '*' expr
这一切令人困惑的原因是,Bison为reduce规则赋予优先级的方式是将其与令牌(默认情况下规则中的最后一个终端)或prec声明中的令牌相关联,然后它使用优先级表将该令牌与您尝试移动的令牌相比较。基本上,prec声明只对冲突的reduce部分有意义,而不计入shift部分

一种方法是使用以下语法

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

在此语法中,您需要在减少第一个规则还是移动ELSE标记之间进行选择。为此,您可以为令牌和ELSE令牌提供优先级,或者使用prec声明并为NOELSE而不是ELSE提供优先级。如果您尝试为第二个令牌提供prec声明,它将被忽略,Bison将继续尝试在优先级表中查找ELSE令牌的优先级。

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

expr: expr '+' expr
    | expr '*' expr
    | expr '*' '*' expr
这一切令人困惑的原因是,Bison为reduce规则赋予优先级的方式是将其与令牌(默认情况下规则中的最后一个终端)或prec声明中的令牌相关联,然后它使用优先级表将该令牌与您尝试移动的令牌相比较。基本上,prec声明只对冲突的reduce部分有意义,而不计入shift部分

一种方法是使用以下语法

command: IF '(' expr ')' command               %prec NOELSE
       : IF '(' expr ')' command ELSE command
在此语法中,您需要在减少第一个规则还是移动ELSE标记之间进行选择。为此,您可以为令牌和ELSE令牌提供优先级,或者使用prec声明并在中为NOELSE提供优先级
代替。如果您尝试为第二个表达式提供prec声明,它将被忽略,Bison将继续尝试在优先级表中查找ELSE标记的优先级。

Yacc优先级规则实际上与表达式的优先级无关,尽管它们可以用于此目的。相反,它们是解决shift/reduce冲突的一种方法,并且仅明确表示shift/reduce冲突

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

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

优先规则解决移位-减少冲突的方法是为语法中的某些标记和规则指定优先权。每当要移位的标记和要缩减的规则之间存在移位/缩减冲突,并且两者都具有优先级时,它将执行优先级较高的一个。如果它们具有相同的优先级,则会查看与优先级相关联的%left/%right/%nonassoc标志-%left表示减少、%right表示移位和%nonassoc表示两者都不做,并将其视为语法错误

剩下的唯一棘手的一点是令牌和规则如何获得它们的优先级。令牌从其所在的%left/%right/%nonassoc指令获取,该指令还设置了顺序。规则从%prec指令或其右侧最右侧的终端获得优先级。因此,当你有:

%left '*'
%left '+'

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

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;
您正在使用%left指令设置“*”和“+”的优先级,这两个规则从这些标记获得优先级

如果您有:

%left '*'
%left '+'

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

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

您正在设置标记乘法和加号的优先级,然后显式设置规则以具有这些优先级。但是,您没有为标记“*”和“+”设置任何优先级。因此,当两个规则中的一个与“*”或“+”之间存在移位/减少冲突时,优先级不会解决冲突,因为标记没有优先级。

Yacc优先级规则实际上与表达式的优先级无关,尽管它们可以用于此。相反,它们是解决shift/reduce冲突的一种方法,并且仅明确表示shift/reduce冲突

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

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

优先规则解决移位-减少冲突的方法是为语法中的某些标记和规则指定优先权。每当要移位的标记和要缩减的规则之间存在移位/缩减冲突,并且两者都具有优先级时,它将执行优先级较高的一个。如果它们具有相同的优先级,则会查看与优先级相关联的%left/%right/%nonassoc标志-%left表示减少、%right表示移位和%nonassoc表示两者都不做,并将其视为语法错误

o 剩下的一点是令牌和规则如何获得它们的优先级。令牌从其所在的%left/%right/%nonassoc指令获取,该指令还设置了顺序。规则从%prec指令或其右侧最右侧的终端获得优先级。因此,当你有:

%left '*'
%left '+'

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

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;
您正在使用%left指令设置“*”和“+”的优先级,这两个规则从这些标记获得优先级

如果您有:

%left '*'
%left '+'

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

expr: expr '+' expr %prec PLUS
    | expr '*' expr %prec MULTIPLY
    ;
您正在设置标记乘法和加号的优先级,然后显式设置规则以具有这些优先级。但是,您没有为标记“*”和“+”设置任何优先级。因此,当两个规则中的一个与“*”或“+”之间存在移位/减少冲突时,优先级不会解决它,因为标记没有优先级