Bison:有效表达式的GLR解析失败,没有错误消息

时间:2018-06-19 10:18:59

标签: parsing bison cobol glr

我正在使用GNU Bison中的GLR解析器,我遇到以下问题:

我试图解析的语言允许布尔表达式包括关系(<,>,< =,...)和布尔组合(和,或,不)。现在的问题是该语言还允许在关系的右侧有多个算术表达式......并且它们使用与布尔组合相同的AND标记组成!这是一种非常愚蠢的语言设计,但我无法改变它。

所以你可以a > b and c(a > b) and (a > c)等效,你也可以a > b and c > d,它应该等同于(a > b) and (c > d)

在此示例中,这导致的S / R冲突已经很明显:在使用lookahead a > b读取and之后,您可以将a > b缩减为布尔表达式并等待另一个布尔值表达式,或者您可以移动and并等待另一个算术表达式。

我的语法目前看起来像这样:

booleanexpression
    : relation
    | booleanexpression TOK_AND booleanexpression
    ...
;
relation
    : arithmeticexpression TOK_GT maxtree
    ...
;
maxtree
    : arithmeticexpression
    | maxtree TOK_AND maxtree
    ...
;

对于任何 k ,语言显然不是 LR(k),因为不能使用任何常量 k来解析S / R冲突 -lookahead,因为它们之间的算术表达可以有任意多个令牌。因此,我开启了GLR解析。

但是当我尝试用这个解析a > b and c时,我可以在调试输出中看到解析器的行为如下:

  • 它会读取a并在前瞻>时将a缩减为arithmeticexpression
  • 它会读取b,并且在前瞻and时会将b缩小为arithmeticexpression,然后已经缩减为maxtree
  • 它将a > b缩减为relation
  • 它会读取c并将其缩减为arithmeticexpression
然后没有任何反应! and c显然已被丢弃 - 调试输出不会显示这些令牌的任何操作。甚至没有错误消息。相应的if语句在我的AST中不存在(我仍然得到一个AST,因为我有错误恢复)。

我认为,在阅读b后,应该有2个堆栈。但是b不应该减少。或者至少它应该给我一些错误信息("语言含糊不清"没关系,我之前看过那条消息 - 我不明白为什么它不适用于此处)。任何人都可以理解这个吗?

从语法上看一段时间,你可以看出这里的主要问题是在下一次算术表达之后是否会出现

  • 另一个关系令牌(然后你应该减少)
  • 另一个布尔组合(然后你应该转移)
  • 布尔/算术 - 表达语法之外的一个标记(如THEN),它会终止表达式,你也应该转移

您能想到以更好/更确定的方式捕捉情况的不同语法吗?你会如何解决这个问题?我目前正在考虑让语法更加从右向左,比如

booleanexpression : relation AND booleanexpression
maxtree : arithmeticexpression AND maxtree
etc.

我认为这会使野牛更喜欢转移,而只是先减少右侧。也许通过使用不同的非终端,它将允许准 - "前瞻"在arithmeticexpression背后......

附注:GnuCOBOL通过收集所有令牌,将它们推送到中间堆栈并从那里手动构建表达式来处理此问题。这让我气馁,但我坚持希望他们这样做,因为野牛在他们开始时并不支持GLR解析...

编辑: 一个可重复的小例子

%{
#include <stdio.h>
int yylex ();
void yyerror(const char* msg);
%}

%glr-parser
%left '&'
%left '>'

%%
input: %empty | input bool '\n' {printf("\n");};

arith : 'a' | 'b' | 'c';
maxtree : arith { printf("[maxtree : arith]  "); }
        | maxtree '&' maxtree { printf("[maxtree : maxtree & maxtree]  "); } ;
rel : arith '>' maxtree { printf("[rel : arith > maxtree]  "); } ;
bool : rel { printf("[bool : rel]  "); }
     | bool '&' bool { printf("[bool : bool & bool]  "); } ;
%%

