减少/减少冲突

时间:2015-12-09 21:22:13

标签: grammar yacc reduce-reduce-conflict

我正在写一个应该识别以下单词的语法(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

1 个答案:

答案 0 :(得分:1)

简单的解决方案是在附加到生产1的语义操作中检查带括号列表的有效性。然后,您只需删除PARAM,然后使用ARG

另一个简单的解决方案是让bison生成GLR解析器而不是LALR解析器。由于语法是明确的,这将工作正常。它会稍慢,但在大多数情况下你不会注意到。

但是,可以修改语法,使其能够准确识别所需的语言。有一个问题:在我们看到以下令牌之前,我们无法确定ident(ident)E还是L的开头。此外,我们无法判断(在大多数情况下)括号列表中的identL的一部分,还是应该缩减为N,因为它是其中的一部分E

为了避免冲突,我们在没有确定减少的情况下构建AST,然后在必要时进行修复。特别是,我们可能需要将L转换为EPARAM转换为ARG,这涉及将idents列表转换为args列表,其中反过来又涉及在每个ident上执行Nident的缩减。

(在下文中,我指的是我写的实际代码,它使用终端为ALL-CAPS的常规约定,而非终端是小写的。习惯很难。抱歉。)

所以我们要做的是将逗号分隔列表的制作分成两个不相交的集。一个是name_list,它是一个简单的标识符列表(ID s),可能会成为参数列表或参数列表。另一个产品是arg_list,其中至少包含一个数字(NUM)。这毫不含糊地是一个参数列表。

如果我们实际上正在解析一个参数列表,我们可能会开始将它解析为一个标识符列表,但我们最终会找到一些强迫我们识别它的东西。要么我们要点击NUM,在这种情况下我们需要追溯性地将所有ID减少到value,否则我们将在没有看到声明的情况下到达声明的末尾 = ,在这种情况下,我们需要将lvalue重新用作调用表达式。

因此导致以下结果。为了尽可能清楚,我包括了语义动作。实际功能不包括在内,但我相信他们的行为或多或少都与他们的名字有关。请注意,在两个操作中有一个显式转换:一个用于将param_list转换为arg_list(遇到NUM时),另一个用于转换lvalueexpr。此外,我实际上没有插入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的列表,而不仅仅是IDNUM。这仍然适用于上述模型。我们需要定义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 ); 的两个作品(可能还有语法中的其他地方)。