Как проверить семантические значения токенов в Flex / Bison - PullRequest
0 голосов
/ 02 сентября 2018

Я пытаюсь создать простой компилятор Pascal, используя Flex / Bison, и я хочу проверить, какие семантические значения хранятся с токенами. У меня есть следующий код для 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

Есть ли способ напечатать значение токена внутри (), например, идентификатор токена (пример). Я проверил похожие вопросы, и они делают то же самое, или, может быть, я что-то упускаю.

P.S. Когда я включаю режим отладки для flex, он показывает, что он принял «пример» по правилу {ID}, но где хранится этот пример и как мне его использовать заранее.

Ответы [ 2 ]

0 голосов
/ 02 сентября 2018

Flex и bison передают семантические значения через семантическое объединение yylval, по умолчанию глобальная переменная (Примечание 1) Если токен имеет семантическое значение, действие flex, сообщающее, что этот тип токена должен установить соответствующий элемент семантического объединения в значение токена, и bison извлечет значение и поместит его в стек синтаксического анализатора.

Bison полагается на пользовательские объявления, чтобы указать, какой член объединения используется для семантического значения токенов и нетерминалов (если они имеют семантические значения). Так что если у вас есть действие flex:

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

можно ожидать увидеть следующее в соответствующем входном файле бизонов:

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

Последняя строка сообщает bison, что ID является типом токена, и что связанный с ним семантический тип - это тип с именем члена stringValue. Впоследствии вы можете ссылаться на семантическое значение токена, и бизон автоматически вставит член доступа, так что если у вас есть правило:

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 не знает, как распечатать произвольное семантическое значение. Однако вы можете определить правила печати для семантических типов или для определенных токенов или нетерминалов. Эти правила должны выводить семантическое значение (без разделителей) в выходной поток yyoutput. В таком правиле $$ может использоваться для доступа к семантическому значению (и бизон заполнит доступ к члену, как указано выше).

Вот полный пример простого языка, состоящего только из вызовов функций:

Файл 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($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 выталкивает из стека, обычно в ответ на синтаксические ошибки.

0 голосов
/ 02 сентября 2018

Бизон не может знать сам, откуда берутся семантические значения. Так Вы должны определить %printer s для своих токенов. В вашем случае вы должны определить тип токена и соответствующий принтер:

%token <stringValue> ID
%printer { fprintf(yyoutput, "%s", $$); } ID;

Определите по одному принтеру для каждого токена, который вы хотите глубоко исследовать в следах, тогда он должен работать так, как вы ожидаете.

...