简单的解决方案是在附加到生产 1 的语义操作中检查括号列表的有效性。然后您只需摆脱PARAM
,并ARG
改用它。
另一个简单的解决方案是让 bison 生成 GLR 解析器而不是 LALR 解析器。由于语法是明确的,因此可以正常工作。它会稍微慢一些,但在大多数情况下你不会注意到。
但是,可以修改语法,以便准确识别所需的语言。有一个问题:在看到以下标记之前,我们无法判断ident(ident)
是 anE
还是 an 的开头。L
此外,我们无法判断(在大多数情况下)ident
带括号的列表中的 an 是否是 an 的一部分L
,或者是否应该将其简化为 an ,N
因为它是 an 的一部分E
。
为了避免冲突,我们在不进行某些缩减的情况下构建 AST,然后在必要时对其进行修复。特别是,我们可能需要将 an 转换L
为 anE
或将 a转换PARAM
为 an ARG
,这涉及将 idents 列表转换为 args 列表,这反过来又涉及对 each执行ident
to的归约。N
ident
(在下文中,我参考了我编写的实际代码,它使用终端是全大写的正常约定,而非终端是小写。习惯很难改掉。对不起。)
所以我们要做的是将逗号分隔列表的产生式分成两个不相交的集合。一个是name_list
,它是标识符 ( ID
s) 的简单列表,它可能会变成参数列表或参数列表。另一个产生式是arg_list
,其中至少包含一个数字 ( NUM
)。这无疑是一个参数列表。
如果我们实际上是在解析一个参数列表,我们可能会开始将它解析为一个标识符列表,但我们最终会找到一些迫使我们识别它的东西。要么我们点击 a NUM
,在这种情况下,我们需要追溯地将所有ID
s 减少为value
s,或者我们将在没有看到 a 的情况下到达语句的末尾=,在这种情况下,我们需要将 s 重新lvalue
用作调用表达式。
所以结果如下。为了尽可能清楚,我将语义动作包括在内。实际功能不包括在内,但我相信它们的行为或多或少从它们的名称中显而易见。请注意,在两个操作中存在显式转换:一个将 a 转换param_list
为 an arg_list
(当NUM
遇到 a 时),另一个将 an 转换lvalue
为 an 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
or NUM
。这仍然可以与上述模型一起使用。我们需要定义expr_not_ID
(即 anexpr
不仅仅是 an ID
),我们将NUM
在上面的产生式中使用它来代替。然后我们可以将 as 定义expr
为expr_not_ID | ID
, 用于两个产生式 for stmt
(并且可能在语法中的其他地方)。