我正在尝试构建一个小的野牛语法,但是我对部分定义存在问题。可以使用右侧的任何表达式(语法中的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
;
答案 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)语法(或者更确切地说,是我想象的语言)。为了产生它,我们需要避免强制减少,这将区分lvalue
和rvalue
,直到我们知道它是哪一个。
所以问题是IDENTIFIER
可以是expression_list的一部分,也可以是identifier_list的一部分。即使我们看到)
,我们也不知道哪一个。因此,我们需要特殊情况IDENTIFIER '(' identifier_list ')'
,以使其成为lvalue
和rvalue
的一部分。换句话说,我们需要类似的东西:
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
的细节外,一切都将落实到位。 lvalue
和expr_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),而且我不知道解析器生成器实现该功能。