C 当一条规则覆盖另一条规则的子集时消除语法歧义
我试图建立一个小野牛语法,但我有一个问题的部分定义。可以使用语法中右侧表达式列表中的任何表达式作为参数来调用函数 出现这个问题的原因是,在左侧,可以通过为函数分配一个标识符,然后是标识符列表(语法中的赋值表达式和标识符列表)来定义函数 我的问题是如何消除语法中的歧义,因为左侧的合法语句是右侧合法语句的子集 语法是用bison v写的。2.4.1 命令的输出为:C 当一条规则覆盖另一条规则的子集时消除语法歧义,c,parsing,grammar,bison,ambiguous,C,Parsing,Grammar,Bison,Ambiguous,我试图建立一个小野牛语法,但我有一个问题的部分定义。可以使用语法中右侧表达式列表中的任何表达式作为参数来调用函数 出现这个问题的原因是,在左侧,可以通过为函数分配一个标识符,然后是标识符列表(语法中的赋值表达式和标识符列表)来定义函数 我的问题是如何消除语法中的歧义,因为左侧的合法语句是右侧合法语句的子集 语法是用bison v写的。2.4.1 命令的输出为: 2 shift/reduce, 2 reduce/reduce warning: rule useless in parser due
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
;
下面是bison从详细模式-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
;
你的课文和语法不太一致。或者我没有正确理解你的文字。你说: 在左侧,可以通过为函数分配一个标识符,后跟标识符列表(语法中的赋值表达式和标识符列表)来定义函数 在我的脑海中,我想象这样一个例子:
comb(n, r) = n! / (r! * (n-r)!)
lvalue: IDENTIFIER | prototype
rvalue: expression_other_than_lvalue | lvalue
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
但你的语法是:
assignment_expression:
constant_expression
| IDENTIFIER EQUAL assignment_expression
| IDENTIFIER LPAREN RPAREN
| IDENTIFIER LPAREN IDENTIFIER RPAREN
| IDENTIFIER LPAREN identifier_list RPAREN
它不会解析上面的定义,因为EQUAL左侧唯一可以显示的是IDENTIFIER。正确的递归允许在赋值\表达式之前重复任意次数的IDENTIFIER=,但最后必须是常量\表达式或三个原型产品之一。因此,这将是匹配的:
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 ')'
我顺便指出,您对标识符列表的定义需要至少两个标识符,这是不必要的复杂,因此我在上面假设标识符列表的实际定义是:
不过,这还不足以解决问题。它仍然使解析器不知道是否:
comb(n | lookahead ','
这是一个新的开始
comb(n, r) = ...
或者只是一个函数调用
comb(n, 4)
所以为了解决这个问题,我们需要撤出一些重炮
我们可以从简单的解决方案开始。这种语法并不含糊,因为左值后面必须跟=。当我们最终到达=,我们可以判断到目前为止我们得到的是右值还是左值,即使它们看起来完全相同。例如,库姆,r。唯一的问题是=可能与我们所处的位置有无限的距离
对于bison,如果我们有一个明确的语法,并且我们不能费心去解决前瞻问题,那么我们可以请求一个GLR解析器。GLR解析器的效率稍低,因为它需要并行维护所有可能的解析,但对于大多数明确的语法来说,它仍然是线性复杂度。GLR解析器甚至可以解析ON3中的歧义语法,但bison实现不能容忍歧义。毕竟,它是为解析编程语言而设计的
因此,要做到这一点,您只需添加
%glr-parser
阅读《野牛手册》中关于语义行为如何受到影响的部分。小结:它们会一直存储到解析被消除歧义为止,因此它们可能不会像在LALR1解析器中那样在解析的早期发生
第二个简单的解决方案是,让解析器接受所需语言的超集,然后在语义操作中添加语法检查,这在实践中相当常见。因此,您可以编写语法来允许任何看起来像call_表达式的内容位于赋值的左侧,但是当您实际为赋值/定义构建AST节点时,请验证调用的参数列表实际上是一个简单的标识符列表
这不仅简化了语法,而且没有太多的实现成本,还可以生成准确的错误消息来描述语法错误,这对于标准的LALR1解析器来说并不容易
尽管如此,你的语言还是有一个LALR1语法,或者更确切地说,是我想象中的你的语言。为了产生它,我们需要避免强制减少,这将区分左值和右值,直到我们知道它是哪一个
因此,问题在于标识符可以是表达式列表的一部分,也可以是标识符列表的一部分。我们不知道是哪一个,甚至当我们看到。因此,我们需要创建一个特殊的案例标识符列表,以允许它同时成为左值和右值的一部分。换句话说,我们需要这样的东西:
comb(n, r) = n! / (r! * (n-r)!)
lvalue: IDENTIFIER | prototype
rvalue: expression_other_than_lvalue | lvalue
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
这就留下了我们如何定义表达式而不是左值的问题
大多数情况下,解决方案都很简单:常量、运算符表达式、括号表达式;这些都不是左值。带有括号列表且包含表达式而非标识符的调用也是express
除标识符之外的其他标识符。唯一不算数的就是标识符,标识符
让我们尽可能地重写语法。我已经将常量_表达式改为左值,因为它比类型短。并用许多标记名替换了实际的符号,我发现更容易阅读。但以下大部分内容与您的原始内容相同
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\u not\u id\u list的细节之外,一切都将就绪。左值和expr_not_左值是不相交的,因此我们可以结束以下内容:
rvalue:
lvalue
| expr_not_lvalue
assignment_expression:
rvalue
| lvalue '=' assignment_expression
我们只需要处理不是标识符列表的表达式列表。如上所述,这类似于:
comb(n, r) = n! / (r! * (n-r)!)
lvalue: IDENTIFIER | prototype
rvalue: expression_other_than_lvalue | lvalue
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
因此,在解析列表时,当我们第一次发现不是标识符的东西时,我们会从标识符列表产品中删除该列表。如果我们看完了整个列表,那么当需要右值时,我们可能仍然会发现自己有一个左值,但是当我们看到=或语句终止符时,最终可以做出决定
因此,我希望完整语法的正确答案是:
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
考虑到简单解决方案的可用性,以及实现精确语法所需的转换的不美观性,难怪您很少看到这种构造。但是,您会发现它在ECMA-262标准中被广泛使用,ECMA-262标准定义了ECMAScript又名Javascript。该报告中使用的语法形式主义包括一种宏功能,它简化了上述转换,但它并没有使语法更易于阅读,而且我不知道有哪种解析器生成器实现了该功能。这并不难。您只需要一个派生子集的非终结符和另一个派生超集的非终结符。然后相应地使用它们。没有办法区分你对paren对的两种用法,因为它们可能包含相同的内容。@gene恐怕我不完全理解……如果你就这个问题发布一个最简单的完整语法,你会得到更多帮助。我的意思是bison会处理一些问题。@rscarson:很抱歉写得那么糟糕,然后又进行编辑信息技术我认为它现在可以使用了,但是如果有任何问题,请告诉我。