首页 > 解决方案 > 如何在 Flex/Bison 中检查标记的语义值

问题描述

我正在尝试使用 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(示例)。我检查了类似的问题,他们以同样的方式做,或者我只是错过了一些东西。

PS 当我为 flex 启用调试模式时,它显示它接受了规则 {ID} 的“示例”,但是该示例存储在哪里以及我应该如何提前使用它。

标签: cbisonflex-lexer

解决方案


Flex 和 bison 通过语义联合来传递语义值yylval,默认情况下是一个全局变量。(注1)如果一个token具有语义值,那么报告token类型的flex动作必须将语义联合的适当成员设置为token的值,bison将提取该值并将其放在解析器堆栈中。

Bison 依靠用户声明来判断哪个联合成员用于标记和非终结符的语义值(如果它们具有语义值)。所以如果你有 flex 动作:

{ID}        {yylval.stringValue= strdup(yytext); return(ID);}

人们希望在相应的野牛输入文件中看到以下内容:

%union {
   /* ... */
   char* stringValue;
}
%token <stringValue> ID

最后一行告诉 bison 这ID是一个标记类型,并且它的关联语义类型是具有成员 name 的语义类型stringValue。随后,你可以参考token的语义值,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 是如何处理令牌流的。启用跟踪后,bison 将在第一次看到令牌时记录该令牌,而上述规则在整个程序被解析之前不会打印 ID 令牌的语义值。

默认情况下,跟踪工具只打印令牌类型,因为 Bison 不知道如何打印任意语义值。但是,您可以为语义类型或特定标记或非终结符定义打印机规则。这些规则应该将语义值(不带分隔符)打印到输出流yyoutput。在这样的规则中,$$可以使用语义值来访问(而bison会填写成员访问,如上)。

这是仅由函数调用组成的简单语言的完整示例:

文件打印机.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($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);
}

文件打印机.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 

笔记:

  1. 语义类型不必是联合,但它很常见。有关其他选项,请参阅野牛手册。

  2. 用于创建令牌的strdup必须与free某处匹配。ID在这个简单的示例中,令牌(和非终端)的语义值call仅用于跟踪,因此一旦它们被其他非终端消费,它们就可以被释放。Bison 不会为解析规则使用的令牌调用析构函数;它假定程序员知道是否需要令牌。析构函数规则用于 Bison 本身从堆栈中弹出的令牌,通常是为了响应语法错误。


推荐阅读