【问题标题】:How to solve bison shift/reduce when implementing try-catch-finally grammar?实现try-catch-finally语法时如何解决bison shift/reduce?
【发布时间】:2020-08-26 06:16:27
【问题描述】:

我正在尝试使用 Bison 在我的玩具语言中实现 try-catch-finally 表达式。

还有一点是,受Scala grammar 的启发,try-catch-finally 中的 item 是一个表达式,而不是块语句。

这是grammar.y

%code top {
#include <cstdio>
}

%union {
    int n;
    Ast *ast;
}

%code requires {
class Ast;
int yylex(void);
void yyerror(const char *msg);
}

%token<n> NUM
%token<n> PLUS '+'
%token<n> MINUS '-'
%token<n> TIMES '*'
%token<n> DIVIDE '/'
%token<n> SEMICOLON ';'
%token<n> NEWLINE '\n'
%token<n> IF "if"
%token<n> ELSE "else"
%token<n> TRY "try"
%token<n> CATCH "catch"
%token<n> FINALLY "finally"
%token<n> LPAREN '('
%token<n> RPAREN ')'

%type<ast> prog expr primaryExpr

/* grammar precedence */
%nonassoc "try_catch" /* lower than finally */
%nonassoc "try_catch_finally"

/* operator precedence is higher than grammar precedence (try-catch-finally) */
%left PLUS MINUS
%left TIMES DIVIDE


%start prog

%%

prog : expr
     ;

expr : "try" expr "catch" expr %prec "try_catch" { $$ = nullptr; }
     | "try" expr "catch" expr "finally" expr %prec "try_catch_finally" { $$ = nullptr; }
     | primaryExpr
     ;

primaryExpr : NUM { $$ = nullptr; }
            | primaryExpr '+' NUM { $$ = nullptr; }
            | primaryExpr '-' NUM { $$ = nullptr; }
            | primaryExpr '*' NUM { $$ = nullptr; }
            | primaryExpr '/' NUM { $$ = nullptr; }
            ;

%%

void yyerror(const char *msg) {
    fprintf(stderr, "%s\n", msg);
}

使用bison --debug --verbose -Wcounterexamples -o grammar.tab.cpp --defines=grammar.tab.h grammar.y 生成文件,我们有一个grammar.output 文件存在移位/减少冲突:

Terminals unused in grammar

    PLUS
    MINUS
    TIMES
    DIVIDE
    SEMICOLON
    ';'
    NEWLINE
    '\n'
    "if"
    "else"
    LPAREN
    '('
    RPAREN
    ')'


State 17 conflicts: 1 shift/reduce


Grammar

    0 $accept: prog $end

    1 prog: expr

    2 expr: "try" expr "catch" expr
    3     | "try" expr "catch" expr "finally" expr
    4     | primaryExpr

    5 primaryExpr: NUM
    6            | primaryExpr '+' NUM
    7            | primaryExpr '-' NUM
    8            | primaryExpr '*' NUM
    9            | primaryExpr '/' NUM


Terminals, with rules where they appear

    $end (0) 0
    '\n' <n> (10)
    '(' <n> (40)
    ')' <n> (41)
    '*' <n> (42) 8
    '+' <n> (43) 6
    '-' <n> (45) 7
    '/' <n> (47) 9
    ';' <n> (59)
    error (256)
    NUM <n> (258) 5 6 7 8 9
    PLUS <n> (259)
    MINUS <n> (260)
    TIMES <n> (261)
    DIVIDE <n> (262)
    SEMICOLON <n> (263)
    NEWLINE <n> (264)
    "if" <n> (265)
    "else" <n> (266)
    "try" <n> (267) 2 3
    "catch" <n> (268) 2 3
    "finally" <n> (269) 3
    LPAREN <n> (270)
    RPAREN <n> (271)
    "try_catch" (272)
    "try_catch_finally" (273)


Nonterminals, with rules where they appear

    $accept (27)
        on left: 0
    prog <ast> (28)
        on left: 1
        on right: 0
    expr <ast> (29)
        on left: 2 3 4
        on right: 1 2 3
    primaryExpr <ast> (30)
        on left: 5 6 7 8 9
        on right: 4 6 7 8 9


