【问题标题】:How does flex support bison-location exactly?flex 究竟是如何支持野牛定位的?
【发布时间】:2010-10-13 23:46:33
【问题描述】:

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

我发现 flex 可以支持名为 bison-locations 的野牛功能,但它是如何工作的。我尝试了 flex 文档中的示例,似乎 yylloc 不是由 flex 自动设置的,我总是得到(1,0)-(1,0)。 flex 可以自动计算每个令牌的位置吗?如果没有,定义了哪些接口函数供我实现?有例子吗?

关于工具有更好的解决方案吗?

最好的问候, 凯文

编辑:

现在yylex的界面变成了:

int yylex(YYSTYPE * yylval_param,YYLTYPE * yylloc_param );

bison 手册未指定词法分析器应如何实现以正确设置 yylloc_param。对我来说,很难手动跟踪每个标记的列号。

【问题讨论】:

    标签: bison flex-lexer


    【解决方案1】:

    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);
    }
    

    【讨论】:

    • 我认为这还不够。我已经尝试过了,当我尝试构建时,总是得到一个“yylloc”未声明的错误。要启用 yylloc,您必须执行其他操作。
    • 您是否添加了 %locations 指令?您是否将生成的 .tab.h 文件包含在词法分析器中?也许您使用的是非常旧的 bison+flex 版本?它适用于 Bison 2.4.1 和 Flex 2.5.35。
    • 在可重入扫描仪中,我必须yyget_lineno(scanner) 而不仅仅是yylineno
    【解决方案2】:

    bisonflex 都不会自动更新 yylloc,但如果你知道诀窍的话,自己动手并不难。

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

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

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

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

    #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++; \
            } \
        }
    

    如果您愿意,也可以将该代码放在一个函数中并让宏调用该函数,但这两种技术是等效的。

    【讨论】:

    • 比其他答案更有帮助,尽管我建议创建一个函数并在宏中调用它 - static void update_loc(YYLTYPE *loc, char *txt) { loc->first_line = ...} / #define YY_USER_ACTION update_loc(yylloc, yytext);
    • 别管位置的事了,原来我有一条匹配整行的规则,REJECTed 它。
    • 这是迄今为止最有帮助的答案,IMO 应该是公认的答案
    【解决方案3】:

    我喜欢 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
    

    列位置可能会发生一些事情,它没有严格跟踪列,而是不断增加。这只是我的无知,如果它让任何人感到困惑,请道歉。我目前正在使用列来保持文件字符数,在我的情况下,这比列位置更有益。

    希望对您有所帮助。

    【讨论】:

    • 非常感谢您发布此内容。
    • 列号不断增加的原因是因为您永远不会在换行符上将其设置回 1,Flex 也不会,因为它甚至不知道您的 yycolumn 变量。显然您需要做的是自己跟踪换行符,而不是依赖%option yylineno
    • yylval.str = strdup(yytext)不设置Bison token的内容吗?如果每个标记都是字符串,您只会希望这成为规则中的默认操作,不是吗?
    • 宏的yylval.str = strdup(yytext); 部分是否必要?
    • 我觉得yylval.str = strdup(yytext);没有必要
    【解决方案4】:

    看看3.6 of the Bison manual 部分 - 这似乎包含了一些详细的位置。结合您在 Flex 手册中找到的内容,这可能就足够了。

    【讨论】:

    • 我认为只有行号是导入给我的。
    【解决方案5】:

    如果您只关心保留行号,Shomi 的答案是最简单的解决方案。但是,如果您还需要列号,则需要跟踪它们。

    一种方法是在出现换行符的任何地方添加yycolumn = 1 规则(如大卫埃尔森的回答中所建议的那样),但如果您不想跟踪可能出现换行符的所有位置(空格、cmets、等等...)另一种方法是在每个操作开始时检查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 选项而烦恼。

    【讨论】:

      【解决方案6】:

      所以,我让这个“工作”,但有几个额外的步骤(我可能在这里忽略了它们......在这种情况下道歉):

      1. parser.y 中,我不得不说:

        #define YYLEX_PARAM &yylval, &yylloc
        

        即使使用%locationsbison --locations,也要让它传递数据。

      2. lexer.l 我不得不使用 -> 而不是 . 来代替 yylloc

      3. 同样在 lexer.l 中,我重置了操作中的列:

        [\n] { yycolumn = 1; }
        

      对于\r 等,显然有点复杂,但至少我让它工作了。

      【讨论】:

      • 在语法后添加 --locations 或 %locations 将意味着 yylloc 在 .y 文件的范围内,前提是您从最后“%%”部分的代码中引用它.
      【解决方案7】:

      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
      

      【讨论】:

        【解决方案8】:

        我想我成功了(感谢野牛手册的作者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;
                  }
        

        现在在 bison 中,检索这些字段:

        statement : IDENTIFIER '=' expression 
                    { printf("%d - %d\n", @1.last_line, @1.last_column); }
        

        默认这些字段初始化为1,我们应该将列字段初始化为零,否则会报错列。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-04-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-06-27
          • 2013-05-03
          • 1970-01-01
          相关资源
          最近更新 更多