Почему этот код bison выдает неожиданный результат?

#parsing #bison #flex-lexer #yacc

#c #bison #flex-lexer

Вопрос:

гибкий код:

   1 %option noyywrap nodefault yylineno case-insensitive
  2 %{
  3 #include "stdio.h"
  4 #include "tp.tab.h"
  5 %}
  6 
  7 %%
  8 "{"             {return '{';}
  9 "}"             {return '}';}
 10 ";"             {return ';';}
 11 "create"        {return CREATE;}
 12 "cmd"           {return CMD;}
 13 "int"           {yylval.intval = 20;return INT;}
 14 [a-zA-Z]        {yylval.strval = yytext;printf("id:%sn" , yylval.strval);return ID;}
 15 [ tn]
 16 <<EOF>>         {return 0;}
 17 .               {printf("mistery charn");}
 18 
  

код bison:

   1 %{
  2 #include "stdlib.h"
  3 #include "stdio.h"
  4 #include "stdarg.h"
  5 void yyerror(char *s, ...);
  6 #define YYDEBUG 1
  7 int yydebug = 1;
  8 %}
  9 
 10 %union{
 11     char *strval;
 12     int intval;
 13 }
 14 
 15 %token <strval> ID
 16 %token <intval> INT
 17 %token CREATE
 18 %token CMD
 19 
 20 %type <strval> col_definition
 21 %type <intval> create_type
 22 %start stmt_list
 23 
 24 %%
 25 stmt_list:stmt ';'
 26 | stmt_list stmt ';'
 27 ;
 28 
 29 stmt:create_cmd_stmt         {/*printf("create cmdn");*/}
 30 ;
 31 
 32 create_cmd_stmt:CREATE CMD ID'{'create_col_list'}'    {printf("%sn" , $3);}
 33 ;
 34 create_col_list:col_definition
 35 | create_col_list col_definition
 36 ;
 37 
 38 col_definition:create_type ID ';' {printf("%d , %sn" , $1, $2);}
 39 ;
 40 
 41 create_type:INT {$$ = $1;}
 42 ;
 43 
 44 %%
 45 extern FILE *yyin;
 46 
 47 void
 48 yyerror(char *s, ...)
 49 {
 50     extern yylineno;
 51     va_list ap;
 52     va_start(ap, s);
 53     fprintf(stderr, "%d: error: ", yylineno);
 54     vfprintf(stderr, s, ap);
 55     fprintf(stderr, "n");
 56 }
 57 
 58 int main(int argc , char *argv[])
 59 {
 60     yyin = fopen(argv[1] , "r");
 61     if(!yyin){
 62         printf("open file %s failedn" ,argv[1]);
 63         return -1;
 64     }
 65 
 66     if(!yyparse()){
 67         printf("parse work!n");
 68     }else{
 69         printf("parse failed!n");
 70     }
 71 
 72     fclose(yyin);
 73     return 0;
 74 }
 75
  

тестовый входной файл:

 create cmd keeplive
{
    int a;
    int b;
};
  

тестовый вывод:

 root@VM-Ubuntu203001:~/test/tpp# ./a.out t1.tp 
id:keeplive
id:a
20 , a;
id:b
20 , b;
keeplive
{
    int a;
    int b;
}
parse work!
  

У меня есть два вопроса:

1) Почему действие в строке 38 выводит токен ‘;’? Например, «20, a;» и «20, b;»

2) Почему действие в строке 32 выводит «keeplive { int a; int b; }» вместо простого «keeplive»?

Ответ №1:

Короткий ответ:

 yylval.strval = yytext;
  

Вы не можете использовать yytext таким образом. Строка, на которую он указывает, является частной для лексера и изменится, как только действие flex завершится. Вам нужно сделать что-то вроде:

 yylval.strval = strdup(yytext);
  

и затем вам нужно убедиться, что вы впоследствии освободили память.


Более длинный ответ:

yytext на самом деле это указатель на буфер, содержащий входные данные. Чтобы заставить yytext работать так, как если бы это была строка, заканчивающаяся нулем, flex фреймворк перезаписывает символ, следующий за токеном, на NUL перед выполнением действия, а затем заменяет исходный символ, когда действие завершается. Таким образом, strdup внутри действия все будет работать нормально, но вне действия (в вашем коде bison) теперь у вас есть указатель на часть буфера, начинающуюся с токена. И это становится хуже позже, так как flex будет считана следующая часть исходного кода в тот же буфер, и теперь ваш указатель на случайный мусор. Существует несколько возможных сценариев, в зависимости от flex опций, но ни один из них не является приятным.

Итак, золотое правило: yytext действует только до окончания действия. Если вы хотите сохранить его, скопируйте его, а затем убедитесь, что вы освободили хранилище для копии, когда она вам больше не понадобится.

Почти во всех лексерах, которые я написал, токен ID фактически находит идентификатор в таблице символов (или помещает его туда) и возвращает указатель в таблицу символов, что упрощает управление памятью. Но у вас все еще есть по существу та же проблема с управлением памятью, например, с символьными строковыми литералами.