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

标签 parsing gnu bison flex-lexer

我已经很长一段时间没有使用 Bison 了,所以我可能在这里遗漏了一些简单的东西,但是,我无法弄清楚为什么下面的语法会产生shift/reduce冲突。我认为下面的语法没有歧义。它的目的是解析如下表达式:

a b
  c d
    e f
  g h

as(伪 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冲突。在调试解析器时,我们可以观察到:

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)

说实话,我不相信这种歧义从何而来。对于如何消除此类语法中的冲突的任何帮助,我将不胜感激。

最佳答案

您的语法不使用INDENT_DEC;如果没有它,您就无法知道缩进的 block 在哪里结束。

实际上,这就是那些转变/归约冲突所告诉您的。由于语法看不到 INDENT_DEC,因此它无法区分分隔同一 block 中的两个 exprEOL 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)

所以语法确实是有歧义的。但移位/归约冲突并不直接指向歧义。他们指出了决定是否在 EOL 之前减少构造的问题,而无需看到 EOL 后面的符号。这是 LR(1) 限制的本质:每次归约都必须在移位下一个符号之前进行,因此,即使语法是明确的(如果您可以预见到足够远的 future ),它仍然会存在移位/归约冲突,如果削减决定可以采取任何一种方式。

关于parsing - Gnu Bison 移位/减少描述分层表达式的基于缩进的语法中的冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56469457/

相关文章:

python - 使用正则表达式解析字符串python3

java - 我应该如何实现这个?

java - J汤 |获取部分 HTML

python-3.x - 在 macOS 上为 Python 3.6.3 使用 dbm.gnu

bison - 如何向 yylex 添加其他参数(在 bison/flex 中)?

xml - 从单独的线程下载XML/图像

c++ - Gnu 编译器限制是否适用于 C 语言

c - 如何构建具有相似名称的多个目标?

c++ - 编码 PHP 代码浏览器 : is Bison/Flex a choice?

grammar - Bison 语法中的歧义