«Синтаксическая ошибка» в Bison без объяснения причин

avatar
Villa
9 августа 2021 в 00:04
106
1
0

Я работаю над консольным приложением. Для создания интерпретатора я использую Flex и Bison. Я создал грамматику, но каждый раз, когда я пытаюсь использовать строку, я получаю «Синтаксическую ошибку» без каких-либо других объяснений. Строка, которую я пытаюсь использовать: MKDISK -PATH=./home/erick/disk.dk -u=k -size=1000\n

Я знаю, что есть проблема с производством

   comando : MKDISK lista_param      
               {
                  printf("Mkdisk con parametros\n");
                  Mkdisk m;
                  m.agregarParametros($2);
                  m.assignParameters();
               }
            ;

Потому что я заметил, что если я добавлю продукцию без lista_param, просто MKDISK, это сработает, и синтаксический анализатор всегда будет использовать эту продукцию, событие Если строка совпадает с другой.

parser.yy:

%skeleton "lalr1.cc" /* -*- C++ -*- */

%defines
%define api.parser.class {Parser}
%define api.token.constructor
%define api.value.type variant

%define parse.trace
%define parse.error verbose
%param { Driver& driver }


%code requires
{
   class Driver;
   class Comando;
   class Parametro;
   class Mkdisk;

}
%{
   using namespace std;
   #include <stdio.h>
   #include <iostream>
   #include <string>
   #include <vector>
   
   #include "driver.h"
%}


/******* TERMINALES ********/
%token <std::string> NUM"NUM" SIZE"SIZE" F"F" PATH"PATH" U"U" BF"BF" FF"FF" WF"WF" K"K" M"M" RUTA"RUTA" MKDISK"MKDISK" RMDISK"RMDISK"
%token GUION"GUION" IGUAL"IGUAL" 


/******* NO TERMINALES ********/
%start inicio;
%type <Parametro> parametro
%type <Comando> comando
%type <std::vector<Parametro>> lista_param
%type <std::string> atributo nom_param


%%

   inicio : lista_comandos "\n"
         { 
            printf("Primer nivel del arbol\n");
         }
          ;

   lista_comandos : lista_comandos comando  
                  { 
                     printf("Lista de comandos\n");
                  }
                  | comando                
                  { 
                     printf("Comando individual\n");
                  }
                  ;

   comando : MKDISK lista_param      
               {
                  printf("Mkdisk con parametros\n");
                  Mkdisk m;
                  m.agregarParametros($2);
                  m.assignParameters();
               }
            ;
   
   lista_param :  lista_param parametro   
                  {
                     printf("Lista de parametros\n");
                     $$=$1;
                     $$.push_back($2);
                  }
               | parametro                
                  {  
                     printf("parametro individual\n");
                     vector<Parametro> params;
                     params.push_back($1);
                     $$ = params;
                  }
               ;
   
   parametro : GUION nom_param IGUAL atributo 
               {  

                  printf("Quinto nivel del arbol\n");
                  Parametro param;
                  param.setNombre($2);
                  param.setValor($4);
                  $$ = param;
               }
             ;

   nom_param : SIZE     { $$=$1; }
             | F        { $$=$1; }
             | PATH     { $$=$1; }
             | U        { $$=$1; }
             ;

   atributo : NUM    { $$=$1; }
            | BF     { $$=$1; }
            | FF     { $$=$1; }
            | WF     { $$=$1; }
            | K      { $$=$1; }
            | M      { $$=$1; }
            | RUTA   { $$=$1; }
            ;

%%

void yy::Parser::error( const std::string& error){
  std::cout <<"\e[0;31m"<< error << std::endl;
}

lexer.l

%{
  #include <stdio.h>
  #include <string>
  #include "driver.h"
  #include "parser.tab.hh"
%}
%option case-insensitive
%option noyywrap
%option outfile="scanner.cc" 


DIGIT   [0-9]
NUM     {DIGIT}+("."{DIGIT}+)?
PATH    \"?(\/([^\/\n])*)+\"?

%%


"MKDISK"          { return yy::Parser::make_MKDISK(yytext); }
"RMDISK"          { return yy::Parser::make_RMDISK(yytext); }


"SIZE"            { return yy::Parser::make_SIZE(yytext); }
"F"               { return yy::Parser::make_F(yytext); }
"PATH"            { return yy::Parser::make_PATH(yytext); }
"U"               { return yy::Parser::make_U(yytext); }


