在yacc中获取行号的方法

时间:2015-09-09 06:38:10

标签: bison yacc

在yacc中获取行号的不同方法有哪些。我知道yylineno但是当yacc读取前瞻标记时,它可以被1关闭。

我在yacc中使用yytext时遇到了同样的问题。它没有给出预期的令牌。我在lex中使用了yylval.str来完成它。是否有类似的东西得到确切的行号。

Bison是否有更好的选择,可以使处理更容易和准确

1 个答案:

答案 0 :(得分:1)

扫描程序的作用是生成一系列词法标记,每个标记标记具有类型,语义值和可能的位置。解析器接收此令牌流,并将它们处理为输入的结构表示。

应该很清楚,在解析器中,"扫描的最后一个语义值"很少或没有价值,特别是因为解析器通常需要让扫描仪向前看至少一个令牌以决定如何继续。但更一般地说,解析器操作将组合关于一系列令牌(以及一系列已经减少的产品)的信息,因此没有"一个值"这在行动中是有意义的。

类似地,每个标记在输入中都有一个位置,如果解析器需要将位置与语法特征相关联,则它需要能够引用任何给定标记的位置。

Bison通过允许扫描程序填写location object以及语义值(yylval)来促进此过程。 location对象称为yylloc,与语义值不同,它通常与每个令牌的类型相同。如果您的Bison源使用位置,它将创建一个与语义值堆栈同步的位置对象堆栈。在规则中,令牌(或非终端)的语义值可以称为$1$2等;同样,令牌/非终端的位置将为@1@2,...

您不需要告诉Bison收集位置信息。如果您只是在任何解析器操作中使用某个位置引用(@n),它将自动发生。

事实上,您不需要在解析器中做很多事情来使用位置信息,因为默认值通常就足够了。除非#define预处理器宏YYLTYPE,否则名为YYLTYPE的位置对象类型将声明如下:

typedef struct YYLTYPE {
  int first_line;
  int first_column;
  int last_line;
  int last_column;
} YYLTYPE;

声明将被放入生成的头文件中,以便扫描仪也可以使用它。 YYLTYPE结构反映了这样一个事实:令牌 - 更重要的是非终端 - 跨越源文件中的一系列位置。

非终端的位置结构也将由生成的解析器自动填充,尽管您可以通过在解析器操作中分配@$来自由修改它。默认设置是从first_line获取first_column@1字段,从last_line获取last_column@n字段,其中 n 是右侧的符号数。换句话说,当您减少生产时,生成的位置将跨越代表生产标记的所有源文本。

虽然yylloc包含行和列信息,但您不需要使用列数据。将这些字段设置为0是最方便的,以防您想要在解析器的某个更高版本中使用它们;您可以通过重新定义YYLTYPE来减少位置堆栈的开销,但是您还需要覆盖默认位置操作,因为它引用了那些命名字段。

填写yylloc对象完全是您的扫描仪的责任,并且不幸的是,flex不会对您有所帮助。如果您要求(yylineno),Flex会保留%option yylineno,但它不会填充yylloc,因此您需要自己执行此操作。幸运的是,flex允许您定义YY_USER_ACTION宏。此宏在每个 flex操作的开头插入,可用于将位置信息复制到yylloc

举一个简单的例子,如果你的任何代币都没有超过一行(或者你不关心跨越多行的代币的起始行),你可以简单地把它放在序言中您的弹性定义:

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;

并使用

启用yylineno跟踪
%option yylineno

一旦您完成了这项工作,并且对您的弹性或野牛定义没有其他更改,您就可以编写以下操作:

assignment: IDENTIFIER '=' expression {
               printf("%s is defined at line %d\n", $1, @1.first_line);
            }

请注意,由于上述规则引用了IDENTIFIER令牌的位置,因此expression消耗了多少行并不重要。您可以更准确地使用@$的默认设置:

assignment: IDENTIFIER '=' expression {
               printf("%s is defined in lines %d to %d\n",
                      $1, @$.first_line, @$.last_line);
            }