灵活布局(Flex)如何准确支持Bison位置信息?

23

我正在尝试使用flex和bison创建一个过滤器,因为我想从一种复杂的语言中获取某些语法元素。我的计划是使用flex + bison来识别语法,并输出感兴趣的元素的位置。(然后使用脚本根据转储位置获取文本。)

我发现flex可以支持bison-locations的功能,但它如何工作还不是很清楚。我尝试了flex文档中的示例,似乎yylloc没有被flex自动设置,我总是得到(1,0)-(1,0)。flex能否自动计算每个标记的位置? 如果不能,有哪些接口函数是定义给我实现的? 有没有示例可供参考?

对于工具,有没有更好的解决方案?

最好的祝愿, Kevin

编辑:

现在yylex的接口已经变成:

int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );
bison 手册没有指定词法分析器应该如何实现以正确设置 yylloc_param。对我来说,手动跟踪每个标记的列号很困难。
8个回答

23

您可能因使用可重入或纯解析器而导致yylex声明发生更改。许多网站的文档似乎表明这是必需的,如果您想要使bison位置起作用,但实际上并非如此。

我也需要行号,并且在Bison文档中找到了令人困惑的部分。下面是简单的解决方案(使用全局变量yylloc):只需在Bison文件中添加%locations指令:

%{
...
%}
%locations
...
%%
...

在您的词法分析器中:

%{
...
#include "yourprser.tab.h"  /* This is where it gets the definition for yylloc from */
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%}
%option yylineno
...
%%
...

每次执行您的令牌操作之前都会使用YY_USER_ACTION宏并更新yylloc。
现在你可以像这样使用@N/@$规则:

statement : error ';'   { fprintf(stderr, "Line %d: Bad statement.\n", @1.first_line); }

或者使用yylloc全局变量:

void yyerror(char *s)
{
  fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s);
}

我认为这还不够。我尝试了这个方法,但始终会在构建时收到“yylloc未声明”的错误。一定还有其他步骤需要完成才能启用yylloc。 - Mike
1
你是否添加了%locations指令? 你是否在词法分析器中包含了已生成的.tab.h文件? 也许你正在使用非常旧的bison+flex版本? 对我来说,使用Bison 2.4.1和Flex 2.5.35可以工作。 - Shlomi Loubaton
在一个可重入的扫描器中,我不得不使用 yyget_lineno(scanner) 而不是仅仅使用 yylineno - barfuin

19

既不bison也不flex会自动更新yylloc,但是如果你知道诀窍,自己实现它其实并不难。

实现yylloc支持的诀窍是,即使yyparse()声明了yylloc,它从不更改它。这意味着如果您在调用词法分析器时修改yylloc,则在下一次调用中将找到相同的值。因此,yylloc将包含最后一个标记的位置。由于最后一个标记的结束与当前标记的开始相同,因此您可以使用旧的yylloc值来帮助确定新值。

换句话说,yylex()不应该计算yylloc;它应该更新 yylloc

要更新yylloc,我们必须首先将last_的值复制到first_,然后更新last_的值以反映刚匹配的标记的长度(这不是标记的strlen();而是行列长度)。我们可以在YY_USER_ACTION宏中执行此操作,在执行任何词法分析器动作之前调用它;这确保如果规则匹配但不返回值(例如,跳过空格或注释的规则),那么该非标记的位置将被跳过,而不是包含在实际标记的开头或以一种使位置跟踪不准确的方式丢失。

下面是一个适用于可重入解析器的版本;您可以通过将->运算符替换为.来修改它,以用于非可重入解析器:

#define YY_USER_ACTION \
    yylloc->first_line = yylloc->last_line; \
    yylloc->first_column = yylloc->last_column; \
    for(int i = 0; yytext[i] != '\0'; i++) { \
        if(yytext[i] == '\n') { \
            yylloc->last_line++; \
            yylloc->last_column = 0; \
        } \
        else { \
            yylloc->last_column++; \
        } \
    }

如果您希望的话,您可以将该代码放入函数中,并使宏调用该函数,但这两种技术是等效的。


2
比其他答案更有帮助,不过我建议创建一个函数并在宏中调用它 - static void update_loc(YYLTYPE *loc, char *txt) { loc->first_line = ...} / #define YY_USER_ACTION update_loc(yylloc, yytext);. - Kevin
不用担心位置的问题,事实证明我有一个匹配整行并将其“REJECT”的规则。 - Kevin
1
这绝对是最有帮助的答案,我认为应该被采纳。 - cqcallaw

16

我喜欢Shlomi的答案。

此外,我还想更新列位置。在阅读Shlomi的答案之后,我发现http://oreilly.com/linux/excerpts/9780596155971/error-reporting-recovery.html更加有意义。

