野牛减少/减少情况

时间:2016-12-12 06:44:07

标签: bison

我有一个特殊的结构,我在调试时遇到了问题。 我希望这种积极的简化能够恰当地证明这个问题:

%token VAR IDENTIFIER NUMBER INT

%%
root:
    code_statements
    ;

code_statements:
    code_statement
    | code_statements code_statement
    ;

code_statement:
    var_statement
    | expression_statement
    ;

var_decl:
    IDENTIFIER ':' type_expression
    ;
var_statement:
    VAR var_decl ';'
    ;

expression_statement:
    value_expression ';'
    ;

function_call:
    '(' ')'
    | '(' value_expression_list ')'
    ;

value_expression_list:
    value_expression
    | value_expression_list ',' value_expression
    ;

/**** UNKNOWN EXPRESSIONS (VALUE/TYPE?) ****/

unknown_expression:
    IDENTIFIER
    | unknown_expression '.' IDENTIFIER
    | postfix_known_value_expression '.' IDENTIFIER
    | postfix_known_type_expression '.' IDENTIFIER
    ;

/**** VALUE EXPRESSION ****/

primary_value_expression:
    NUMBER
    | '(' value_expression ')'
    ;

postfix_known_value_expression:
    primary_value_expression
    | postfix_value_expression function_call
    ;

postfix_value_expression:
    postfix_known_value_expression
    | unknown_expression
    ;

value_expression:
    postfix_value_expression
    ;


/**** TYPE EXPRESSION ****/

primary_type_expression:
    INT
    | '(' type_expression ')'
    ;

postfix_known_type_expression:
    primary_type_expression
    | postfix_type_expression '*'
    ;

postfix_type_expression:
    postfix_known_type_expression
    | unknown_expression
    ;

type_expression:
    postfix_type_expression
    ;

%%

错误输出:

State 11

   14 unknown_expression: unknown_expression . '.' IDENTIFIER
   22 postfix_value_expression: unknown_expression .
   29 postfix_type_expression: unknown_expression .

    '.'  shift, and go to state 26

    ';'       reduce using rule 22 (postfix_value_expression)
    ';'       [reduce using rule 29 (postfix_type_expression)]
    ')'       reduce using rule 22 (postfix_value_expression)
    ')'       [reduce using rule 29 (postfix_type_expression)]
    '*'       reduce using rule 29 (postfix_type_expression)
    $default  reduce using rule 22 (postfix_value_expression)

这里有两个问题,一个与)相关的问题很容易理解,但我很难想出一个优雅的解决方法。关于;的问题,我无法看到它是如何发生的(虽然我确实知道如何影响它),而且我不确定如何从错误中取出相关信息到调试实际问题。

在我的实际语法中,还有一些,但我认为这些模式与这两种模式基本相同。

1 个答案:

答案 0 :(得分:2)

这里的基本问题是状态11中显示的一对产品:(注意:我使用命令行选项--report=all而不是-v来查看先行计算< / em>的。)

22 postfix_value_expression: unknown_expression .  [';', '(', ')', ',']
29 postfix_type_expression: unknown_expression .  [';', ')', '*']

在这里,您可以看到postfix_value_expressionpostfix_type_expression的计算前瞻包括;

其中第一个是(有些)虚假的;这是LALR算法混淆实际上具有不同前瞻的两个状态的罕见情况之一。 (这个问题在bison manual中进行了简要讨论,在大多数标准解析教科书中都有更长的篇幅。)如果我们要求bison使用Canonical LR算法(%define lr.type canonical-lr),这种冲突会消失,但第二个遗体(在州52):

22 postfix_value_expression: unknown_expression .  ['(', ')']
29 postfix_type_expression: unknown_expression .  [')', '*']

