我正在写一个应该识别以下单词的语法(YACC - “LALR”),例如:
ident(ident,...,ident) = ident(num,ident,num,...,num)
ident(ident,...,ident) = num
ident = num
ident = ident(num,ident,num,...,num)
ident(ident,num,...,num)
num
ident
我写了下面的语法:
(1) L : ident ( PARAM ) = E
(2) L : E
(3) E : ident ( ARG )
(4) E : N
(5) N : num
(6) N : ident
(7) ARG : ARG , E
(8) ARG : E
(9) PARAM : PARAM , ident
(10)PARAM : ident
所以,我的问题是减少/减少规则(6)和(10)之间的冲突。 有谁知道如何解决这个问题?
以前,我问过以下问题,认为这是我真正问题的简化......但事实并非如此。
为了记录,我之前的问题是语法识别这些词:
a
b
b,b
b,b,b
[...]
b,b,b,[...],b # infinite list of b
所以,我写了下面的语法:
L : E
| F
E : a
| b
F : F , b
| b
但是,从逻辑上讲,我对规则有减少/减少冲突:
F : b
和
E : b
这个问题的正确语法是:
E : a
E : F
F : F,b
F : b
答案 0 :(得分:1)
简单的解决方案是在附加到生产1的语义操作中检查带括号列表的有效性。然后,您只需删除PARAM
,然后使用ARG
。
另一个简单的解决方案是让bison生成GLR解析器而不是LALR解析器。由于语法是明确的,这将工作正常。它会稍慢,但在大多数情况下你不会注意到。
但是,可以修改语法,使其能够准确识别所需的语言。有一个问题:在我们看到以下令牌之前,我们无法确定ident(ident)
是E
还是L
的开头。此外,我们无法判断(在大多数情况下)括号列表中的ident
是L
的一部分,还是应该缩减为N
,因为它是其中的一部分E
。
为了避免冲突,我们在没有确定减少的情况下构建AST,然后在必要时进行修复。特别是,我们可能需要将L
转换为E
或PARAM
转换为ARG
,这涉及将idents列表转换为args列表,其中反过来又涉及在每个ident
上执行N
到ident
的缩减。
(在下文中,我指的是我写的实际代码,它使用终端为ALL-CAPS的常规约定,而非终端是小写的。习惯很难。抱歉。)
所以我们要做的是将逗号分隔列表的制作分成两个不相交的集。一个是name_list
,它是一个简单的标识符列表(ID
s),可能会成为参数列表或参数列表。另一个产品是arg_list
,其中至少包含一个数字(NUM
)。这毫不含糊地是一个参数列表。
如果我们实际上正在解析一个参数列表,我们可能会开始将它解析为一个标识符列表,但我们最终会找到一些强迫我们识别它的东西。要么我们要点击NUM
,在这种情况下我们需要追溯性地将所有ID
减少到value
,否则我们将在没有看到声明的情况下到达声明的末尾 = ,在这种情况下,我们需要将lvalue
重新用作调用表达式。
因此导致以下结果。为了尽可能清楚,我包括了语义动作。实际功能不包括在内,但我相信他们的行为或多或少都与他们的名字有关。请注意,在两个操作中有一个显式转换:一个用于将param_list
转换为arg_list
(遇到NUM
时),另一个用于转换lvalue
到expr
。此外,我实际上没有插入value: ID | NUM
作品,因为在这种情况下,作为语义动作的一部分进行简化是更直截了当的。请参阅语法后面的注释。
prog: /* EMPTY */
| prog stmt ';' { print_stmt($2); free_node($2); }
param_list
: ID { $$ = push_name(0, $1); }
| param_list ',' ID { $$ = push_name($1, $3); }
arg_list
: NUM { $$ = push_val(0, make_ival($1)); }
| arg_list ',' ID { $$ = push_val($1, make_sval($3)); }
| arg_list ',' NUM { $$ = push_val($1, make_ival($3)); }
| param_list ',' NUM { $$ = push_val(names_to_vals($1),
make_ival($3)); }
lvalue
: ID '(' param_list ')' { $$ = make_proto($1, reverse_names($3)); }
expr: ID '(' arg_list ')' { $$ = make_call($1, reverse_vals($3)); }
| lvalue { $$ = proto_to_call($1); }
| NUM { $$ = make_ival($1); }
| ID { $$ = make_sval($1); }
stmt: lvalue '=' expr { $$ = make_assign($1, $3); }
| expr
以下是上述示例输出:
id1;
[E: id1]
3;
[E: 3]
f(id1);
[CALL: f([E: id1])]
f(3);
[CALL: f([E: 3])]
f(id1,3);
[CALL: f([E: id1], [E: 3])]
f(3,id1);
[CALL: f([E: 3], [E: id1])]
f(id1)=g(id2);
[ASSIGN: [PROTO: f(id1)] = [CALL: g([E: id2])]]
f(id1)=42;
[ASSIGN: [PROTO: f(id1)] = [E: 42]]
f(id1)=g(42);
[ASSIGN: [PROTO: f(id1)] = [CALL: g([E: 42])]]
f(id1)=g;
[ASSIGN: [PROTO: f(id1)] = [E: g]]
在真正的语法中,arg_list
实际上可能是expr
的列表,而不仅仅是ID
或NUM
。这仍然适用于上述模型。我们需要定义expr_not_ID
(即expr
,而不仅仅是ID
),我们将在上述作品中使用NUM
代替expr
。然后我们可以将expr_not_ID | ID
定义为stmt
,用于app.get('/question/:id', function(req, res) {
var id = req.params.id;
//do something with this id like render a page
);
的两个作品(可能还有语法中的其他地方)。