当规则涵盖另一个规则的子集时,消除语法歧义

时间:2016-02-07 01:10:18

标签: c parsing grammar bison ambiguous

我正在尝试构建一个小的野牛语法,但是我对部分定义存在问题。可以使用右侧的任何表达式(语法中的expression_list)作为参数调用函数。

问题出现了,因为在左侧,可以通过分配函数来定义函数(标识符后跟标识符列表 - 语法中的assignment_expression和identifier_list)

我的问题是如何消除语法中的模糊性,因为左侧合法的陈述是右侧合法的一部分。

语法是用野牛写的(v.2.4.1)

命令的输出是:

2 shift/reduce, 2 reduce/reduce
warning: rule useless in parser due to conflicts: assignment_expression: IDENTIFIER LPAREN RPAREN

这是完整的语法:

expression:
    assignment_expression
    | expression DECORATOR IDENTIFIER

value:
    IDENTIFIER
    | HEX 
    | BIN 
    | OCT
    | SCI 
    | FLOAT 
    | INT
    ;

constant_expression:
    value
    | LPAREN constant_expression RPAREN
    | constant_expression OR constant_expression
    | constant_expression XOR constant_expression
    | constant_expression AND constant_expression
    | constant_expression LSHIFT constant_expression
    | constant_expression RSHIFT constant_expression
    | constant_expression PLUS constant_expression
    | constant_expression MINUS constant_expression
    | constant_expression MUL constant_expression
    | constant_expression DIV constant_expression
    | constant_expression MOD constant_expression
    | constant_expression POW constant_expression
    | constant_expression FACTORIAL
    | NOT constant_expression
    | IDENTIFIER LPAREN RPAREN
    | IDENTIFIER LPAREN constant_expression RPAREN
    | IDENTIFIER LPAREN expression_list RPAREN
    ;

expression_list:
    constant_expression COMMA constant_expression
    | expression_list COMMA constant_expression
    ;

assignment_expression:
    constant_expression
    | IDENTIFIER EQUAL assignment_expression
    | IDENTIFIER LPAREN RPAREN
    | IDENTIFIER LPAREN IDENTIFIER RPAREN
    | IDENTIFIER LPAREN identifier_list RPAREN
    ;

identifier_list:
    IDENTIFIER COMMA IDENTIFIER
    | identifier_list COMMA IDENTIFIER
    ;

以下是来自详细模式(-v)的野牛输出的相关部分:

State 34 conflicts: 2 shift/reduce
State 35 conflicts: 2 reduce/reduce

state 34

3 value: IDENTIFIER .
25 constant_expression: IDENTIFIER . LPAREN RPAREN
26                    | IDENTIFIER . LPAREN constant_expression RPAREN
27                    | IDENTIFIER . LPAREN expression_list RPAREN
33 assignment_expression: IDENTIFIER LPAREN IDENTIFIER . RPAREN
35 identifier_list: IDENTIFIER . COMMA IDENTIFIER

COMMA   shift, and go to state 53
LPAREN  shift, and go to state 39
RPAREN  shift, and go to state 54

COMMA     [reduce using rule 3 (value)]
RPAREN    [reduce using rule 3 (value)]
$default  reduce using rule 3 (value)


state 35

25 constant_expression: IDENTIFIER LPAREN RPAREN .
32 assignment_expression: IDENTIFIER LPAREN RPAREN .

$end       reduce using rule 25 (constant_expression)
$end       [reduce using rule 32 (assignment_expression)]
DECORATOR  reduce using rule 25 (constant_expression)
DECORATOR  [reduce using rule 32 (assignment_expression)]
$default   reduce using rule 25 (constant_expression)

根据请求,这里是一个带有问题的最小语法:

assignment_expression:
    constant_expression
    | IDENTIFIER LPAREN identifier_list RPAREN
    ;

value:
    IDENTIFIER
    | INT
    ;