State 0

    0 $accept: • prog $end

    NUM    shift, and go to state 1
    "try"  shift, and go to state 2

    prog         go to state 3
    expr         go to state 4
    primaryExpr  go to state 5


State 1

    5 primaryExpr: NUM •

    $default  reduce using rule 5 (primaryExpr)


State 2

    2 expr: "try" • expr "catch" expr
    3     | "try" • expr "catch" expr "finally" expr

    NUM    shift, and go to state 1
    "try"  shift, and go to state 2

    expr         go to state 6
    primaryExpr  go to state 5


State 3

    0 $accept: prog • $end

    $end  shift, and go to state 7


State 4

    1 prog: expr •

    $default  reduce using rule 1 (prog)


State 5

    4 expr: primaryExpr •
    6 primaryExpr: primaryExpr • '+' NUM
    7            | primaryExpr • '-' NUM
    8            | primaryExpr • '*' NUM
    9            | primaryExpr • '/' NUM

    '+'  shift, and go to state 8
    '-'  shift, and go to state 9
    '*'  shift, and go to state 10
    '/'  shift, and go to state 11

    $default  reduce using rule 4 (expr)


State 6

    2 expr: "try" expr • "catch" expr
    3     | "try" expr • "catch" expr "finally" expr

    "catch"  shift, and go to state 12


State 7

    0 $accept: prog $end •

    $default  accept


State 8

    6 primaryExpr: primaryExpr '+' • NUM

    NUM  shift, and go to state 13


State 9

    7 primaryExpr: primaryExpr '-' • NUM

    NUM  shift, and go to state 14


State 10

    8 primaryExpr: primaryExpr '*' • NUM

    NUM  shift, and go to state 15


State 11

    9 primaryExpr: primaryExpr '/' • NUM

    NUM  shift, and go to state 16


State 12

    2 expr: "try" expr "catch" • expr
    3     | "try" expr "catch" • expr "finally" expr

    NUM    shift, and go to state 1
    "try"  shift, and go to state 2

    expr         go to state 17
    primaryExpr  go to state 5


State 13

    6 primaryExpr: primaryExpr '+' NUM •

    $default  reduce using rule 6 (primaryExpr)


State 14

    7 primaryExpr: primaryExpr '-' NUM •

    $default  reduce using rule 7 (primaryExpr)


State 15

    8 primaryExpr: primaryExpr '*' NUM •

    $default  reduce using rule 8 (primaryExpr)


State 16

    9 primaryExpr: primaryExpr '/' NUM •

    $default  reduce using rule 9 (primaryExpr)


State 17

    2 expr: "try" expr "catch" expr •
    3     | "try" expr "catch" expr • "finally" expr

    "finally"  shift, and go to state 18

    "finally"  [reduce using rule 2 (expr)]
    $default   reduce using rule 2 (expr)

    shift/reduce conflict on token "finally":
        2 expr: "try" expr "catch" expr •
        3 expr: "try" expr "catch" expr • "finally" expr
      Example: "try" expr "catch" "try" expr "catch" expr • "finally" expr
      Shift derivation
        expr
        ↳ "try" expr "catch" expr
                             ↳ "try" expr "catch" expr • "finally" expr
      Reduce derivation
        expr
        ↳ "try" expr "catch" expr                        "finally" expr
                             ↳ "try" expr "catch" expr •



State 18

    3 expr: "try" expr "catch" expr "finally" • expr

    NUM    shift, and go to state 1
    "try"  shift, and go to state 2

    expr         go to state 19
    primaryExpr  go to state 5


State 19

    3 expr: "try" expr "catch" expr "finally" expr •

    $default  reduce using rule 3 (expr)

让我们关注冲突部分:

