【问题标题】:Bison parser expanding rule instead of reducing ruleBison 解析器扩展规则而不是减少规则
【发布时间】:2014-08-03 14:35:02
【问题描述】:

Bison 规则是否可以扩大而不是缩小,从而变成更多的标记?换一种方式问:是否可以在解析器输入中的下一个标记之前插入要解析的额外标记?

这是我可能想要的示例:

假设我想要一个能够理解三种标记类型的解析器。数字(为简单起见只是正整数 - INT)、单词(任意数量的字母、大写或小写 STRING)和某种其他符号(让我们无缘无故地使用感叹号 - EXC)

假设我有一个规则,减少一个单词,后跟一个数字,后跟一个感叹号。该规则产生一个整数类型,现在假设它只是将其输入加倍。该规则还允许自己成为它解析的整数。

我还有一个规则可以连续接受任意数量的这些(开始规则)。

Bison 解析器如下所示:(quicktest.y)

%{
#include <stdio.h>
%}

%union {
    int INT_VAL;
}

%token STRING EXC
%token <INT_VAL> INT
%type <INT_VAL> somenumber

%%

    start: somenumber           {printf ("Result: %d\n", $1);}
         | start somenumber     {printf ("Result: %d\n", $2);}
         ;

    somenumber: STRING INT EXC           {$$ = $2 *2;}
              | STRING somenumber EXC    {$$ = $2 *2;}
              ;

%%

main(int argc, char ** argv){

    yyparse();

}

yyerror(char* s){
    fprintf(stderr, "%s\n", s);
}

可以使用弹性词法分析器生成标记,如下所示:(quicktest.l)

%{
    #include "quicktest.tab.h"  
%}

%%

[A-Za-z]+               {return STRING;}
[1-9]+                  {yylval.INT_VAL = atoi(yytext); return INT;}
"!"                     {return EXC;}
.                       {}

这可以使用以下命令构建:

bison -d quicktest.y
flex quicktest.l
gcc -o quicktest quicktest.tab.c lex.yy.c -lfl -ggdb

我现在可以输入如下内容:

double double 2 ! !

并得到结果 8

现在,如果我希望用户能够避免在一行上有很多感叹号,就像这样:

a b c d e f 2 ! ! ! ! ! !

我希望能够允许他们输入如下内容:

a b c d e f 2 !*6

所以我可以为这样的标记添加一个 flex 表达式,它可以简单地提取所需的感叹号:

!\*[1-9]+               {
                            char *number = malloc(sizeof(char) * (strlen(yytext)-1));
                            strcpy(number, yytext+2);
                            yylval.INT_VAL = atoi(number);
                            free(number);
                            printf("Multiple exclamations: %d\n", yylval.INT_VAL);
                            return REPEAT_EXC;
                        }

但是我将如何实现野牛的一面呢?

我可以像这样添加令牌类型:

%token <INT_VAL> REPEAT_EXC

然后也许是某种规则?

    repeat_exc: REPEAT_EXC      {/*expand into n exclamation marks (EXC tokens)*/}
              ;

Bison 是否以任何方式支持这一点?

如果没有,我应该如何实现?

我是否应该让词法分析器在收到重复的 exc 表达式时返回 n 次 EXC 令牌? (如果可能的话,我宁愿避免这种情况,因为这需要 flex 代码记录某种状态,它可能处于重复感叹状态或正常状态。词法分析器就不那么容易维护了。)

