【问题标题】:Bison shift/reduce conflict for do block做块的野牛移位/减少冲突
【发布时间】:2021-11-20 17:02:14
【问题描述】:

我在解决移位/减少冲突时遇到问题。

我正在尝试编写一个while循环语法:

while expression do code() end

问题在于do关键字。

我的一个表达式是一个调用表达式,它也接受一个可选的 do 块用于回调,例如:

function_call() do print("callback") end

这反过来似乎会导致 while 循环的 do 关键字中的 shift/reduce 冲突。

如果我这样做:

while call() do stuff() end

相反,它会尝试将 do 与函数调用相匹配,并弄乱整个解析。

Ruby 有一个非常相似的语法,但它似乎正确地支持 while 循环的 do 关键字而不是表达式。在 Ruby 中,如果需要,您可以将回调放在括号中,这也是我想要解决的理想方式:

while (call() do stuff() end) do more_stuff() end

我该如何克服这个问题?我在运算符优先级定义方面搞砸了很多,但似乎没有任何效果,你将如何解决这个问题? ruby 是如何做到正确的?

这是重现问题的完整语法:

%token IDENT DO END WHILE

%%

program:
       %empty
       |
       stmts
       ;

stmts:
     stmt
     |
     stmts ';' stmt
     |
     stmts ';'
     ;

opt_stmts:
         %empty
         |
         stmts
         ;

opt_semi:
        %empty
        |
        ';'
        ;

term:
    ';'
    |
    DO opt_semi
    ;

while_loop:
          WHILE expr term opt_stmts END
          |
          WHILE term opt_stmts END
          ;

stmt:
    expr
    |
    while_loop
    ;

expr:
    IDENT
    |
    call
    |
    '(' expr ')'
    ;

do_block:
        DO '|' args '|' opt_stmts END
        |
        DO opt_stmts END
        ;

call:
    IDENT '(' args ')'
    |
    IDENT '(' args ')' do_block
    |
    IDENT do_block
    ;

args:
    %empty
    |
    expr
    |
    args ',' expr
    |
    args ','
    ;

%%

旁注:我正在使用 Jison,它是 JavaScript 的野牛克隆,因为我正在为 JavaScript 编写编译器,但这不应该成为问题,因为这是一般语法问题和上述最小的 sn-p我写的也跑过原版的 Bison。

【问题讨论】:

标签: parsing syntax bison bnf


【解决方案1】:

您已经确定了问题 - DO 的歧义可能是 WHILE..DO 的一部分,也可能是 call-DO 的一部分。这特别难,因为 DO 在这两种情况下都是可选的,但是语法是这样的,一些 DO 可能只与 WHILE 或调用相关联,具体取决于它们之后的各种事物,这意味着非模棱两可的情况不能是无论如何,无需更多前瞻即可确定。它还遇到了一些 LALR 与 LR 的问题,这也使其变得困难(也意味着您不能使用优先级来解决它。)

你可以通过重构语法来解决这个问题,就像对悬空 else 问题所做的一样——拆分 expr 规则并只允许在 WHILE 语句中不以 call-DO 结尾的表达式.这样,在 WHILE 之后(而不是在括号中)的 DO 将始终与 WHILE 相关联,而不是与调用相关联。它确实稍微改变了语言 - 带有可选 ';' 的 WHILE..DO 和中间的 call-DO(没有括号)将被拒绝作为语法错误(第一个 DO 将与 WHILE 匹配,第二个DO 不能是 call-DO,因为 DO 后面紧跟着 ';'

对于您的示例,这意味着将 expr 规则拆分为 call-DO 案例和所有其他表达式案例:

expr: non_call_do_expr | call_withdo ;
non_call_do_expr: IDENT | call_nodo | '(' expr ')' ;


call_nodo: IDENT '(' args ')' ;

call_withdo:
    IDENT '(' args ')' do_block |
    IDENT do_block ;

如果您添加任何其他 expr 规则,则需要将它们添加到 non_call_do_expr 规则中除非它们(可以)以 call-DO 结束。在这种情况下,这两种情况需要两个版本,这与解决悬空-else 情况的语句所需要的完全一样。 while 则变为:

while_loop:
          WHILE non_call_do_expr term opt_stmts END |
          WHILE term opt_stmts END ;

【讨论】:

  • 谢谢。我已经考虑过这样的方法,但我没有实现它,但我认为那是因为我的语法一团糟,当我在用其他方法来解决它时,我就放弃了。但这确实看起来是唯一的方法,所以我现在又试了一次,它有效。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-24
  • 2013-07-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多