yacc shift / reduce conflict:函数调用vs函数定义

时间:2014-02-02 14:57:51

标签: yacc shift-reduce-conflict

我有以下lex定义:

[a-zA-Z][a-zA-Z0-9_]*       return NAME;

\,              return COMMA;
\:              return COLON;
\;              return SEMICOLON;
\(              return OPAREN;
\)              return CPAREN;
\+              return PLUS;

以下yacc制作规则:

program: 
    | program statement;

arglist:
    OPAREN CPAREN
    | OPAREN expressionlist CPAREN;

trailed:
    NAME
    | trailed arglist;

expression:
    trailed
    | expression PLUS trailed;

expressionlist:
    expression
    | expressionlist COMMA expression;

statement:
    expression SEMICOLON
    |NAME arglist COLON expression SEMICOLON;

如果我注释掉最后一条规则,那么一切都会好好编译。根据最后一条规则,我遇到了冲突:

yacc: 1 shift/reduce conflict.

所以我猜,yacc无法决定是将下一个符号移到堆栈上还是使用给定规则来减少堆栈。

  1. 我的语法模棱两可吗?

  2. 规则“trailed:trailed arglist”和“statement:NAME arglist COLON expression SEMICOLON”之间的决定不应该没有冲突,因为前者从来没有冒号,而后者总是有?

  3. 这与先行缓冲区的大小有关吗?

  4. 如何修复此语法以解析“a(b)();”和“a(b,c):b + c;”作为有效的陈述?

  5. 如何以更详细的方式回溯冲突?

  6. ----编辑

    关于MichaelMoser的回答:

    更改

    arglist:
        OPAREN CPAREN
        | OPAREN expressionlist CPAREN;
    
    expressionlist:
        expression
        | expressionlist COMMA expression;
    

    arglist: OPAREN expressionlist CPAREN;
    
    expressionlist:
    | expressionlist COMMA expression; //this now allows for expression lists like ,a,b but NVM
    

    如建议没有帮助。 statement活跃的第二条规则仍然存在冲突,一旦评论该规则,就不会发生冲突。

3 个答案:

答案 0 :(得分:2)

正如其他人所指出的那样,问题是您需要多个前瞻标记来区分函数定义和函数调用。编写语法的问题在于,当前瞻为trailed: NAME时,需要在看到statement: NAME arglist COLON expression SEMICOLON之后决定减少规则NAME和转移以匹配规则OPAREN 。但是在它看到arglist之后才能决定是否有COLON之后(这是两个案例的区别)。

要解决此问题,您需要重构语法,以便在到达COLON之前不需要减少仅存在于一个替代方案上的任何内容。使用此语法,您可以通过重构trailed规则以始终至少需要一个arglist,并使NAME没有arglist单独的expression规则来实现此目的:< / p>

trailed:
    NAME arglist
    | trailed arglist;

expression:
    NAME
    | trailed
    | expression PLUS NAME
    | expression PLUS trailed;

现在,当您获得输入NAME OPAREN ...时,无需减少任何内容 - 您只需切换到与arglist匹配的规则,并在匹配arglist之后,您可以看到下一个标记,并确定这是函数调用还是函数定义。

答案 1 :(得分:1)

如果你想调试shift / reduce错误,那么将--report = all --report-file = pars.report添加到bison命令行,生成的pars.report文件会让你清楚。

缩小的语法文件

%token OPAREN CPAREN NAME PLUS COMMA SEMICOLON COLON

%%
program:
    | program statement;

arglist:
    OPAREN CPAREN
    | OPAREN expressionlist CPAREN;

trailed:     
    NAME
    | trailed arglist;

expression:
    trailed
    | expression PLUS trailed;

expressionlist:
    expression      
    | expressionlist COMMA expression;

statement:     
    expression SEMICOLON
    |NAME arglist COLON expression SEMICOLON;

给出以下报告:

State 3 conflicts: 1 shift/reduce

....

state 3

    3 arglist: . OPAREN CPAREN
    4        | . OPAREN expressionlist CPAREN
    5 trailed: NAME .  [OPAREN, PLUS, SEMICOLON]
   12 statement: NAME . arglist COLON expression SEMICOLON

    OPAREN  shift, and go to state 7

    OPAREN    [reduce using rule 5 (trailed)]
    $default  reduce using rule 5 (trailed)

    arglist  go to state 8

在报告的开头有一个导致错误的解析器状态,然后在以后你可以看到解析状态的细节。这个文件也很有趣,因为它实际上解释了解析器在每一步都在做什么。

冲突发生在

之间
statement : NAME arglist COLON expression SEMICOLON;

statement : expression;
expression : trailed;
trailed : NAME | tailed arglist;

答案 2 :(得分:0)

鉴于源语法:

%token COLON COMMA CPAREN NAME OPAREN PLUS SEMICOLON

%%

program: 
    | program statement
    ;

arglist:
      OPAREN CPAREN
    | OPAREN expressionlist CPAREN
    ;

trailed:
      NAME
    | trailed arglist
    ;

expression:
      trailed
    | expression PLUS trailed
    ;

expressionlist:
      expression
    | expressionlist COMMA expression
    ;

statement:
      expression SEMICOLON
    | NAME arglist COLON expression SEMICOLON
    ;

我从bison -v xyz.y获得的报告提供了xyz.y: conflicts: 1 shift/reduce,但xyz.output中的说明与MichaelMoser中的answer报告略有不同。

State 3 conflicts: 1 shift/reduce

…

state 3

    5 trailed: NAME .
   12 statement: NAME . arglist COLON expression SEMICOLON

    OPAREN  shift, and go to state 7

    OPAREN    [reduce using rule 5 (trailed)]
    $default  reduce using rule 5 (trailed)

    arglist  go to state 8

这意味着当语法读取了NAME并获得OPAREN时,无法推断出它是否在trailed后面跟arglist或是statement使用NAME后跟arglist。在它到达COLON之前,它将无法确定差异,因为它太远而无法通过一个前瞻标记来确定。

这使得语法无法使用普通Yacc进行解析,因为Yacc只能向前看一个标记。 (我不确定这是否会使其变得含糊不清 - “在Yacc中不起作用”涵盖了这种情况.Bison提供了可能有用的GLR语法。)