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代码来保存某种状态的记录,它可能处于重复感叹状态或处于正常状态。然后,词法分析器不那么简单。 )
答案 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();
}
这是lemon
解析器生成器的样式。 lemon
最初是为了创建sqlite
SQL解析器而编写的,但为了方便“推送”界面而在各种项目中使用。 bison
的推送解析器支持是最新的,非常受欢迎。
我并不为INT_VAL
而疯狂;我更喜欢小写的联合标签,但我试图最小化差异。