void yyerror(const char* msg) { printf("%s\n", msg); }
int yylex () {
    int c;
    while ((c = getchar ()) == ' ' || c == '\t');
    return c == EOF ? 0 : c;
}
int main (int argc, char** argv) {
    return yyparse();
}

这个奇怪地打印错误信息&#34;语法错误&#34;输入a>b&c

3 个答案:

答案 0 :(得分:2)

通过使用优先级声明来简化语法确实很方便(有时)[注1],但在使用GLR解析器时效果不佳,因为它可能导致早期拒绝明确的解析。

优先级声明背后的思想是,它们使用简单的单令牌前瞻和在可能的减少与可能的移位之间配置的优先级来解决歧义(或更准确地说,消除/减少冲突)。如果语法没有移位/减少冲突,则不会使用优先级声明,但是如果使用它们,则将根据(静态)优先级关系使用抑制或抑制移位。

由Bison生成的GLR解析器实际上并不能解决歧义,但是它允许继续开发可能不正确的解析,直到语法解决歧义为止。与使用优先级不同,这是延迟的解决方案。慢一点,但功能强大得多。 (GLR解析器可以产生一个包含所有可能解析的“解析森林”。但是Bison并没有实现此功能,因为它希望解析编程语言,并且与人类语言不同,编程语言不能是模棱两可的。)

用您的语言,无法静态解决转移/减少冲突的不确定性,就像您在问题中所指出的那样。您的语法根本不是LR(1),运算符的优先级要低得多,因此GLR解析是一种可行的解决方案。但是您必须允许GLR进行其工作。通过优先级比较过早地消除一个可能的解析,将阻止GLR算法在以后考虑它。如果您设法消除唯一可能正确的解析,这将尤其严重。

在您的语法中,不可能定义rel产生式和&符号之间的优先级关系,因为不存在任何优先级关系。在某些句子中,rel减少需要获胜;换句话说,换班应该获胜。由于语法不是模棱两可的,只要允许同时进行平移和归约,GLR最终将找出哪个是正确的。

在您的全部语言中,布尔表达式和算术表达式都具有类似于运算符优先级的内容,但仅在它们各自的域内。运算符优先级解析器(以及yacc / bison的优先级声明等效)通过擦除不同非终结符之间的差异来工作。它无法处理像您这样的语法,其中某些运算符在不同的域(或不同的域之间)具有不同的优先级。

幸运的是,对优先级声明的这种特殊使用只是一种捷径;它不会为语法提供任何额外的功能,并且可以通过创建新的非终结符(每个优先级各一个)来轻松而机械地实现。替代语法将不会模棱两可。您可以在任何包含解析算术表达式的教科书或教程中找到的经典示例就是expr / term / factor语法。在这里,我还提供了优先级语法进行比较:

                              %left '+' '-'
                              %left '*' '/'
%%                            %%
expr  : term
      | expr '+' term         expr: expr '+' expr
      | expr '-' term             | expr '-' expr
term  : factor
      | term '*' factor           | expr '*' expr
      | term '/' factor           | expr '/' expr
factor: ID                        | ID
      | '(' expr ')'              | '(' expr ')'

在您的最小示例中,已经有足够多的非终端设备,不需要发明新的终端设备,所以我只是根据上述模型重写了它。

我已在编写动作时留下了动作,以防样式对您有用。请注意,这种样式像筛子一样会泄漏内存,但是可以进行快速测试:

%code top {
#define _GNU_SOURCE 1
}

%{
#include <ctype.h>
#include <stdio.h>
#include <string.h>

int yylex(void);
void yyerror(const char* msg);
%}

%define api.value.type { char* }
%glr-parser
%token ID

%%
input   : %empty
        | input bool '\n'   { puts($2); }

arith   : ID
maxtree : arith 
        | maxtree '&' arith { asprintf(&$$, "[maxtree& %s %s]", $1, $3); }
rel     : arith '>' maxtree { asprintf(&$$, "[COMP %s %s]", $1, $3); }
bool    : rel
        | bool '&' rel      { asprintf(&$$, "[AND %s %s]", $1, $3); }
