Gnu Bison移位/减少描述分层表达的基于缩进的语法中的冲突

时间:2019-06-06 00:00:21

标签: parsing gnu bison flex-lexer

我已经很长时间没有使用Bison了,所以这里有可能我遗漏了一些简单的东西,但是,我无法弄清楚为什么以下语法会产生移位/减少冲突。我认为以下语法不是模棱两可的。目的是解析像这样的表达式:

a b
  c d
    e f
  g h

为(在伪AST中):

App 
    (App a b) 
    (Seq 
        [ App 
              (App c d) 
              (Seq [App e f])
        , (App g h)
        ]
    )

语法:

%token <Token> VAR
%token <Token> EOL

%token <Token> INDENT_INC
%token <Token> INDENT_DEC

%token <AST> CONS
%token <AST> WILDCARD
%type  <AST> expr
%type  <AST> subExpr
%type  <AST> block
%type  <AST> tok

%start program

%%
program:
  expr { result = $1; }

expr:
  subExpr  {$$=$1;}
| subExpr EOL INDENT_INC block { $$ = AST.app($1,$3); } 

subExpr:
  tok          {$$=$1;}
| subExpr tok  {$$ = AST.app($1,$2); } 

block:
  expr  {$$=$1;}
| block EOL expr {$$=AST.seq($1,$3);} // causes error

tok:
  VAR { $$ = AST.fromToken($1); }
%%

错误仅是2 shift/reduce conflicts。在调试解析器时,我们可以观察到:

Grammar

    0 $accept: program $end
    1 program: expr
    2 expr: subExpr
    3     | subExpr EOL INDENT_INC block
    4 subExpr: tok
    5        | subExpr tok
    6 block: expr
    7      | block EOL expr
    8 tok: VAR

[...]

State 4

    2 expr: subExpr .
    3     | subExpr . EOL INDENT_INC block
    5 subExpr: subExpr . tok

    VAR  shift, and go to state 1
    EOL  shift, and go to state 7

    EOL       [reduce using rule 2 (expr)]
    $default  reduce using rule 2 (expr)

    tok  go to state 8

[...]

State 11

    3 expr: subExpr EOL INDENT_INC block .
    7 block: block . EOL expr

    EOL  shift, and go to state 12

    EOL       [reduce using rule 3 (expr)]
    $default  reduce using rule 3 (expr)

说实话,我不知道模棱两可的地方。对于在这种语法中消除冲突方面的帮助,我将深表感谢。

1 个答案:

答案 0 :(得分:2)

您的语法不使用INDENT_DEC;否则,您将无法知道缩进的block在哪里结束。

实际上,这就是移位/减少冲突所告诉您的。由于语法看不到INDENT_DEC,因此无法区分在同一块中分隔两个EOL的{​​{1}}和终止{{1}的expr }。因此,EOL是不明确的(假设已经看到至少一个block)。

这是模棱两可的简单证明。要解析的表达式是:

EOL

这是最左边的两个派生,它们在嵌套INDENT_INC的位置上有所不同(为简单起见,我将a EOL INDENT_INC b EOL INDENT_INC c EOL d 的路径简化为:

d

所以语法确实是模棱两可的。但是转移/减少冲突并不直接指向歧义。他们指出了一个问题,即决定是否减少subexpr ⇒ var ⇒ TOK之前的结构而不看到# Here, d belongs to the outer subexpr (effectively, a single indent) expr ⇒ subexpr EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC block EOL expr ⇒ TOK (a) EOL INDENT_INC expr EOL expr ⇒ TOK (a) EOL INDENT_INC subexpr EOL INDENT_INC block EOL expr ⇒ TOK (a) EOL INDENT_INC subexpr EOL INDENT_INC expr EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC subexpr EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL subexpr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL TOK (d) # Here, d belongs to the inner subexpr (effectively two indents) expr ⇒ subexpr EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC expr ⇒ TOK (a) EOL INDENT_INC subexpr EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC block ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC block EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC expr EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC subexpr EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL expr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL subexpr ⇒ TOK (a) EOL INDENT_INC TOK (b) EOL INDENT_INC TOK (c) EOL TOK (d) 之后的符号。这是LR(1)限制的本质:每次减少都必须在转移下一个符号之前进行,因此,即使您可以看到足够远的将来,即使文法是明确的,但如果减少了冲突,也仍然会产生转移/减少冲突。减少决策可以采用任何一种方式。