constant_expression:
    value
    | IDENTIFIER LPAREN expression_list RPAREN
    ;

expression_list:
    constant_expression COMMA constant_expression
    | expression_list COMMA constant_expression
    ;

identifier_list:
    IDENTIFIER COMMA IDENTIFIER
    | identifier_list COMMA IDENTIFIER
    ;

1 个答案:

答案 0 :(得分:4)

你的文字和你的语法并不完整。或者我可能没有正确理解你的文字。你说:

  

在左侧,可以通过赋值来定义函数(标识符后跟标识符列表 - 语法中的assignment_expression和identifier_list)

在我的脑海中,我想象一下这样的例子:

comb(n, r) = n! / (r! * (n-r)!)

但你的语法是:

assignment_expression:
    constant_expression
    | IDENTIFIER EQUAL assignment_expression
    | IDENTIFIER LPAREN RPAREN
    | IDENTIFIER LPAREN IDENTIFIER RPAREN
    | IDENTIFIER LPAREN identifier_list RPAREN

这不会解析上面的定义,因为EQUAL左侧唯一可以显示的是IDENTIFIER。右递归允许在赋值表达式之前进行任意数量的IDENTIFIER =重复,但最后一件事必须是constant_expression或三个原型制作中的一个。所以这将匹配:

c = r = f(a,b)

但是这样:

c = r = f(2, 7)

我说这会使你的语法本身含糊不清,但这可能是一个错误。你的意思是:

assignment_expression: rvalue
                     | lvalue '=' assignment_expression

rvalue: constant_expression

lvalue: IDENTIFIER
      | IDENTIFIER '(' ')'
      | IDENTIFIER '(' identifier_list ')'

我顺便提一下,您对identifier_list的定义至少需要两个标识符是不必要的复杂,所以我在上面假设identifier_list的实际定义是:

identifier_list: IDENTIFIER | identifier_list ',' IDENTIFIER
但是,这还不足以解决问题。它仍然让解析器不知道是否:

comb(n      | lookahead ',' 

的开始
comb(n, r) = ...

或只是一个函数调用

comb(n, 4)

所以要解决这个问题,我们需要拔出一些重型火炮。

我们可以从简单的解决方案开始。这个语法不含糊,因为lvalue必须跟=。当我们最终到达=时,我们可以判断到目前为止我们所拥有的是rvalue还是lvalue,即使它们恰好相同。 (例如comb(n, r)。)唯一的问题是=可能与我们碰巧的位置无限距离。

对于野牛,如果我们有一个明确的语法,我们不能解决前瞻问题,我们可以要求一个GLR解析器。 GLR解析器的效率略低,因为它需要并行维护所有可能的解析,但对于大多数明确的语法而言,它仍然是线性复杂度。 (GLR解析器甚至可以解析O中的模糊语法(N 3 ),但是野牛实现并不容忍歧义。毕竟,它旨在解析编程语言。)

所以要做到这一点,你只需要添加

%glr-parser

并阅读bison手册中有关语义操作如何受到影响的部分。 (摘要:它们存储起来直到解析消除歧义,所以它们可能不会像在LALR(1)解析器中那样在解析期间发生。)

第二个简单的解决方案,在实践中相当普遍,是让解析器接受所需语言的超集,然后在语义动作中添加可以说是句法检查的内容。所以你可以编写语法来允许看起来像call_expression的任何东西在赋值的左侧,但是当你为赋值/定义实际构建AST节点时,验证参数呼叫列表实际上是一个简单的标识符列表。

这不仅可以在没有太多实现成本的情况下简化语法,还可以生成准确的错误消息来描述语法错误,这对于标准LALR(1)解析器来说并不容易。

仍然, 是你的语言的LALR(1)语法(或者更确切地说,是我想象的语言)。为了产生它,我们需要避免强制减少,这将区分lvaluervalue,直到我们知道它是哪一个。

所以问题是IDENTIFIER可以是expression_list的一部分,也可以是identifier_list的一部分。即使我们看到),我们也不知道哪一个。因此,我们需要特殊情况IDENTIFIER '(' identifier_list ')',以使其成为lvaluervalue的一部分。换句话说,我们需要类似的东西:

lvalue: IDENTIFIER | prototype
rvalue: expression_other_than_lvalue | lvalue

这就留下了我们如何定义expression_other_than_lvalue的问题。

很多时候,解决方案很简单:常量,运算符表达式,带括号的表达式;这些都不是左值。包含expression_other_than_identifier的括号列表的调用也是expression_other_than_identifier。唯一不会被计算的是IDENTIFIER(IDENTIFIER,IDENTIFIER,...)

所以让我们尽可能地重写语法。 (我已将constant_expression更改为lvalue,因为它的输入时间更短。并且为实际符号替换了许多令牌名称,我发现这些名称更具可读性。但以下大部分内容与你原来的。)

value_not_identifier: HEX | BIN | OCT | SCI | FLOAT | INT

expr_not_lvalue:
    value_not_identifier
    | '(' rvalue ')'
    | rvalue OR rvalue
    | ...
    | IDENTIFIER '(' list_not_id_list ')'

lvalue:
    IDENTIFIER
    | IDENTIFIER '(' ')'
    | IDENTIFIER '(' identifier_list ')'

identifier_list:
    IDENTIFIER | identifier_list ',' IDENTIFIER

现在,除了我们尚未定义list_not_id_list的细节外,一切都将落实到位。 lvalueexpr_not_lvalue不相交,因此我们可以完成:

rvalue:
    lvalue
    | expr_not_lvalue

assignment_expression:
    rvalue
    | lvalue '=' assignment_expression

我们只需要处理不是标识符列表的表达式列表。如上所述,这就像:

expr_not_identifier:
    expr_not_lvalue
    lvalue_not_identifier

list_not_id_list:
    expr_not_identifier
    | list_not_id_list ',' rvalue
    | identifier_list ',' expr_not_identifier

因此,在解析列表时,我们第一次找到不是标识符的内容时,会从identifier_list生产中删除列表。如果我们浏览整个列表,那么当需要lvalue时,我们仍然可能会发现rvalue,但是当我们看到=或者expression: assignment_expression | expression DECORATOR IDENTIFIER assignment_expression: rvalue | lvalue '=' assignment_expression value_not_identifier: HEX | BIN | OCT | SCI | FLOAT | INT expr_not_lvalue: value_not_identifier | '(' rvalue ')' | rvalue OR rvalue | rvalue XOR rvalue | rvalue AND rvalue | rvalue LSHIFT rvalue | rvalue RSHIFT rvalue | rvalue '+' rvalue | rvalue '-' rvalue | rvalue '*' rvalue | rvalue '/' rvalue | rvalue '%' rvalue | rvalue POW rvalue | rvalue '!' | NOT rvalue | IDENTIFIER '(' list_not_id_list')' lvalue_not_identifier: IDENTIFIER '(' ')' | IDENTIFIER '(' identifier_list ')' lvalue: lvalue_not_identifier | IDENTIFIER rvalue: lvalue | expr_not_lvalue identifier_list: IDENTIFIER | identifier_list ',' IDENTIFIER list_not_id_list: expr_not_identifier | list_not_id_list ',' rvalue | identifier_list ',' expr_not_identifier expr_not_identifier: expr_not_lvalue lvalue_not_identifier 时,我们可以做出决定(最终)声明终结者。

所以正确的(我希望)完整的语法是:

{{1}}

鉴于简单解决方案的可用性以及实现精确语法所需的转换的不雅,难怪您很少看到这种结构。但是,您会发现它在ECMA-262标准(定义ECMAScript又称Javascript)中广泛使用。该报告中使用的语法形式包括一种简化上述转换的宏观特征,但它不会使语法更容易阅读(imho),而且我不知道解析器生成器实现该功能。