%%

void yyerror(const char* msg) { printf("%s\n", msg); }
int yylex(void) {
    int c;
    while ((c = getchar ()) == ' ' || c == '\t');
    if (isalpha(c)) {
      *(yylval = strdup(" ")) = c;
      return ID;
    }
    else return c == EOF ? 0 : c;
}

int main (int argc, char** argv) {
#if YYDEBUG
    if (argc > 1 && strncmp(argv[1], "-d", 2) == 0) yydebug = 1;
#endif
    return yyparse();
}

这里是一个示例运行。请注意野牛发出的有关减少/减少冲突的警告。如果没有这样的警告,则GLR解析器可能是不必要的,因为没有冲突的语法是确定性的。 (另一方面,由于bison的GLR实现针对确定性进行了优化,因此在确定性语言上使用GLR解析器不会花费太多成本。)

$ bison -t -o glr_prec.c glr_prec.y
glr_prec.y: warning: 1 shift/reduce conflict [-Wconflicts-sr]
$ gcc -Wall -o glr_prec glr_prec.c
$ ./glr_prec
a>b
[COMP a b]
a>b & c
[COMP a [maxtree& b c]]
a>b & c>d
[AND [COMP a b] [COMP c d]]
a>b & c & c>d
[AND [COMP a [maxtree& b c]] [COMP c d]]
a>b & c>d & e
[AND [COMP a b] [COMP c [maxtree& d e]]]
$

注释

  1. 虽然优先声明在您了解实际情况时很方便,但人们有很大的倾向只是将他们从互联网上发现的其他某种语法中挑剔出来,而不是很少使用的语法货源来自其他地方。当优先声明不能按预期工作时,下一步是随机修改它们,以期找到有效的配置。有时这种方法会成功,通常会留下不必要的碎屑,这些碎屑会再次被运用于货色。

    因此,尽管在某些情况下优先级声明确实可以简化语法,并且明确的实现会复杂得多(例如,具有多种不同复合语句类型的语言中的悬挂-其他解析),但我仍然发现我自己建议不要使用它们。

    a recent answer to a different question中,我写了我希望对优先级算法有一个好的解释(如果不是,请让我知道它的不足)。

答案 1 :(得分:2)

欢迎来到COBOL的美好世界。我可能是错的,但您可能有几个 这里还有其他问题。 COBOL中的A > B AND C这样的表达式是不明确的 直到您知道如何声明C。考虑以下程序:

   IDENTIFICATION DIVISION.
   PROGRAM-ID EXAMPLE.
   DATA DIVISION.
   WORKING-STORAGE SECTION.
   01     A     PIC 9 VALUE 2.
   01     B     PIC 9 VALUE 1.
   01     W     PIC 9 VALUE 3.
       88 C           VALUE 3.
   PROCEDURE DIVISION.
       IF A > B AND C
          DISPLAY 'A > B AND 88 LEVEL C is TRUE because W = ' W
       ELSE
          DISPLAY 'A not > B or 88 LEVEL C is not TRUE'
       END-IF
       DISPLAY 'A: ' A ' B: ' B ' W:' W  
       GOBACK
       .

此程序的输出为:

A > B AND 88 LEVEL C is TRUE because W = 3
A: 2 B: 1 W: 3

本质上,表达式A > B AND C等同于A > B AND W = 3。有C 以类似于A和B的方式定义,语义将 一直为:A > B AND A > C,在这种情况下为FALSE。

答案 2 :(得分:0)

上面提到的代码很好用,但是即使我看不出我的真实项目与该代码之间的区别,我也从未在实际项目中使用它。

这让我发疯,但是我刚刚在代码中发现了另一个问题,这使该方法无法正常工作: 我在序言中有一个(固执地教养的)%skeleton "lalr1.cc",这再次禁用了GLR解析! 我需要用

替换它
%skeleton "glr.cc"