我正在使用Lex和Bison解析器生成器。 我有我的.lex文件,它定义了语法和定义语义的.ypp文件。在我的.ypp中我有这句话:
Statement : Type ID ASSIGN Exp {check_types_match($1.type, $4.type)} SC
在这个简单的输入文件中:
int x = true
;
我知道错误在第2行而不在第1行。如何让它在第1行中打印错误?
答案 0 :(得分:1)
在您到达第2行的分号之前,语句不会被识别。因此,在调用check_types_match
时,yylineno
必须指向第2行。
如果要生成具有不同行号的错误消息,您当然需要确定应打印哪行。在这里,您至少有两种可能性,因为错误位于令牌int
和令牌true
之间。在这种情况下,这两个都在第1行,但如果程序文本是:
int x =
true;
这些令牌中的一个应该被标记为导致错误似乎是合理的,因此问题减少到找出令牌出现在哪一行上。由于该令牌在减少发生时是古老的历史,因此唯一的方法是记住可能仍然需要的每个令牌的位置,通常每个令牌仍然在解析器堆栈上。
幸运的是,bison
有一种简单的方法。如果需要,它将维护与解析器堆栈并行的位置堆栈,然后您只需引用@1
即可访问令牌1的位置对象。更好的是,只需在bison文件中的某处使用对位置对象的引用即可说服bison维护此信息。所以你可以将你的行动改为:
Statement : Type ID ASSIGN Exp {check_types_match($1.type, $4.type, @1)} SC
(或@4
,如果您认为将错误归咎于Exp
更合适。
当然,它从未如此简单。还需要安排bison
知道每个传入令牌的位置,并了解如何为新创建的非终端创建位置(例如上例中的Exp
)。 )
由于位置对象可以指代令牌序列的位置(如在非终端的情况下),其可以分布在多条线上,因此位置对象指示起点和终点是正常的。 。此外,通常需要行号和列偏移来产生准确的错误消息。因此,默认位置对象具有以下类型:
typedef struct YYLTYPE {
int first_line;
int first_column;
int last_line;
int last_column;
} YYLTYPE;
默认情况下,计算非终端的位置对象,就像编写了类似
的内容一样@$.first_line = @1.first_line;
@$.first_column = @1.first_column;
@$.last_line = @N.last_line;
@$.last_column = @N.last_column;
其中N
是右侧最后一个语法符号的索引。 (由于bison
没有任何符号来表示"语法符号的数量"并且不允许$N
结构中的变量,您不能实际上写的是。但这就是想法。)
由于所有这些都非常符合你的要求,因此野牛的方面没有问题。但您还需要首先从flex
获取信息。
如果使用依赖于全局变量的flex
和bison
之间的简单接口,则与当前标记对应的位置对象的名称为yylloc
(类似于{ {1}})。 yylval
可以自动创建flex
,但它不会自动将其存储在yylineno
中,也没有任何内置机制来跟踪列号,也不处理返回令牌的情况分布在多条线上(例如,对于字符串常量,这可能是可能的)。
使所有基础设施正确有点超出了本问题的范围,因为您只询问行号信息。如果您只需要跟踪行号并且您没有多行令牌,那么将以下内容添加到每个flex规则就足够了:
yylloc
如果您有多行令牌,则可以使用以下代码:
yylloc.first_line = yylloc.last_line = yylineno;
这必须添加到每个令牌操作中,即使是那些不做任何事情的操作(评论和空白)。幸运的是,yylloc.first_line = yylloc.last_line;
yylloc.last_line = yylineno;
有一个在每个操作开始时添加的宏,因此您不必使整个Flex文件复杂化。添加以下内容就足够了:
flex
(如果您最终也跟踪列号,则需要对其进行修改。)
您还需要确保将#define YY_USER_ACTION do { \
yylloc.first_line = yylloc.last_line; \
yylloc.last_line = yylineno; \
} while(0)
初始化为yylloc.last_line
;否则,您的第一个令牌将从第0行开始。
有关详细信息,请阅读手册:
如果您使用的是可重入/纯扫描程序和解析器,那么您需要参考文档,了解如何在没有全局变量的情况下传递位置对象。请注意,1
声明并不总是您想要的(如果您不使用可重入/纯扫描程序和解析器,则绝对不是您想要的。)