我正在尝试使用Flex / Bison创建简单的Pascal编译器,我想检查使用令牌存储的语义值。我有以下用于flex的代码:
...
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
...
以下是野牛代码:
...
program: PROGRAM ID LBRACKET identifier_list RBRACKET DELIM declarations subprogram_declarations compound_statement DOT {printf($2);}
...
以及以下测试文件:
program example(input, output);
...
Flex和bison可以完美地识别所有内容,并且可以解析,但是如果我想先检查代码中的令牌值,那么它就不会起作用:
Starting parse
Entering state 0
Reading a token: Next token is token PROGRAM ()
Shifting token PROGRAM ()
Entering state 1
Reading a token: Next token is token ID ()
Shifting token ID ()
Entering state 3
是否有一种方法可以在()中打印令牌值,例如令牌ID(示例)。我检查了类似的问题,它们以相同的方式进行操作,或者也许我只是缺少一些东西。
P.S。当我为flex启用调试模式时,它表明它已被规则{ID}接受了“示例”,但是该示例存储在哪里以及应如何提前使用它。
答案 0 :(得分:2)
Flex和bison通过语义联合yylval
(默认情况下是全局变量)传达语义值。 (注1)如果令牌具有语义值,则报告令牌类型的flex操作必须将语义联合的适当成员设置为令牌的值,而bison会提取该值并将其放置在解析器堆栈中。>
Bison依靠用户声明来判断将哪个并集成员用于令牌和非终结符(如果它们具有语义值)的语义值。因此,如果您有flex动作:
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
人们希望在相应的野牛输入文件中看到以下内容:
%union {
/* ... */
char* stringValue;
}
%token <stringValue> ID
最后一行告诉野牛,ID
是一种令牌类型,其关联的语义类型是成员名为stringValue
的语义类型。随后,您可以参考令牌的语义值,而bison会自动插入成员访问权限,以便您有以下规则:
program: PROGRAM ID LBRACKET identifier_list RBRACKET
DELIM declarations subprogram_declarations compound_statement DOT
{ printf("%s\n", $2); /* Always use a format string in a printf! */ }
$2
将替换为stack[frame_base + 2].stringValue
。
但是,使用bison文件中的操作几乎没有意义,因为使用bison的跟踪工具可以很容易地看到bison如何处理令牌流。启用跟踪后,将在野牛第一次看到令牌时记录该令牌,这与上述规则相反,该规则直到解析了整个程序后才显示ID令牌的语义值。
默认情况下,跟踪工具仅打印令牌类型,因为Bison不知道如何打印任意语义值。但是,您可以为语义类型或特定标记或非终端定义打印机规则。这些规则应将语义值(不带分隔符)打印到输出流yyoutput
中。在这样的规则中,$$
可用于访问语义值(如上所述,野牛会填充成员访问权限。)
这是仅包含函数调用的简单语言的完整示例:
%{
#include <stdio.h>
#include <string.h>
int yylex(void);
%}
%defines
%define parse.trace
%union {
char* str;
long num;
}
%token <str> ID
%token <num> NUM
%type <str> call
/* Printer rules: token-specific, non-terminal-specific, and by type. */
%printer { fprintf(yyoutput, "%s", $$); } ID
%printer { fprintf(yyoutput, "%s()", $$); } call
%printer { fprintf(yyoutput, "%ld", $$); } <num>
/* Destructor rule: by semantic type */
%destructor { free($$); } <str>
%code provides {
void yyerror(const char* msg);
}
%%
prog: %empty
| prog stmt ';'
stmt: %empty
| call { free($1); /* See Note 2 */ }
call: ID '(' args ')' { $$ = $1; /* For clarity; this is the default action */ }
args: %empty
| arglist
arglist: value
| arglist ',' value
value: NUM
| ID { free($1); /* See Note 2 */ }
| call { free($1); /* ditto */ }
%%
int main(int argc, char** argv) {
if (argc > 1 && strcmp(argv[1], "-d") == 0) yydebug = 1;
return yyparse();
}
void yyerror(const char* msg) {
fprintf(stderr, "%s\n", msg);
}
%{
#include <stdlib.h>
#include "printer.tab.h"
%}
%option noinput nounput noyywrap nodefault
%%
[[:space:]]+ ;
[[:alpha:]_][[:alnum:]_]* { yylval.str = strdup(yytext); return ID; }
[[:digit:]]+ { yylval.num = strtol(yytext, NULL, 10); return NUM; }
. return *yytext;
bison -d -t -o printer.tab.c printer.y
flex -o printer.lex.c printer.l
gcc -Wall -ggdb -std=c11 -o printer printer.lex.c printer.tab.c
语义类型不必是一个并集,但这是很常见的。有关其他选择,请参阅野牛手册。
用于创建令牌的strdup
必须与某个地方的free
匹配。在这个简单的示例中,ID
令牌(和call
非终结符)的语义值仅用于跟踪,因此一旦被其他一些非终奌站。 Bison不会为解析规则使用的令牌调用析构函数。它假定程序员知道是否需要令牌。析构函数规则用于Bison本身从堆栈弹出的令牌,通常是响应语法错误。
答案 1 :(得分:0)
野牛无法自己知道语义值应从何处获取。所以
您必须为令牌定义%printer
。在您的情况下,您必须定义令牌的类型和相应的打印机:
%token <stringValue> ID
%printer { fprintf(yyoutput, "%s", $$); } ID;
为每个要在痕迹中进行深入检查的令牌定义一台打印机,然后它应该可以正常工作。