野牛解析器扩展规则而不是减少规则

时间:2014-08-03 14:35:03

标签: parsing insert token bison

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

可以使用flex lexer生成标记:(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表达式时,我应该以某种方式让词法分析器返回EXC令牌n次吗? (如果可能的话,我宁愿避免这种情况,因为这需要flex代码来保存某种状态的记录,它可能处于重复感叹状态或处于正常状态。然后,词法分析器不那么简单。 )

1 个答案:

答案 0 :(得分:1)

在无上下文语法中真的不可能。

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

野牛手册没有很好地解释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);
}

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

  • PARSE将具有指定类型标记和值的标记发送到yyparse;宏PARSE_TOKEN发送没有语义值的令牌。

  • %options行会从编译步骤中删除多个警告

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

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

  • 添加了!*<int>规则。

  • 添加了<<EOF>>规则。 (对于词法分析器驱动的推送解析来说,这是非常好的锅炉板。)

  • 添加了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而疯狂;我更喜欢小写的联合标签,但我试图最小化差异。

相关问题