所以我试图了解Bison和Flex(以及两者如何结合在一起)。我给出的示例语法非常简单,
e → e plus t
e → t
t → t TIMES f
t → f
f → LPAREN e RPAREN
f → ID
我的测试输入只是“x”,我希望输出为:
"(e (t (f (ID x))))"
我得到的实际输出是:
ID x f t
我想知道为什么我的输出是倒退的(我还没有添加括号)。这是我的flex和bison文件。
野牛:
%{
#include "expr-parse-defs.h"
#include <iostream>
std::string AST;
%}
%union {
char *sval;
}
%token <sval> ID PLUS TIMES LPAREN RPAREN
%%
e :
| e PLUS t { AST += std::string("e ") + $2 + "t "; }
| t { AST += "t "; }
;
t :
| t TIMES f { AST += std::string("t ") + $2 + "f "; }
| f { AST += "f "; }
;
f :
| LPAREN e RPAREN { AST += $1 + std::string("e ") + $3; }
| ID { AST += std::string("ID ") + $1 + " " ; }
;
%%
int main() {
yyparse();
std::cout << AST;
return 0;
}
软硬度:
%{
#include <cstring>
#include <string>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include "expr-parse.tab.h"
#include "expr-parse-defs.h"
using namespace std;
int tokenpos = 0;
char * process_token(const char* token){
// we have to copy because we can't rely on yytext not changing underneath us:
char *res = new char[strlen(yytext) + 3];
strcpy(res, yytext);
yylval.sval = res;
}
%}
ID [a-zA-Z][a-zA-Z0-9_]*
%%
"+" { yylval.sval = process_token("PLUS"); return PLUS; }
"*" { yylval.sval = process_token("TIMES"); return TIMES; }
"(" { yylval.sval = process_token("LPAREN"); return LPAREN; }
")" { yylval.sval = process_token("RPAREN"); return RPAREN; }
{ID} { yylval.sval = process_token("ID"); return ID; }
[\n]
%%
int yyerror(const char *s) {
cerr << "this is a bad error message and needs to be changed eventually" << endl;
return 0;
}
答案 0 :(得分:3)
Bison会生成bottom-up LALR(1)解析器。你可以想象它的工作原理如下:
ID
。ID
后面的情况,所以它知道它可以简单地移动这个标记。现在它的堆栈上有ID
终端。ID
唯一有效的方法是将降低到f
。所以它适用f → ID
,现在堆栈上有f
。t → f
缩小以获得t
。TIMES
,因此规则t → t TIMES f
将不适用,因此使用e → t
减少来获取e
}。PLUS
,也没有什么可以转移到这里。e
是根符号,而前瞻是文件结束标记,您就完成了。这种自下而上的操作对您来说可能看起来很奇怪,但通常更强大,并且还可以导致比自上而下解析更具描述性的错误消息。您可以看到它在哪些时候使用前瞻来决定下一步。您还可以想象,如果您有实际数字并且正在实施一些玩具计算器,那么这种自下而上的方法将允许您在解析整个表达式之前评估表达式的各个部分。手册有details on the algorithm。
我希望输出为:
"(e (t (f (ID x))))"
然后这样写。举个例子,拿这个:
%{
#include "expr-parse-defs.h"
#include <iostream>
#define YYSTYPE std::string;
%}
%token ID PLUS TIMES LPAREN RPAREN
%%
e :
| e PLUS t { $$ = std::string("(e ") + $1 + " " + $3 + ")"; }
| t { $$ = std::string("(e ") + $1 + ")"; }
;
[…]
这使用字符串作为非终端的语义值。您cannot use C++ with non-POD types喜欢std::string
的Noite。现在,当解析器执行其规则时,您期望的表单的表达式被“内部”组合。使用单个AST
变量的方法适用于您的简单示例,但是具有两个非终端子项(如e → e plus t
)的规则必须组合两个字符串。最好使用语义值。您可以定义自己的C ++类型来保存各种信息,例如术语的文本表示,数值以及源中定义它的位置。