Parsing 组合具有不同优先级的一元运算符

Parsing 组合具有不同优先级的一元运算符,parsing,syntax-error,bison,operator-precedence,Parsing,Syntax Error,Bison,Operator Precedence,我在Bison创建操作员时遇到了一些问题: 好的,让我根据您的草图提出一个可能的错误语法: low_postfix: mid_infix | low_postfix "<-" mid_infix: high_postfix | mid_infix '+' high_postfix high_postfix: term | high_postfix "++" term: ID '(' expr ')'

我在Bison创建操作员时遇到了一些问题:
好的,让我根据您的草图提出一个可能的错误语法:

low_postfix:
    mid_infix
|   low_postfix "<-"
mid_infix:
    high_postfix
|   mid_infix '+' high_postfix
high_postfix:
    term
|   high_postfix "++"
term:
    ID
    '(' expr ')'
那么,我们如何解决这个问题呢

在某种程度上,能够写出一个明确的语法来表达我们的意图是很好的。当然,写一个明确的语法是可能的,它将把意图传达给野牛。但它是否能向人类读者传达任何信息,这至少是一个悬而未决的问题,因为为了跟踪什么是可接受的、什么不是可接受的分组,需要大量杂乱的多条规则,这将相当令人畏惧

另一方面,bison/yacc优先级声明非常简单。我们只是按顺序列出操作符,解析器生成器相应地解决所有歧义。[见下文注1]

这里有一个与上面类似的语法,带有优先级声明。(我保留了操作,以防您想使用它,尽管它绝不是一个可复制的示例;它所依赖的基础设施比语法本身大得多,除了我之外,对任何人都没有什么用处。因此,您必须定义这三个函数并填写一些bison类型声明。或者干脆删除AST fu(请选择并使用您自己的。)

但是,在一些表达式中,运算符优先级虽然“正确”,但可能不容易向新手用户解释。例如,尽管箭头操作符看起来有点像括号,但它们并不是这样分组的。此外,在我看来,两个运算符中哪一个具有更高优先级的决定是完全武断的(事实上,我可能会做得与您预期的不同)。考虑:

? <-2*f(a)->+3
=> [<- [+ [-> [* 2 [CALL f a]]] 3]]
? <-2+f(a)->*3
=> [<- [* [-> [+ 2 [CALL f a]]] 3]]
? 2+<-f(a)->*3
=> [+ 2 [<- [* [-> [CALL f a]] 3]]]
如果这是你的意图,好吧。这是你的语言

请注意,有些运算符优先级问题不太容易通过按优先级顺序列出运算符来解决。有时,二元运算符在左侧和右侧具有不同的绑定能力是很方便的

一个经典的(但可能有争议的)案例是赋值操作符,如果它是一个操作符的话。赋值必须关联到右边(因为解析
a=b=0
as
(a=b)=0
将是荒谬的),通常的期望是它贪婪地接受尽可能多的右边赋值。如果赋值具有一致的优先级,那么它也会接受尽可能多的左对齐,这似乎有点奇怪,至少对我来说是这样。如果
a=2+b=7
是有意义的,我的直觉认为它的意义应该是
a=(2+(b+7))
[注2]。这将需要差分优先级,这有点复杂,但并非前所未闻。C通过将赋值运算符的左侧限制为(语法)左值来解决这个问题,左值不能是二进制运算符表达式。但是在C++中,它确实意味着<代码> A=((2 +B)=7)< />代码,如果代码> 2 +b>代码>已经被一个返回引用的函数重载,则语义上是有效的。
笔记
  • 优先级声明并没有真正为解析器生成器添加任何功能。它可以为其生成解析器的语言是完全相同的语言;它产生同样类型的解析机(下推自动机);至少从理论上讲,我们可以利用下推自动机,对其中的语法进行逆向工程。(在实践中,这一过程产生的语法通常是可怕的,但它们是存在的。)

    优先级声明所做的只是根据一些用户提供的规则解决解析冲突(通常是在不明确的语法中)。所以值得一提的是,使用优先级声明比编写明确的语法要简单得多

    一个简单的挥手回答是,优先规则只适用于发生冲突的情况。如果解析器处于只能执行一个操作的状态,那么不管优先级规则会说什么,仍然会执行该操作。在简单表达式语法中,中缀运算符后跟前缀运算符一点也不含糊:前缀运算符必须移位,因为对于以中缀运算符结尾的部分序列没有reduce操作

    但是,当我们编写语法时,我们必须明确地指定语法中每个点上可能的构造,我们通常通过定义一组非终结符来实现,每个非终结符对应于某个解析状态。表达式的明确语法已经将
    表达式
    非终结符拆分为一系列级联的非终结符,每个运算符优先级值对应一个。但是一元运算符在两侧的绑定能力并不相同(因为,如上所述,一元运算符的一侧不能接受操作数)。这意味着一个二元运算符可以接受一元运算符作为其一个操作数,而不能接受同一个一元运算符作为其另一个操作数。这反过来意味着我们需要再次拆分所有非终端,对应于非终端是出现在二进制运算符的左侧还是右侧

    这需要做很多工作,而且很容易出错。如果幸运的话,错误将导致解析冲突;但同样地,它也可能导致语法无法识别一个你永远不会想到尝试的特定结构,但一些愤怒的语言使用者认为这是绝对必要的。(如
    41+非False

  • 很可能我的直觉在很小的时候就已经被学习APL永久地标记了。在APL中,所有运算符都关联到右侧,基本上没有任何优先级差异

  • 请不要把“我不能做X”放在问题里。把“我尝试了以下,但它没有满足我的要求。”然后把精确的代码,一个或多个具体的结果
    $ python3 -c 'print(41 + not False)'
      File "<string>", line 1
        print(41 + not False)
                     ^
    SyntaxError: invalid syntax
    
    %left ','
    %precedence "<-"                                                        
    %precedence "->" 
    %left '+'
    %left '*'                                                               
    %precedence NEG
    %right "++" '('
    %%
    expr: expr ',' expr                { $$ = make_binop(OP_LIST, $1, $3); }
        | "<-" expr                    { $$ = make_unop(OP_LARR, $2); }
        | expr "->"                    { $$ = make_unop(OP_RARR, $1); }
        | expr '+' expr                { $$ = make_binop(OP_ADD, $1, $3); }
        | expr '*' expr                { $$ = make_binop(OP_MUL, $1, $3); }
        | '-' expr          %prec NEG  { $$ = make_unop(OP_NEG, $2); }
        | expr '(' expr ')' %prec '('  { $$ = make_binop(OP_CALL, $1, $3); }
        | "++" expr                    { $$ = make_unop(OP_PREINC, $2); }
        | expr "++"                    { $$ = make_unop(OP_POSTINC, $1); }
        | VALUE                        { $$ = make_ident($1); }
        | '(' expr ')'                 { $$ = $2; }
    
    ? <-2*f(a)->+3
    => [<- [+ [-> [* 2 [CALL f a]]] 3]]
    ? <-2+f(a)->*3
    => [<- [* [-> [+ 2 [CALL f a]]] 3]]
    ? 2+<-f(a)->*3
    => [+ 2 [<- [* [-> [CALL f a]] 3]]]
    
    ? 2+f(a)*3
    => [+ 2 [* [CALL f a] 3]]
    ? 2+f(a)->*3
    => [* [-> [+ 2 [CALL f a]]] 3]