【问题讨论】:

    标签: parsing insert token bison


    【解决方案1】:

    这在上下文无关语法中确实是不可能的。

    在传统的词法分析器中做起来并不难,但正如你所说,它需要词法分析器保持状态。一种更简单的方法是使用push parser,其中解析器是从词法分析器调用的,而不是相反。 [注1]

    bison 手册没有很好地解释 API;如果你声明一个纯推送解析器,你得到的接口是:

    int yypush_parse(yypstate*, int, const YYSTYPE*);
    

    或者,如果启用了位置跟踪:

    int yypush_parse(yypstate*, int, const YYSTYPE*, YYLTYPE*);
    

    为了显示 push_parser 界面,我对您的示例进行了相当小的更改。一是解析器;唯一的区别是声明推送解析器的%define 指令;消除了main(词法分析器现在是顶级的),并声明yyerror 具有显式的void 返回类型。 [注2]

    %{
        #include <stdio.h>
        void yyerror(char* msg);
    %}
    
    %define api.pure full
    %define api.push-pull push
    %union {
        int INT_VAL;
    }
    
    %token STRING EXC
    %token <INT_VAL> INT
    %type <INT_VAL> somenumber
    
    %%
    
        start: somenumber           {printf ("Result: %d\n", $1);}
             | start somenumber     {printf ("Result: %d\n", $2);}
             ;
    
        somenumber: STRING INT EXC           {$$ = $2 *2;}
                  | STRING somenumber EXC    {$$ = $2 *2;}
                  ;
    
    %%
    
    void yyerror(char* s){
        fprintf(stderr, "%s\n", s);
    }
    

    词法分析器有一些更实质性的变化,但我不认为最终结果更难阅读或维护。它甚至可能更容易。

    • PARSEyyparse发送一个带有指定类型标签和值的token;宏 PARSE_TOKEN 发送一个没有语义值的令牌。

    • %options 行从编译步骤中删除了几个警告

    • 添加了解析器状态的初始化。 (%% 之后和任何规则之前的缩进行插入到词法分析器函数的顶部,在本例中为 yypush_parse,因此它们可用于声明和初始化局部变量。)

    • INT 规则已更改为允许 10 成为有效整数。

    • 添加了!*&lt;int&gt; 规则。

    • 添加了&lt;&lt;EOF&gt;&gt; 规则。 (对于词法分析器驱动的推送解析来说,这是一个很好的样板。)

    • 添加了main 函数,该函数调用yylex

    (哦,我更改了一条规则以避免重复新行。)

    %{
      #include "push.tab.h"  
      #define PARSE(tok,tag,val) do { \
        YYSTYPE yylval = {.tag=val};  \
        int status = yypush_parse(ps, tok, &yylval); \
        if (status != YYPUSH_MORE) return status; \
      } while(0)
      #define PARSE_TOKEN(tok) do { \
        int status = yypush_parse(ps, tok, 0); \
        if (status != YYPUSH_MORE) return status; \
      } while(0)
    %}
    %option noyywrap nounput noinput
    
    %%
                             yypstate *ps = yypstate_new ();
    
    [A-Za-z]+               {PARSE_TOKEN(STRING);}
    [1-9][0-9]*             {PARSE(INT,INT_VAL,atoi(yytext));}
    "!*"[1-9][0-9]*         {int r = atoi(yytext+2);
                             while (r--) PARSE_TOKEN(EXC);
                            }
    "!"                     {PARSE_TOKEN(EXC);}
    .|\n                    {}
    <<EOF>>                 {int status = yypush_parse(ps, 0, 0);
                             yypstate_delete(ps);
                             return status; 
                            }
    
    %%
    
    int main(int argc, char** argv) {
      return yylex();
    }
    

    注意事项

    1. 这是lemon 解析器生成器的样式。 lemon 最初是为了创建sqlite SQL 解析器而编写的,但正是为了方便“推送”接口而在各种项目中使用。 bison 的推送解析器支持较新,非常受欢迎。

    2. 我不喜欢INT_VAL;我更喜欢小写的联合标签,但我试图最小化差异。

    【讨论】:

    • 这一切看起来都很棒,但是...... 哪个版本的 Bison 是第一个支持此功能的?在 2.5 版中,我收到一个编译器错误,提示 api.pure 的值无效。我正在使用的机器有点旧且难以更新,我会尽快在以后的机器上尝试它。另外,是的,我不知道为什么我将 INT_VAL 设为大写。我通常也会这样做。
    • @DanJAB:我相信它是在 2.4.1 中实现的,但在该版本中声明的语法可能有所不同。请查阅 2.5 版本中包含的文档(希望已安装)。 %define api.pure full 声明很可能是后来添加的;在这个简单的情况下,只需 %define api.pure 就足够了。
    • @DanJAB:更具体地说,full 声明仅影响yyerror 的原型;最初的 %define api.pure 声明并没有改变 yyerror 的原型,后来被认为是一个坏主意,但不能在不失去向后兼容性的情况下进行更改。
    • 啊哈!使用更新的机器(和 Bison 3.0.2)编译和运行没有问题。完美的。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多