State 17

    2 expr: "try" expr "catch" expr •
    3     | "try" expr "catch" expr • "finally" expr

    "finally"  shift, and go to state 18

    "finally"  [reduce using rule 2 (expr)]
    $default   reduce using rule 2 (expr)

    shift/reduce conflict on token "finally":
        2 expr: "try" expr "catch" expr •
        3 expr: "try" expr "catch" expr • "finally" expr
      Example: "try" expr "catch" "try" expr "catch" expr • "finally" expr
      Shift derivation
        expr
        ↳ "try" expr "catch" expr
                             ↳ "try" expr "catch" expr • "finally" expr
      Reduce derivation
        expr
        ↳ "try" expr "catch" expr                        "finally" expr
                             ↳ "try" expr "catch" expr •

对于"try" expr "catch" "try" expr "catch" expr "finally" expr,在默认reduce 中,"finally" 绑定到第一个"try" 而不是第二个"try"。我认为这与 Java/Scala 行为不同。

我尝试使用%prec调整优先级来解决它,但失败了。

我应该如何解决这个问题?

【问题讨论】:

  • 悬空 finally 问题与悬空 else 问题完全相同唯一的 区别在于标记具有不同的拼写。解决方法也是一样的。
  • 另外:yacc/bison 的默认冲突解决算法是更喜欢移位。这正是您想要解决悬空 else(或 finally)的问题,而这正是解析器在这里所​​做的。
  • @rici,我已经使用%prec 技能更新了答案来尝试解决它。但移位/减少仍然存在。请再次检查问题。
  • @rici,嗨,rici,我对理解野牛给我的实例感到困惑。在示例"try" expr "catch" "try" expr "catch" expr "finally" expr 中,它会将finally 与第二个try 绑定吗?

标签: c scala parsing bison


【解决方案1】:

正如评论中所指出的,try – catch – finally 语句中的可选finally 子句所产生的移位减少冲突与if – then – else 语句中的可选else 子句完全相同,即所谓“悬而未决”。

由于“悬空 finally”与“悬空 else”是同一个问题,我们可以预期解决方案是相同的。在解决方案中,最简单的是使用优先声明,其中最简单的是

%right "if" "else" "catch" "finally"

将这些令牌(以及最后一个终端是这些令牌之一的产生式)声明为%right 意味着当涉及这些令牌之一的冲突发生时,应该选择 shift 操作.由于这是 bison 的默认冲突解决方案(请参阅注 2),该优先级声明的唯一效果是抑制有关冲突的警告消息。

编辑过的问题中过度设计的解决方案也可以工作,但我会提醒不要不必要地使用%nonassoc。 [注1] 但是,添加评论是不够的:

%nonassoc "try_catch" /* lower than finally */

您实际上还需要添加声明

%right finally

上面显示的优先解决方案的优点是它是独立的。它不仅不依赖于其他优先级声明,也不依赖于%prec 声明,这也很容易被意外省略。

虽然它与如何解决问题的问题并不特别相关,但值得注意的是,您误解了 bison 的报告输出。 Bison 将状态 17 中的状态转换报告为:

    "finally"  shift, and go to state 18

    "finally"  [reduce using rule 2 (expr)]
    $default   reduce using rule 2 (expr)

这应该读作如下:

  1. "finally" 是前瞻时,shift前瞻令牌并转到状态 18。

  2. 前瞻令牌"finally" 也存在冲突操作:reduce 使用规则 2 到 expr。冲突解决算法消除了此操作 [注意2]。 (Bison 将操作放在括号中([reduce using rule 2 (expr)] 表示该操作已通过解决冲突消除。)

  3. 对于所有其他前瞻令牌 ($default),使用规则 2减少expr

请注意,Bison 不会报告优先声明消除的解析操作。那些被默默地丢弃了。


注意事项

  1. 如果要声明优先关系而不指定关联性,请使用%precedence。与%nonassoc 不同,它不会默默隐藏语法错误。

  2. 默认的冲突解决算法是:

    • 如果有移位操作,请使用它。 (可能的转变永远不会超过一种。)
    • 如果没有shift动作,使用规则号最小的reduce动作;即语法文件中最先出现的那个。

【讨论】:

  • 我在%nonassoc "try_catch" %nonassoc "try_catch_finally" 之后添加%right "catch" "finally",移位/减少警告消失了!非常感谢你,rici
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-31
  • 1970-01-01
  • 2013-05-20
  • 1970-01-01
  • 2013-12-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多