如何知道 yylineno 中将打印哪一行?

How to know what line will be printed in yylineno?

我正在使用 Lex 和 Bison 解析器生成器。 我有定义语法的 .lex 文件和定义语义的 .ypp 文件。在我的 .ypp 中,我有这一行:

Statement : Type ID ASSIGN Exp {check_types_match(.type, .type)} SC

在这个简单的输入文件中:

int x = true 
;

我知道错误在第 2 行而不是在第 1 行。我怎样才能让它在第 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(.type, .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 没有 "the number of grammar symbols" 的任何符号并且不允许 $<i>N</i> 结构中的变量,你实际上不能这么写。但这就是想法。)

既然这一切都很好,那么bison这边就没有问题了。但是你还需要首先从 flex 获取信息。

如果使用flexbison之间的简单接口,依赖全局变量,则当前token对应的location对象名称为yylloc(类似yylval)。 flex 可以自动创建 yylineno 但它不会自动将其存储在 yylloc 中,也没有任何内置机制来跟踪列号或处理返回的令牌是分布在不止一行(例如,对于字符串常量,这可能是可能的)。

正确设置所有基础设施有点超出了这个问题的范围,因为您只需要行号信息。如果您只需要跟踪行号并且没有多行标记,则将以下内容添加到每个 flex 规则就足够了:

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被初始化为1;否则,您的第一个标记将从第 0 行开始。

更多信息,请阅读手册:

如果您正在使用 reentrant/pure 扫描器和解析器,您需要参考文档了解如何在没有全局变量的情况下传递位置对象。请注意,%bison-locations 声明并不总是您想要的(如果您不使用 reentrant/pure 扫描器和解析器,则绝对不是您想要的。)