不幸的是,该页面上的yylloc存在拼写错误。我在下面进行了简化。

在您的解析器中添加:

%locations

在你的词法分析器中:

%{

#include "parser.tab.h"

int yycolumn = 1;

#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno; \
    yylloc.first_column = yycolumn; yylloc.last_column = yycolumn + yyleng - 1; \
    yycolumn += yyleng; \
    yylval.str = strdup(yytext);

%}

%option yylineno

列位置可能存在某些问题,它并不严格跟踪列,而只是不断增加。这只是我的无知,如果有人感到困惑,我很抱歉。我目前使用列来保持文件字符计数,在我的情况下比列位置更有益。

希望这能帮到你。


2
列号不断增加的原因是因为您从未在换行时将其重置为1,Flex也不会这样做,因为它甚至不知道您的“yycolumn”变量。显然,您需要自己跟踪换行符,而不是依赖于“%option yylineno”。 - hugomg
yylval.str = strdup(yytext) 这句话不是设置Bison token的内容吗?如果每个token都是字符串,你才会希望这成为规则的默认操作,对吧? - Jeremy West
宏中的 yylval.str = strdup(yytext); 部分是否必要? - Andrei Bârsan
我认为 yylval.str = strdup(yytext); 不是必要的。 - januw a
由于我无法在5分钟后编辑我的评论,所以这只是为了说明我在写yy.lex.c时实际上是指lex.yy.c... - LHP
显示剩余2条评论

8
Shomi提供的解决方案是最简单的,如果您只关心保留行号。然而,如果您还想要列号,那么您需要追踪它们。
一种方法是在每个换行符处添加“yycolumn = 1”的规则(如David Elson的答案中建议的那样),但如果您不想跟踪换行符可能出现的所有位置(空格、注释等...),则可以在每个操作的开始检查“yytext”缓冲区作为另一种选择。
static void update_loc(){
  static int curr_line = 1;
  static int curr_col  = 1;

  yylloc.first_line   = curr_line;
  yylloc.first_column = curr_col;

  {char * s; for(s = yytext; *s != '\0'; s++){
    if(*s == '\n'){
      curr_line++;
      curr_col = 1;
    }else{
      curr_col++;
    }
  }}

  yylloc.last_line   = curr_line;
  yylloc.last_column = curr_col-1;
}

#define YY_USER_ACTION update_loc();

最后,需要注意的一点是,一旦您开始手动跟踪列号,您可以在同一位置同时跟踪行号,而不必使用Flex的yylineno选项。


8

请看Bison手册的第3.6节——它似乎对位置信息有详细介绍。结合您在Flex手册中找到的内容,这可能已经足够了。


我发现对我来说只有行号很重要。 - Kevin Yu

5

所以,我让这个“工作了”,但需要一些额外的步骤(我可能在这里忽略了它们……如果是这种情况,请原谅):

  1. In parser.y, I had to say:

    #define YYLEX_PARAM &yylval, &yylloc
    

    even with %locations and bison --locations, to get it to pass the data.

  2. In lexer.l I had to use -> instead of . for yylloc

  3. Also in lexer.l, I reset the column in the action:

    [\n] { yycolumn = 1; }
    
显然,对于\r等内容,会稍微复杂一些,但至少我让它正常工作了。

在命令行中添加--locations或在语法后面加上%locations,这意味着yylloc将在.y文件的范围内,只要您在最终的"%%"部分的代码中引用它。 - cardiff space man

2

在Shlomi的回答中还需要补充一点:

如果你正在使用bison中的%define api.pure来创建可重入解析器,你还需要在flex中指定%option bison-locations。这是因为在可重入解析器中,yylloc不是全局变量,需要传递给词法分析器。

因此,在解析器中:

%define api.pure
%locations

在词法分析器中:

#include "yourprser.tab.h"
#define YY_USER_ACTION yylloc.first_line = yylloc.last_line = yylineno;
%option bison-locations
%option yylineno

1

我认为我成功让它工作了(功劳归于bison手册作者ltcalc词法分析器)。默认情况下,bison创建包含yylloc的内容。

{ first_line, first_column , last_line , last_column }

我们只需要在词法分析器中更新这些值。例如:
[ \t]     { ++yylloc.last_column; }
[\n]      { yyloc.last_column = 0; return EOL; }
[a-zA-Z]+ { 
            yylloc.last_column += strlen(yytext);
            return IDENTIFIER;
          }

现在在Bison中,要检索这些字段:
statement : IDENTIFIER '=' expression 
            { printf("%d - %d\n", @1.last_line, @1.last_column); }

默认情况下,这些字段被初始化为1,我们应该将列字段初始化为0,否则它们会报告错误的列。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接