Parsing bison/yacc中的特定错误恢复
我正在阅读Kenneth Louden的《编译器构造、原理和实践》一书,并试图理解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 :
%{
#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:很抱歉我完全误读了你的问题。我写了一个新的答案,可能会更有帮助,但笼统的答案是“这是一门艺术,不是一门科学。”(答案中包含了这一点,但可能不够强调。)