{NUM}             { return yy::Parser::make_NUM(yytext);}
"BF"              { return yy::Parser::make_BF(yytext); }
"FF"              { return yy::Parser::make_FF(yytext); }
"WF"              { return yy::Parser::make_WF(yytext); }
"K"               { return yy::Parser::make_K(yytext); }
"M"               { return yy::Parser::make_M(yytext); }
{PATH}            { return yy::Parser::make_RUTA(yytext); }


"-"               { return yy::Parser::symbol_type(); }
"="               { return yy::Parser::symbol_type(); }

[[:blank:]]       {}
.                 { printf("Caracter no reconocido: %s\n",yytext);}

%%

void Driver::runScanner(){
    yy_flex_debug = false;
    yyin = fopen (file.c_str (), "r");
    if(yyin == NULL){
        printf("No se encontro el archivo de entrada");
        exit(1);
    }
}

void Driver::runScannerWithText(std::string text){
    yy_flex_debug = true;
    YY_BUFFER_STATE buffer = yy_scan_string(text.c_str());
}

void Driver::closeFile(){
    fclose(yyin);
}
Источник
rici
9 августа 2021 в 00:27
0

Насколько я понимаю, ваш лексер никогда не возвращает токен GUION, что, безусловно, делает невозможным совпадение parametro с чем-либо. Почему бы вам не включить трассировку бизонов, чтобы вы могли видеть, что происходит? Я подозреваю, что лексер возвращает EOF, когда видит - или =.

rici
9 августа 2021 в 02:31
0

Я ошибался. Лексер возвращает "пустой символ". Смотрите ответ.

Ответы (1)

avatar
rici
9 августа 2021 в 02:41
0

Хотя вы не включаете driver.cc или driver.hh в свой вопрос, я подозреваю, что они адаптированы из примера кода C++ в руководстве Bison. Этот код позволяет включить трассировку сканера или синтаксического анализатора с помощью флагов командной строки. Если вы не включили эту часть кода примера, я настоятельно рекомендую вам вернуть ее и включить трассировку. Вам будет намного легче увидеть, что происходит.

Непосредственная проблема заключается в том, что когда ваш сканер видит -, он выполняет действие:

"-"               { return yy::Parser::symbol_type(); }

который отправляет парсеру пустой токен. Пустые токены не являются действительными токенами, поэтому синтаксический анализатор жалуется. Вот трассировка (создана путем вызова исполняемого файла с флагом -p):

Starting parse
Entering state 0
Stack now 0
Reading a token
MKDISK -PATH=./home/erick/disk.dk -u=k -size=1000  
Next token is token MKDISK (MKDISK)
Shifting token MKDISK (MKDISK)
Entering state 1
Stack now 0 1
Reading a token
Next token is empty symbol                   <====== AQUÍ
syntax error
Error: popping token MKDISK (MKDISK)
Stack now 0
Stack now 0

Очевидно, что bison даже не пытается создать осмысленное сообщение об ошибке, когда сталкивается с подобной проблемой.

Помимо исправления действий - и =, вам нужно что-то сделать с:

inicio : lista_comandos "\n"

Несмотря на то, что это законно, это не сработает. Сканер даже не реагирует на символы новой строки (даже не объявляя их недопустимыми), потому что правило сканера не применяется. (Мне нравится использовать %option nodefault, чтобы flex предупредил меня, если я пропустил какой-то возможный ввод.) Но даже если сканер обнаружил символ новой строки, он не знает, как отправить синтаксическому анализатору "\n" , потому что у этого токена нет имени. Поскольку сканер не может отправить токен, правило никогда не может совпасть.

Вам нужно будет создать именованный токен новой строки и использовать его в этом правиле вместо "\n". И, конечно же, вам нужно будет заставить сканер отправлять этот токен при чтении новой строки.

Кстати, нет особого смысла давать токенам псевдоним, точно совпадающий с именем токена. Смысл псевдонимов в том, чтобы предоставить лучшие имена токенов в сообщениях об ошибках; если вы не дадите токену псевдоним, синтаксический анализатор будет использовать имя токена как есть. Таким образом, единственный смысл предоставления псевдонима — если он более удобочитаем, чем имя токена. Псевдонимы не имеют другого применения.

Villa
10 августа 2021 в 05:20
0

Ты обалденный! Извините, что не добавил driver.h. Ага! Собственно в этом и была проблема, я заменил 'symbol_type()' на 'make_GUION' и все заработало.