Parsing bison/yacc中的特定错误恢复

Parsing bison/yacc中的特定错误恢复,parsing,yacc,Parsing,Yacc,我正在阅读Kenneth Louden的《编译器构造、原理和实践》一书,并试图理解Yacc中的错误恢复 作者使用以下语法给出了一个示例: %{ #include <stdio.h> #include <ctype.h> int yylex(); int yyerror(); %} %% command : exp { printf("%d\n", $1); } ; /* allows printing of the result */ exp :

我正在阅读Kenneth Louden的《编译器构造、原理和实践》一书,并试图理解Yacc中的错误恢复

作者使用以下语法给出了一个示例:

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

int yylex();
int yyerror();
%}

%%

command : exp { printf("%d\n", $1); }
        ; /* allows printing of the result */

exp : exp '+' term { $$ = $1 + $3; }
    | exp '-' term { $$ = $1 - $3; }
    | term { $$ = $1; }
    ;

term : term '*' factor { $$ = $1 * $3; }
     | factor { $$ = $1; }
     ;

factor : NUMBER { $$ = $1; }
       | '(' exp ')' { $$ = $2; }
       ;

%%

int main() {
  return yyparse();
}

int yylex() {
  int c;

  /* eliminate blanks*/
  while((c = getchar()) == ' ');

  if (isdigit(c)) {
    ungetc(c, stdin);
    scanf("%d\n", &yylval);
    return (NUMBER);
  }

  /* makes the parse stop */
  if (c == '\n') return 0;

  return (c);
}

int yyerror(char * s) {
  fprintf(stderr, "%s\n", s);
  return 0;
} /* allows for printing of an error message */
然后劳登博士给出了以下例子:

考虑一下,如果将错误产品添加到 yacc定义

factor : NUMBER {$$ = $1;}
       | '(' exp ')' {$$=$2;}
       | error {$$ = 0;}
       ;
考虑前一个示例中的第一个错误输入2++3(我们继续使用表5.11,尽管额外的错误生成会导致一个稍微不同的表。)与前面一样,解析器将 达到以下几点:

parsing stack         input
$0 exp 2 + 7          +3$
parsing stack                 input
$0 exp 2 + 7 factor 4         +3$
现在,
因子
的误差产生将提供误差是一个 状态7中的法律前瞻和错误将立即转移 添加到堆栈上,并减少到
因子
,导致值0为 返回。现在,解析器已达到以下点:

parsing stack         input
$0 exp 2 + 7          +3$
parsing stack                 input
$0 exp 2 + 7 factor 4         +3$
这是正常情况,解析器将继续执行 通常到最后。其效果是将输入解释为2+0+3 -两个+符号之间存在0,因为这是插入错误伪标记的位置,并且是由错误操作插入的 生产,错误被视为等同于具有值的因子 0

我的问题很简单:

通过查看语法,他是如何知道为了从这个特定错误(2++3)中恢复,他需要向
因子
产品中添加一个错误伪标记的?有简单的方法吗?或者,唯一的方法是使用状态表计算出多个示例,并认识到该特定错误将在该给定状态下发生,因此,如果我将错误伪标记添加到某个特定产品中,则错误将得到修复


非常感谢您的帮助。

在这种简单的语法中,您只有很少的错误生成选项,所有选项都将允许继续解析

在这种情况下,选择派生树底部的一个是有意义的,但这不是一个通用的启发式方法。将错误生成放在派生树的顶部更为有用,在那里它们可以用于重新同步解析。例如,假设我们修改了语法以允许多个表达式,每个表达式都在自己的行上:(这需要修改
yylex
,以便在看到
\n
时不会伪造EOF):

现在,如果我们只想忽略错误并继续解析,我们可以添加一个重新同步错误生成:

       | program error '\n'
上面的“\n”终端将导致跳过令牌,直到可以移动换行符以减少错误产生,以便可以继续分析下一行

不过,并不是所有的语言都那么容易重新同步。类C语言中的语句不一定以
结尾,如果错误是(例如)丢失的
}
,则如上所述重新同步的天真尝试将导致一定程度的混乱。然而,它将允许解析以某种方式继续,这可能就足够了

根据我的经验,正确地制作错误通常需要大量的尝试和错误;与其说它是一门科学,不如说它是一门艺术。尝试大量错误输入并分析错误恢复将有所帮助

产生错误的关键是从错误中恢复。生成良好的错误消息是一个不相关但同样具有挑战性的问题。当解析器尝试进行错误恢复时,错误消息已经发送到
yyerror
。(当然,该函数可以忽略错误消息,并将其留给错误产品来打印错误,但没有明显的理由这样做。)


产生好的错误消息的一种可能策略是对解析器堆栈和lookahead令牌进行某种形式的表查找(或计算)。实际上,bison的内置扩展错误处理就是这样做的,并且通常会产生相当合理的结果,所以这是一个很好的起点。已经探讨了替代战略。一个很好的参考是克林顿·杰弗里2003年的论文;您还可以查看他是如何将这个想法应用到Go编译器的。

谢谢您的回复。但我还是不清楚。让我重新措辞这个问题。假设我有一个任务来解决这个特殊的问题,即处理2++3的输入,或者更一般地说是数字+数字。通过查看语法,我如何知道我需要在
因子
产品中插入
错误
伪标记?@flashburn:很抱歉我完全误读了你的问题。我写了一个新的答案,可能会更有帮助,但笼统的答案是“这是一门艺术,不是一门科学。”(答案中包含了这一点,但可能不够强调。)