我正在尝试使用flex和bison来创建一个过滤器,因为我想从复杂的语言中获取某些语法元素。我的计划是使用flex + bison识别语法,并转出感兴趣元素的位置。 (然后使用脚本根据转储的位置获取文本。)
我发现flex可以支持一个叫做bison-locations的野牛功能,但它是如何工作的。我在flex文档中尝试了这个例子,似乎yylloc不是由flex自动设置的,我总是得到(1,0)-(1,0)
。可以自动灵活计算每个令牌的位置吗?如果没有,我定义了什么接口函数来实现?有什么例子吗?
有关工具的更好解决方案吗?
最诚挚的问候, 凯文
修改
现在yylex的界面转到:
int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );
bison手册未指定词法分析者应如何正确设置yylloc_param。对我来说,很难手动跟踪每个令牌的列号。
答案 0 :(得分:17)
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 global var:
void yyerror(char *s)
{
fprintf(stderr, "ERROR line %d: %s\n", yylloc.first_line, s);
}
答案 1 :(得分:12)
我喜欢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 :(得分:10)
bison
和flex
都不会自动更新yylloc
,但实际上并不难做到 - 如果你知道这个诀窍。
实施yylloc
支持的技巧是,即使yyparse()
声明yylloc
,它也永远不会改变它。这意味着如果您在对词法分析器的一次调用中修改yylloc
,则在下次调用时会在其中找到相同的值。因此,yylloc
将包含最后一个标记的位置。由于最后一个标记的结束与当前标记的开始相同,因此您可以使用旧的yylloc
值来帮助您确定新值。
换句话说,yylex()
不应计算 yylloc
;它应该更新 yylloc
。
要更新yylloc
,我们必须先将last_
值复制到first_
,然后更新last_
值以反映刚刚匹配的令牌的长度。 (这不是令牌的strlen()
;它是行和列的长度。)我们可以在YY_USER_ACTION
宏中执行此操作,该宏在执行任何词法分析器操作之前调用;确保如果规则匹配但不返回值(例如,跳过空格或注释的规则),则跳过该非令牌的位置,而不是包含在实际令牌的开头,或者丢失的方式使位置跟踪不准确。
这是一个针对可重入解析器的版本;您可以通过交换->
的{{1}}运算符来修改非重入解析器:
.
如果您愿意,可以将该代码放入函数中并使宏调用函数,但这两种技术是等效的。
答案 3 :(得分:7)
看一下3.6 of the Bison manual部分 - 这似乎涵盖了一些细节。结合您在Flex手册中找到的内容,这可能就足够了。
答案 4 :(得分:6)
如果您只关心保留行号,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
选项。< / p>
答案 5 :(得分:4)
所以,我得到了这个“工作”,但有几个额外的步骤(我可能在这里忽略了它们......在这种情况下道歉):
在 parser.y 中,我不得不说:
#define YYLEX_PARAM &yylval, &yylloc
即使使用%locations
和bison --locations
,也可以让它传递数据。
在 lexer.l 中,我必须使用->
代替.
yylloc
同样在 lexer.l 中,我重置了操作中的列:
[\n] { yycolumn = 1; }
对于\r
等,显然有点复杂,但至少我让它起作用。
答案 6 :(得分:1)
我认为我设法让它发挥作用(归功于野牛手册ltcalc lexical analyzer的作者)。 默认情况下,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;
}
现在在野外,检索这些字段:
statement : IDENTIFIER '=' expression
{ printf("%d - %d\n", @1.last_line, @1.last_column); }
默认情况下,这些字段初始化为1,我们应该将列字段初始化为零,否则会报告错误的列。
答案 7 :(得分:0)
Shlomi回答的补充:
如果你在野牛中使用%define api.pure来创建一个可重入的解析器,你还需要在flex中指定%option bison-locations。这是因为在重入解析器中,yylloc不是全局变量,需要传递给词法分析器。
所以,在解析器中:
public Observable<List<Item>> items() {
List<Observable> obs = new ArrayList<>();
Observable<TrustedItemList> memoryObs = Observable.defer(this::getMemoryItems);
Observable<TrustedItemList> diskObs = Observable.defer(this::getDiskItems);
Observable<TrustedItemList> networkObs = Observable.defer(this::getNetworkItems);
Observable<TrustedItemList> concat = Observable.concat(memoryObs, diskObs, networkObs;
return concat.takeWhile(this::shouldContinueSearching)
.filter(trustedItemList -> trustedItemList.items != null && !trustedItemList.items.isEmpty())
.map(trustedItemList -> trustedItemList.items);
}
private boolean shouldContinueSearching(TrustedPoiList data) {
return data == null || data.items == null || data.items.isEmpty() || !data.canTrustIfNotEmpty;
}
词法分析器:
%define api.pure
%locations