"Syntax Error" 在 Bison 中没有解释
"Syntax Error" in Bison without explanation
我正在开发控制台应用程序。为了创建解释器,我使用了 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();
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();
m.assignParameters();
}
;
lista_param : lista_param parametro
{
printf("Lista de parametros\n");
$$=;
$$.push_back();
}
| parametro
{
printf("parametro individual\n");
vector<Parametro> params;
params.push_back();
$$ = params;
}
;
parametro : GUION nom_param IGUAL atributo
{
printf("Quinto nivel del arbol\n");
Parametro param;
param.setNombre();
param.setValor();
$$ = param;
}
;
nom_param : SIZE { $$=; }
| F { $$=; }
| PATH { $$=; }
| U { $$=; }
;
atributo : NUM { $$=; }
| BF { $$=; }
| FF { $$=; }
| WF { $$=; }
| K { $$=; }
| M { $$=; }
| RUTA { $$=; }
;
%%
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);
}
虽然您的问题中没有包含 driver.cc
或 driver.hh
,但我怀疑它们是从 Bison manual 中的示例 C++ 代码改编而来的。该代码允许您使用命令行标志启用扫描器或解析器跟踪。如果您没有包含示例代码的那部分,我强烈建议您将其放回原处并启用跟踪。您会发现更容易看到正在发生的事情。
这里的直接问题是,当您的扫描器看到 -
时,它会执行操作:
"-" { 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"
。而且,当然,您必须让扫描器在读取换行符时发送该令牌。
顺便说一句,给令牌一个与令牌名称完全相同的别名几乎没有意义。别名的目的是在错误消息中提供更好的标记名称;如果您不给令牌别名,解析器将按原样使用令牌名称。因此,提供别名的唯一要点是它是否比令牌名称更具可读性。别名没有任何其他用途。
我正在开发控制台应用程序。为了创建解释器,我使用了 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();
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();
m.assignParameters();
}
;
lista_param : lista_param parametro
{
printf("Lista de parametros\n");
$$=;
$$.push_back();
}
| parametro
{
printf("parametro individual\n");
vector<Parametro> params;
params.push_back();
$$ = params;
}
;
parametro : GUION nom_param IGUAL atributo
{
printf("Quinto nivel del arbol\n");
Parametro param;
param.setNombre();
param.setValor();
$$ = param;
}
;
nom_param : SIZE { $$=; }
| F { $$=; }
| PATH { $$=; }
| U { $$=; }
;
atributo : NUM { $$=; }
| BF { $$=; }
| FF { $$=; }
| WF { $$=; }
| K { $$=; }
| M { $$=; }
| RUTA { $$=; }
;
%%
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);
}
虽然您的问题中没有包含 driver.cc
或 driver.hh
,但我怀疑它们是从 Bison manual 中的示例 C++ 代码改编而来的。该代码允许您使用命令行标志启用扫描器或解析器跟踪。如果您没有包含示例代码的那部分,我强烈建议您将其放回原处并启用跟踪。您会发现更容易看到正在发生的事情。
这里的直接问题是,当您的扫描器看到 -
时,它会执行操作:
"-" { 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"
。而且,当然,您必须让扫描器在读取换行符时发送该令牌。
顺便说一句,给令牌一个与令牌名称完全相同的别名几乎没有意义。别名的目的是在错误消息中提供更好的标记名称;如果您不给令牌别名,解析器将按原样使用令牌名称。因此,提供别名的唯一要点是它是否比令牌名称更具可读性。别名没有任何其他用途。