我们这里的语法是一种真正含糊不清的模糊性。 (我假设&#34;类型&#34;可以有&#34;值&#34;成员(反之亦然),正如语法似乎表明的那样。)

假设输入包括

x.member

解析器无需知道x是类型还是值。 x将简单地解析为unknown_expression(生产13),索引选择也将是unknown_expression(生产14)。 [注意:我在本回答结尾处复制了输出文件中的号码制作规则]。

但由于类型可以用括号括起来(制作25),所以可以合理地写出上述内容:

(x).member

现在解析器需要应用生产15或16才能派生unknown_expression,这意味着它需要使用生产22(继续生产15)或生产29来减少x(用于生产16)。但是,它不知道x是一个类型还是一个值,并且它无法知道(至少在没有查询符号表的情况下)。虽然两种可能的减少(可能)是等价的,但它们是不同的产品,因此语法是模糊的。因此减少/减少冲突。

看着这种方式,冲突是我们可能称之为过早分类的结果。由于语法实际上并不关心索引运算符的左侧操作数是类型还是值,因此此时不需要解析左侧操作数。这将通过后续步骤中的语义分析来解决。在这种情况下,最简单的解决方案是不要费心区分语法中的类型和值,这对于这个简单的语言子集来说会很好。一个非常简单的语法就足够了:

expression        : postfix_expression
postfix_expression: term
                  | postfix_expression '.' IDENTIFIER
                  | postfix_expression '(' expression_list ')'
                  | postfix_expression '*'
term              : IDENTIFIER
                  | '(' expression ')'

一旦所有标识符都被分类,任何必要的语义检查(以确保表达式是正确的)都可以在后树行走中完成。

询问是否有必要在语法上解析表达式的类别是很有用的。实际上是否需要消除解析的歧义?

在C(和朋友)中,除非对标识符进行分类,否则语言确实不明确。例如,(x)*y是:

  • 取消引用指针y
  • 的演员表
  • 标量xy
  • 的产品

如果不知道x的类别,则无法生成正确的解析树。 (为了使这一点更加清晰,请考虑z/(x)*y,其中解析树的形状在两个备选方案之间根本不同。还有许多其他示例。)

为C生成精确解析树的常用解决方案是求助于词汇反馈,这反过来要求在使用前声明,至少在分类的范围内。 (即使C ++坚持在使用前声明类型别名,即使它允许在声明之前使用类成员。)

一个合理的替代方法是使用类似GLR解析器的东西,它产生两个(或所有)解析树。 (这通常称为解析林。)一旦标识符被分类,后续的语义分析过程可以修剪解析林,尽管仍然需要对森林中的所有解析树进行明确的分类。 [注1]

避免上述含糊不清主要是语言设计的问题; C casts的语法使用方括号而不是括号,例如,问题可能已被消除。 (当然,这会耗尽C ++用于lambdas的语法空间。)尽管如此,很容易想象一种语法,即使不知道每个标识符的类别,其语法也是明确的,但是但是,由于句法分歧,需要考虑表达域。您的语言似乎可能是这种形式。 (在某种意义上,这是自上而下的分类而不是自下而上的分类:声明和表达式语句具有不同的解析上下文。)

例如,很容易想象您预期以与C系列类似的方式使用 * ,作为前缀解除引用运算符和中缀乘法运算符。结合上面显示的后缀类型构造运算符,最终会有一个运算符用于前缀,后缀和中缀。如果将所有用途无差别地放入同一个包中,则必然是不明确的:单个令牌最多可以表示操作数,中缀运算符,前缀运算符和后缀运算符中的两个。如果 * 可以是三种运算符类型中的任何一种,那么a * * b可能意味着(a*) * ba * (*b)

但即使不知道标识符的类别,该表达式仍然可以明确地解析,因为我们知道前缀和中缀 * 运算符都不能应用于后缀 * 类型构造函数。因此,(a*) * b是不可能的,唯一有效的解析是a * (*b)

不幸的是,由于LR(1)解析器需要根据单个先行令牌决定是否减少,因此决定a*是否应减少为type或遇到第二个*时不需要这样做,这是不可能的,因为(a * *)是一个有效的类型表达式。更糟糕的是,表达式中的星号数量没有限制,实际上可能是a*…*[b]。我们可以根据最后一个星号后面的令牌消除歧义,但这需要任意前瞻。

鉴于上述情况,最佳解决方案可能是GLR解析器,它没有先行限制,并结合足够的语法来至少划分可以使用*等运算符的上下文。

我们不能在OP中提出的语法中抛出一个GLR解析器,因为 - 如上所述 - 即使模糊性不重要,它仍然是模棱两可的。幸运的是,我们可以通过避免过早分类来创建一个明确的语法。

如上所示,带括号的表达式的问题在于语法要求它们是类型或值表达式,即使在它不关心哪些情况下也是如此。为了避免这种情况,我们需要允许延迟决定的可能性,直到有必要通过创建第三种类型的括号表达式。这导致了以下语法,实际上是LALR(1)。

我将表达式分离出来,因为否则样板是压倒性的。每个产品都有效地指示了参数的预期类别,运算符的语法优先级以及结果的类别(或未知)。每个产品的左侧是known_category_precedence,断言语法需要该类别,或unknown_precedence,表明语法本身不能预测类别。 (例如,&#39;(expr).b&#39;可能是类型或值,因此select运算符的结果为unknown_postfix。)

在左侧,您将看到category_precedence形式的非终端,它断言语义分析必须导致具有该类别的对象。这些非终端真的只是为了方便;每一个都被简单地定义为

category_precedence: known_category_precedence | unknown_precedence

其中第二个替代表示需要进行语义检查。如果在解析期间可以实现,则将其插入到第二个备选方案的操作中。或者,可以将语义检查节点插入AST中。

我在锅炉盘中加入了这些便利产品,但大多数都被注释掉了,因为当语法中没有使用非终端时,野牛会抱怨。

%token VAR "var"
%token IDENTIFIER TYPE NUMBER
%%
program            : %empty
                   | program statement
statement          : var_declaration
                   | expr_statement
var_declaration    : "var" IDENTIFIER ':' type ';'
expr_statement     : value ';'
 /* Productions for expression syntaxes */
parameters         : '(' ')'
                   | '(' value_list ')'
value_list         : value
                   | value_list ',' value
known_value_postfix: value_postfix parameters    { /* function call */ }
unknown_postfix    : any_postfix '.' IDENTIFIER  { /* select */ }
known_type_postfix : type_postfix '*'            { /* pointer type constructor */ }

 /* Primary terms */
unknown_primary    : IDENTIFIER
                   | '(' unknown ')'
known_value_primary: NUMBER
                   | '(' known_value ')'
known_type_primary : TYPE
                   | '(' known_type ')'
 /* Boilerplate precedence grammar with two infix precedence levels */
unknown_postfix    : unknown_primary
known_value_postfix: known_value_primary
known_type_postfix : known_type_primary
value_postfix      : known_value_postfix | unknown_postfix
type_postfix       : known_type_postfix  | unknown_postfix
any_postfix        : known_value_postfix | known_type_postfix | unknown_postfix

unknown_prefix     : unknown_postfix
known_value_prefix : known_value_postfix
known_type_prefix  : known_type_postfix
/* value_prefix    : known_value_prefix | unknown_prefix */
/* type_postfix    : known_type_prefix  | unknown_prefix */

unknown_infix9     : unknown_prefix
known_value_infix9 : known_value_prefix
known_type_infix9  : known_type_prefix
/* value_infix9    : known_value_infix9 | unknown_infix9 */
/* type_infix9     : known_type_infix9  | unknown_infix9 */

unknown_infix8     : unknown_infix9
known_value_infix8 : known_value_infix9
known_type_infix8  : known_type_infix9
/* value_infix9    : known_value_infix8 | unknown_infix8 */
/* type_infix9     : known_type_infix8  | unknown_infix8 */

/* The last stanza is mostly for convenience but it also serves
 * to avoid the spurious reduce/reduce conflict on ';'.
 */
unknown            : unknown_infix8
known_value        : known_value_infix8
known_type         : known_type_infix8
value              : known_value | unknown
type               : known_type | unknown

有了这个框架,我们就可以开始添加其他类型的表达式了。

首先,让我们面对 * 上的三方冲突。基于上述模式,基本表达式制作非常简单:

known_value_prefix : '*' value_prefix            { /* dereference */ }
known_value_infix9 : value_infix9 '*' value_prefix { /* produce */ }

(我们还需要取消注释value_prefixvalue_infix9作品。)

尽管语言仍然是明确的,但它不再是LALR(1)(或者甚至是任何k的LR(k)),如上面在该语法的讨论中所指出的。因此,野牛会抱怨减少冲突。我们无法修复该投诉,但我们可以轻松制作出有效的解析器;我们需要做的就是插入一个bison生成GLR解析器的请求:

%glr-parser

这并没有抑制冲突警告[注2],但它确实产生了一个有效的解析器。 (野牛无法验证语法是否明确,因为没有精确的算法。)由于我们无法证明语法是明确的,我们需要进行广泛的测试。如果GLR解析器遇到歧义,它将产生错误消息,因此当我们没有看到任何错误时,我们可以放心:

$ ./typevar3
(*a).b;
[EXP [ASVALUE [SELECT [DEREF [ASVALUE a]] b]]]
a*b;
[EXP [PROD [ASVALUE a] [ASVALUE b]]]
a**b;
[EXP [PROD [ASVALUE a] [DEREF [ASVALUE b]]]]
(a***b).c;
[EXP [ASVALUE [SELECT [PROD [ASVALUE a] [DEREF [DEREF [ASVALUE b]]]] c]]]
(a***).c;
[EXP [ASVALUE [SELECT [POINTER [POINTER [POINTER [ASTYPE a]]]] c]]]

现在,让我们假设我们想要添加一个数组类型构造函数,有点类似于C.我们将允许以下形式:

type[count]  // eg. int[3]

请注意,表单在语法上类似于数组索引操作(a [2]),我们也会将其添加到语言中。

这与select运算符略有不同。在select运算符的情况下,语法允许从中选择值或类型,并且不能预测结果。在数组构造/索引的情况下,结果的类别恰好是第一个参数的类别。

因为我们需要通过&#34;第一个参数的类别,我们需要三个产品,而不是括号产品:

known_value_postfix: known_value_postfix '[' value ']'
known_type_postfix : known_type_postfix '[' value ']'
unknown_postfix    : unknown_postfix '[' value ']'

在第三种情况下,我们需要插入&#34; construct_or_index&#34;节点进入AST。这可以通过稍后的单元生产解析为构造节点或索引节点,该单元生产将未知类别转换为某个特定类别,或者可以留给语义分析阶段。

添加这三个产品不会产生任何问题。但是,现在我们想添加一个构造可变大小数组的语法,我们将选择语法int[*],生成另一个不兼容的 * lexeme用法。该语法具有明确的结果类别,因为它不是有效的索引表达式,因此我们可以编写生成:

known_type_postfix : type_postfix '[' '*' ']'

这一次,我们选择在右侧使用便利的非终端。当然,这产生了大量新的语法冲突,包括shift-reduce和reduce-reduce,但语法仍然按照预期的方式工作:

var a: (int*)[*];
[DECL a [ARRAY [POINTER [TYPE int]]]]
var a: (int*)[2*2];        
[DECL a [ARRAY [POINTER [TYPE int]] [PROD [VALUE 2] [VALUE 2]]]]
a[2];
[EXP [ASVALUE [INDEX_OR_ARRAY a [VALUE 2]]]]
(*a)[2]; 
[EXP [INDEX [DEREF [ASVALUE a]] [VALUE 2]]]

作为练习留下:使用中缀 + 运算符来表示两个值的标量添加,以及两种类型的并集。请注意,您必须处理两个操作数中只有一个具有已知类别(因此必须将另一个操作数强制转换为同一类别)的情况,以及两个操作数都没有已知类别的情况。 / p>

注释

  1. bison实现的GLR解析器并不适用于此目的,因为它希望生成单个解析树。通过使用%merge声明(如手册中所述),可以使用bison生成的解析器手动创建解析林,但这不会实现其他GLR解析器可以生成的节省空间的图形表示。

  2. 野牛手册建议使用%expect-rr指令来做到这一点,但恕我直言,只有在语法准备就绪后才能完成。不幸的是,你只能压制已知的冲突数量,而不是压制特定的预期冲突;我想这是因为很难准确地描述单个预期的冲突,但最终结果是抑制冲突使得在开发语法时很容易遗漏问题。验证冲突是预期的冲突是令人讨厌的,但是比错过语法错误更不烦人。

  3. 包含编号作品的原始语法:

     1 root: code_statements
     2 code_statements: code_statement
     3                | code_statements code_statement
     4 code_statement: var_statement
     5               | expression_statement
     6 var_decl: IDENTIFIER ':' type_expression
     7 var_statement: VAR var_decl ';'
     8 expression_statement: value_expression ';'
     9 function_call: '(' ')'
    10              | '(' value_expression_list ')'
    11 value_expression_list: value_expression
    12                      | value_expression_list ',' value_expression
    13 unknown_expression: IDENTIFIER
    14                   | unknown_expression '.' IDENTIFIER
    15                   | postfix_known_value_expression '.' IDENTIFIER
    16                   | postfix_known_type_expression '.' IDENTIFIER
    17 primary_value_expression: NUMBER
    18                         | '(' value_expression ')'
    19 postfix_known_value_expression: primary_value_expression
    20                               | postfix_value_expression function_call
    21 postfix_value_expression: postfix_known_value_expression
    22                         | unknown_expression
    23 value_expression: postfix_value_expression
    24 primary_type_expression: INT
    25                        | '(' type_expression ')'
    26 postfix_known_type_expression: primary_type_expression
    27                              | postfix_type_expression '*'
    28 postfix_type_expression: postfix_known_type_expression
    29                        | unknown_expression
    30 type_expression: postfix_type_expression