如何检查 Flex/Bison 中标记的语义值
How to check semantic values of tokens in Flex/Bison
我正在尝试使用 Flex/Bison 创建简单的 Pascal 编译器,我想检查使用标记存储了哪些语义值。我有以下 flex 代码:
...
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
...
下面是 bison 中的代码:
...
program: PROGRAM ID LBRACKET identifier_list RBRACKET DELIM declarations subprogram_declarations compound_statement DOT {printf();}
...
以及以下测试文件:
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} 的 "example",但是该示例存储在哪里以及我应该如何提前使用它。
Bison 自己无法知道语义值应该从哪里获取。所以
您必须为您的令牌定义 %printer
s。在您的情况下,您必须定义令牌的类型和相应的打印机:
%token <stringValue> ID
%printer { fprintf(yyoutput, "%s", $$); } ID;
为每个令牌定义一台打印机,您希望在跟踪中对其进行深入检查,然后它应该会按预期工作。
Flex 和 bison 通过语义联合 yylval
传达语义值,默认情况下是一个全局变量。 (注 1)如果一个标记有一个语义值,报告该标记类型的 flex 动作必须将语义联合的适当成员设置为该标记的值,bison 将提取该值并将其放入解析器堆栈。
Bison 依靠用户声明来判断哪个联合成员用于标记和非终结符的语义值(如果它们具有语义值)。所以如果你有弹性动作:
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
人们希望在相应的 bison 输入文件中看到以下内容:
%union {
/* ... */
char* stringValue;
}
%token <stringValue> ID
最后一行告诉 bison ID
是一种标记类型,其关联的语义类型是成员名称为 stringValue
的类型。随后,您可以参考令牌的语义值,bison 将自动插入成员访问,这样如果您有规则:
program: PROGRAM ID LBRACKET identifier_list RBRACKET
DELIM declarations subprogram_declarations compound_statement DOT
{ printf("%s\n", ); /* Always use a format string in a printf! */ }
</code> 将替换为 <code>stack[frame_base + 2].stringValue
的等价物。
但是,在 bison 文件中使用类似的操作没有什么意义,因为使用 bison 的跟踪工具很容易查看 bison 如何处理令牌流。启用跟踪后,标记将在 bison 首次看到时记录下来,这与上面的规则相反,后者在整个程序被解析之前不会打印 ID 标记的语义值。
默认情况下,跟踪工具只打印标记类型,因为 Bison 不知道如何打印任意语义值。但是,您可以为语义类型或特定标记或非终端定义打印机规则。这些规则应该将语义值(不带分隔符)打印到输出流 yyoutput
。在这样的规则中,可以使用$$
来访问语义值(而bison会填写成员访问,如上)。
这是一个仅包含函数调用的简单语言的完整示例:
文件printer.y
%{
#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(); /* See Note 2 */ }
call: ID '(' args ')' { $$ = ; /* For clarity; this is the default action */ }
args: %empty
| arglist
arglist: value
| arglist ',' value
value: NUM
| ID { free(); /* See Note 2 */ }
| call { free(); /* 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);
}
文件printer.l
%{
#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
备注:
语义类型不一定是并集,但很常见。有关其他选项,请参阅 bison 手册。
用于创建令牌的 strdup
必须在某处与 free
匹配。在这个简单的示例中,ID
标记(和 call
非终结符)的语义值仅用于跟踪,因此一旦它们被其他非终结符使用,它们就可以被释放终端。 Bison 不会为解析规则使用的标记调用析构函数;它假定程序员知道是否需要令牌。析构函数规则用于 Bison 本身从堆栈中弹出的标记,通常用于响应语法错误。
我正在尝试使用 Flex/Bison 创建简单的 Pascal 编译器,我想检查使用标记存储了哪些语义值。我有以下 flex 代码:
...
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
...
下面是 bison 中的代码:
...
program: PROGRAM ID LBRACKET identifier_list RBRACKET DELIM declarations subprogram_declarations compound_statement DOT {printf();}
...
以及以下测试文件:
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} 的 "example",但是该示例存储在哪里以及我应该如何提前使用它。
Bison 自己无法知道语义值应该从哪里获取。所以
您必须为您的令牌定义 %printer
s。在您的情况下,您必须定义令牌的类型和相应的打印机:
%token <stringValue> ID
%printer { fprintf(yyoutput, "%s", $$); } ID;
为每个令牌定义一台打印机,您希望在跟踪中对其进行深入检查,然后它应该会按预期工作。
Flex 和 bison 通过语义联合 yylval
传达语义值,默认情况下是一个全局变量。 (注 1)如果一个标记有一个语义值,报告该标记类型的 flex 动作必须将语义联合的适当成员设置为该标记的值,bison 将提取该值并将其放入解析器堆栈。
Bison 依靠用户声明来判断哪个联合成员用于标记和非终结符的语义值(如果它们具有语义值)。所以如果你有弹性动作:
{ID} {yylval.stringValue= strdup(yytext); return(ID);}
人们希望在相应的 bison 输入文件中看到以下内容:
%union {
/* ... */
char* stringValue;
}
%token <stringValue> ID
最后一行告诉 bison ID
是一种标记类型,其关联的语义类型是成员名称为 stringValue
的类型。随后,您可以参考令牌的语义值,bison 将自动插入成员访问,这样如果您有规则:
program: PROGRAM ID LBRACKET identifier_list RBRACKET
DELIM declarations subprogram_declarations compound_statement DOT
{ printf("%s\n", ); /* Always use a format string in a printf! */ }
</code> 将替换为 <code>stack[frame_base + 2].stringValue
的等价物。
但是,在 bison 文件中使用类似的操作没有什么意义,因为使用 bison 的跟踪工具很容易查看 bison 如何处理令牌流。启用跟踪后,标记将在 bison 首次看到时记录下来,这与上面的规则相反,后者在整个程序被解析之前不会打印 ID 标记的语义值。
默认情况下,跟踪工具只打印标记类型,因为 Bison 不知道如何打印任意语义值。但是,您可以为语义类型或特定标记或非终端定义打印机规则。这些规则应该将语义值(不带分隔符)打印到输出流 yyoutput
。在这样的规则中,可以使用$$
来访问语义值(而bison会填写成员访问,如上)。
这是一个仅包含函数调用的简单语言的完整示例:
文件printer.y
%{
#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(); /* See Note 2 */ }
call: ID '(' args ')' { $$ = ; /* For clarity; this is the default action */ }
args: %empty
| arglist
arglist: value
| arglist ',' value
value: NUM
| ID { free(); /* See Note 2 */ }
| call { free(); /* 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);
}
文件printer.l
%{
#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
备注:
语义类型不一定是并集,但很常见。有关其他选项,请参阅 bison 手册。
用于创建令牌的
strdup
必须在某处与free
匹配。在这个简单的示例中,ID
标记(和call
非终结符)的语义值仅用于跟踪,因此一旦它们被其他非终结符使用,它们就可以被释放终端。 Bison 不会为解析规则使用的标记调用析构函数;它假定程序员知道是否需要令牌。析构函数规则用于 Bison 本身从堆栈中弹出的标记,通常用于响应语法错误。