yacc语法树系列(五)

650 阅读5分钟

这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

本文为译文,原文链接:dinosaur.compilertools.net/yacc/

接上文,继续下一节。

6:Precedence(优先级)

有一个普遍的场景,上面给出的用来解决冲突的规则是不够充分的;就是在解析算术表达式的时候。大多数算术表达式常用的构造可以自然地被操作者的优先级的概念描述出来,包括左结合或者右结合的信息。结果是模棱两可的语法加上恰当的消除二义性规则可以被用来创建比通过非歧义的语法更快更简单的解析器。基本概念是把所有的二元和一元运算符预期的语法规则写成这种格式:

        expr  :  expr  OP  expr
and
        expr  :  UNARY  expr

这里创建了一个非常歧义的语法,还有很多解析冲突。至于非歧义的规则,用户对所有的运算符指定优先级,或者绑定强度,还有二运运算符的结合。这种允许yacc按照这些规则来解决解析冲突的信息是充分的,并且构造了一个实现了预期的优先级和结合性的解析器。

优先级和结合性是附属在声明(declarations)部分的tokens上的。这是由一系列以yacc关键字:%left,%right,或者%nonassoc等开头的行完成的,接着是tokens列表。同一行的所有tokens被认定为具有相同的优先级水平和结合性;这些行被按照递增的优先级或者绑定强度的顺序来排列。因此,

        %left  '+'  '-'
        %left  '*'  '/'

这个描述了四个算术运算符的优先级和结合性。加减是左结合的,并且具有比乘除更低的优先级,也是左结合的。关键词%right被用来描述右结合的操作符,然后关键字%nonassoc是用来描述像Fortran中的 .LT. 这种操作符,可能跟它们自己没有联系;因此

        A  .LT.  B  .LT.  C

这种在Fortran是合法的,这种运算符也可以被yacc中的关键字%nonassoc来描述。作为一个这些声明的行为的例子,下面这个描述

        %right  '='
        %left  '+'  '-'
        %left  '*'  '/'

        %%

        expr    :       expr  '='  expr
                |       expr  '+'  expr
                |       expr  '-'  expr
                |       expr  '*'  expr
                |       expr  '/'  expr
                |       NAME
                ;

可能会被用来把下面的输入构造成下一个:

        a  =  b  =  c*d  -  e  -  f*g
        a = ( b = ( ((c*d)-e) - (f*g) ) )

当这种机制被使用的时候,一元运算符通常必须被指定一个优先级。有时候一个一元运算符和一个二元运算符具有同样的符号表示,但是有不同的优先级。一个例子是一元和二元的减法"-";一元减法可能是被设置了跟乘法相同或者更高的强度,同时二元减法有一个比乘法更低的强度。关键词%prec改变了一个特别的语法规则的相关的优先级。%prec立刻出现在语法规则的body之后,在动作或者结束的分号之前,接着是一个token名称或者字面量。它导致了这条语法规则的优先级成为接下来的token名称或者字面量的优先级。比如说,让一元减法具有跟乘法相同的优先级,规则可能类似:

        %left  '+'  '-'
        %left  '*'  '/'

        %%

        expr    :       expr  '+'  expr
                |       expr  '-'  expr
                |       expr  '*'  expr
                |       expr  '/'  expr
                |       '-'  expr      %prec  '*'
                |       NAME
                ;

一个被%left,%right, 和 %nonassoc声明的token不需要,但可能也被%token声明。

优先级和结合性被yacc使用来解决解析冲突;它们使得消除二义性规则发生。形式上,规则按照下面这些来工作:

  1. 为那些具有它们的tokens和字面量记录优先级和结合性。

  2. 一个优先级和结合性跟每个语法规则相关联;就是一条规则中的上一个token或者字面量的优先级和结合性。如果%prec结构被使用了,就会重写这种默认机制。有些语法贵哦可能没有优先级和结合性跟它们关联。

  3. 当发现reduce/reduce冲突,或者有shift/reduce冲突,并且输入标识和语法规则都没有优先级和结合性的时候,然后章节开头的两个消除二义性的规则就会被使用,并且冲突会被报告出来。

  4. 如果有shift/reduce冲突,并且语法规则和输入字符都有优先级和结合性相关联,则冲突会被解决成有利于那个关联了更高优先级的动作(shift 或者reduce)。如果优先级相同,则使用结合性来;左结合意味着reduce,右结合意味着shift,非结合性意味着报错。

被优先级解决的冲突不会被算进yacc对 shift/reduce 和 reduce/reduce冲突报告中的数量。这意味着优先级规范中的错误可能会隐藏关输入语法中的错误;保留优先级是个好主意,把它们当做食谱来使用,直至已经获得一些经验。y.output文件对于决定解析器是否真的在做预期的事情是非常有用的。

OK,今天就先翻译到这里,预告一下,下一篇会开始《错误捕获》章节,敬请期待。