【问题标题】:How to know what line will be printed in yylineno?如何知道 yylineno 会打印哪一行?
【发布时间】:2017-01-08 16:53:54
【问题描述】:

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

Statement : Type ID ASSIGN Exp {check_types_match($1.type, $4.type)} SC
  • 类型可以是 intboolean
  • ID 是一个标识符。
  • ASSIGN 是 = 符号。
  • Exp可以是很多东西,其中有Exp : true,它将表达式的类型保存为布尔值。
  • SC 是一个符号“;”。
  • check_types_match 检查类型不匹配并打印错误所在的行 (yylineno)(如果有)。

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

int x = true 
;

我知道错误在第 2 行而不是在第 1 行。我怎样才能让它在第 1 行打印错误?

【问题讨论】:

    标签: compiler-construction bison lex


    【解决方案1】:

    直到您到达第 2 行的分号,该语句才会被识别。因此,在调用 check_types_match 的那一刻,yylineno 必须指向第 2 行。

    如果您想生成带有不同行号的错误消息,您当然需要决定应该打印哪一行。在这里,您至少有两种可能性,因为错误位于令牌 int 和令牌 true 之间。在这种情况下,这两个都在第 1 行,但如果程序文本是:

    int x =
      true;
    

    应该将其中一个标记标记为导致错误似乎是合理的,因此问题归结为找出标记出现在哪一行。由于在还原发生时该标记已成为古老历史,因此唯一的方法是记住可能仍需要的每个标记的位置,通常是仍在解析器堆栈上的每个标记。

    幸运的是,bison 有一个简单的方法来做到这一点。如果需要,它将维护一个与解析器堆栈平行的位置堆栈,然后您可以通过简单地引用 @1 来访问令牌 1 的位置对象。更好的是,只需在野牛文件中的某处使用对位置对象的引用就足以说服野牛维护此信息。因此,您可以将操作更改为:

    Statement : Type ID ASSIGN Exp {check_types_match($1.type, $4.type, @1)} SC
    

    (或@4,如果你认为将错误归咎于Exp更合适。)

    当然,事情从来没有这么简单。还需要安排bison知道每个传入token的位置,还要了解如何为新创建的非终端(如上例中的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 没有“语法符号的数量”的任何表示法,并且不允许在$<i>N</i> 结构中使用变量,因此您实际上不能这样写。但就是这样。)

    既然所有这些都是你想要的,那么野牛这边没有问题。但您还需要首先从flex 获取信息。

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

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

    yylloc.first_line = yylloc.last_line = yylineno;
    

    如果您确实有多行标记,则可以改用以下内容:

    yylloc.first_line = yylloc.last_line;
    yylloc.last_line = yylineno;
    

    必须将其添加到 每个 标记操作,即使是不执行任何操作的操作(cmets 和空格)。幸运的是,flex 在每个动作的开头添加了一个宏,因此您不必使整个 flex 文件复杂化。添加如下内容就足够了:

    #define YY_USER_ACTION do {             \
      yylloc.first_line = yylloc.last_line; \
      yylloc.last_line = yylineno;          \
    } while(0)
    

    (如果您最终也跟踪列号,则需要对其进行修改。)

    还需要确保yylloc.last_line被初始化为1;否则,您的第一个令牌将从第 0 行开始。

    更多信息,请阅读手册:

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

    【讨论】:

    • 感谢您的详细回复!它帮助了我 :) 干杯!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-09
    • 1970-01-01
    • 2019-08-09
    • 2016-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多