我正在尝试使用flex和bison来创建一个过滤器,因为我想从复杂的语言中获取某些语法元素.我的计划是使用flex + bison来识别语法,并转出感兴趣的元素的位置.(然后使用脚本根据转储的位置获取文本.)
我发现flex可以支持一个叫做bison-locations的野牛功能,但它是如何工作的.我在flex文档中尝试了这个例子,似乎yylloc不是由flex自动设置的,我总是得到(1,0)-(1,0)
.可以自动灵活计算每个令牌的位置吗?如果没有,我定义了什么接口函数来实现?有什么例子吗?
有关工具的更好解决方案吗?
最诚挚的问候,凯文
编辑:
现在,yylex的界面转向:
int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );
bison手册没有说明lexer应如何实现正确设置yylloc_param.对我来说,很难手动跟踪每个令牌的列号.
yylex声明可能已更改,因为您使用了可重入或纯解析器.似乎网络上的许多文档表明,如果您希望野牛位置有效,则需要它,但这不是必需的.
我也需要行号,并发现Bison文档在这方面令人困惑.简单的解决方案(使用全局var 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); }
我喜欢Shlomi的回答.
此外,我也在寻找更新列位置.找到了http://oreilly.com/linux/excerpts/9780596155971/error-reporting-recovery.html,在阅读了Shlomi的答案后更有意义.
不幸的是,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
列位置可能会发生一些事情,它不会严格跟踪列,而只是不断增加.如果它让任何人感到困惑,那只是我的无知和道歉.我目前正在使用列来保存文件字符数,在我的情况下,这比列位置更有利.
希望有所帮助.
既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++; \ } \ }
如果您愿意,可以将该代码放在函数中并使宏调用函数,但这两种技术是等效的.
看一下Bison手册的第3.6节- 它似乎详细地介绍了位置.结合您在Flex手册中找到的内容,这可能就足够了.
如果您只关心保留行号,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
选项.