带有flex和bison的c ++扫描程序未定义对yylex和yytext的引用

时间:2017-09-18 23:11:07

标签: c++ bison flex-lexer

我试图制作一个简单的解析器来分析BNF语法作为使用flex和bison与c ++的输入。我得到了一些编译时错误。我在其他问题中搜索过类似的错误并修正了我的文件以匹配他们的文件,我仍然得到错误。

这是我的lex.l

%{
#include <iostream>
#include <string>
#define tkerror -1

#include "sintactic.tab.h"

using namespace std;
extern int row =1;
int col=0;
%}

%option caseful
%option noyywrap
%option yylineno
%option c++

ignora " "|\t|\n
ID [a-zA-Z]([a-zA-Z0-9_])*

%%

{ignora}+       {;}
"terminal"      {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkterminal;}
";"             {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkptocma;}
","             {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkcma;}
"no"            {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkno;}
"iniciar"       {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkiniciar;}
"con"           {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkcon;}
"="             {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkasignar;}
".rule"         {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkrule;}
"|"             {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkor;}
"%"             {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tksep;}
"EPSILON"       {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkeps;}
{ID}            {col = col + strlen(yylval.cad); strcpy(yylval.cad, yytext); return tkid;}
[\r\n]          {row++; col = 0;}
.           {return tkerror;}

%%

这是我的sintactic.y

%{
#include <iostream>
#include <string>
#include "lex.yy.cc"
using namespace std;

extern int row;
extern int yylineno;
extern int col;
extern char* yytext;

extern "C" int yylex();

int yyerror(const char* men)
{
   string output = yytext;
   std::cout<<"Error sintactico "<<output<<" linea "<<row<<" columna "<<col<<endl;

   return 0;
}

%}

%union{
    int entero;
    char cad [256];
}

%token<cad> TOK_EMPTY_LINE;

%token<cad> tkterminal;
%token<cad> tkptocma;
%token<cad> tkno;
%token<cad> tkiniciar;
%token<cad> tkcon;
%token<cad> tkasignar;
%token<cad> tkrule;
%token<cad> tkor;
%token<cad> tksep;
%token<cad> tkeps;
%token<cad> tkid;
%token<cad> tkcma;

%type<nodo> Lenguaje
%type<nodo> Area_Declaraciones;
%type<nodo> Area_NTInicial;
%type<nodo> Area_Gramatica;
%type<nodo> Lista_Declaraciones;
%type<nodo> Declaracion;
%type<nodo> Dec_Terminal;
%type<nodo> Dec_NoTerminal;
%type<nodo> Ids;
%type<nodo> Producciones;
%type<nodo> Produccion;
%type<nodo> Izquierda;
%type<nodo> Derecha;
%type<nodo> Id_Eps;

%%

Lenguaje: Area_Declaraciones tksep Area_NTInicial tksep Area_Gramatica;

Area_Declaraciones: Lista_Declaraciones;
Lista_Declaraciones: Declaracion Lista_Declaraciones
    | ;
Declaracion: Dec_Terminal
    | Dec_NoTerminal;
Dec_Terminal: tkterminal Ids tkptocma;
Dec_NoTerminal: tkno tkterminal Ids tkptocma;

Ids: tkid tkcma Ids
    | tkid;

Area_NTInicial: tkiniciar tkcon tkid tkptocma;

Area_Gramatica: Producciones;
Producciones: Produccion Producciones
    | ;
Produccion: Izquierda tkasignar Derecha tkptocma;
Izquierda: Ids tkrule;
Derecha: Id_Eps Derecha
    | Id_Eps
    | tkor Derecha;
Id_Eps: tkid
    | tkeps;

%%

使用

通过控制台进行编译
bison -d sintactic.y
flex lex.l
g++ sintactic.tab.c -lfl -o scanner.sh

我得到的错误是:

/usr/lib/x86_64-linux-gnu/libfl_pic.a(libmain.o): On the function `main':(.text.startup+0x9): undefined reference to `yylex'

/tmp/ccatti3x.o: On the function `yyerror(char const*)': sintactic.tab.c:(.text+0x23): undefined reference to `yytext[abi:cxx11]'

/tmp/ccatti3x.o: On the function `yyparse()': sintactic.tab.c:(.text+0x409): undefined reference to `yylex()'

collect2: error: ld returned 1 exit status

我可以将这些动作添加到sintactic.y之后,我可以毫无错误地编译它们。我已经看到了其他几乎和我一样的例子,他们似乎编译得很好。我不熟悉c ++或flex / bison,所以我不知道这些错误可能来自哪里。

1 个答案:

答案 0 :(得分:0)

  1. 删除%option c++。它根本没有帮助你。您可以使用C ++代码,包括C ++数据类型,但union成员的通常限制除外(这意味着您不能将任何与非平凡析构函数一起使用作为语义类型联合的一部分。)使用此选项会导致flex生成完全不同的API,不包括全局函数yylex。如果您愿意,可以使用此API - 它在flex手册中有记录 - 但您不会找到很多示例。使用标准C接口要容易得多,事实上,这也是您尝试使用的接口。

  2. 更改bison文件中yylex的声明
    extern "C" int yylex();
    

    int yylex();
    

    将其声明为C会更改其内部表示名称的方式;如果在某些C ++文件中将函数声明为extern "C",则必须在所有这些文件中执行此操作,包括定义它的函数(在这种情况下,是词汇扫描程序。)

    您可以通过定义YY_DECL宏将声明添加到flex文件中,但在这种情况下没有意义。将它编译为C ++函数更容易。

  3. extern中的extern int row = 1;并非真的有必要。如果在文件级声明int row = 1;,它将具有全局链接(即,您可以从不同的&#34;翻译单元&#34;(源文件)引用它。)您通常会使用{{1表示标识符是在不同的翻译单元中定义的;在这种情况下,您不会初始化标识符(因为它是在定义它的源文件中初始化的。)

  4. 请勿在您的野牛档案中extern。我见过教授推荐这种做法,以避免在C(或C ++)项目中教他们的学生多个翻译单元,但这是一个死胡同。学习如何进行单独编译;它将在任何优秀的C教科书中描述。 (或者只是将bison和flex生成的C文件的名称放在编译行中。)如果你在第1点中提出我的建议,那么flex生成的扫描器的默认名称将是#include "lex.yy.cc"但是不要#39; t依赖于此:使用flex lex.yy.c选项为生成的文件提供有意义的名称(如果要将其编译为C ++,则使用-o扩展名。)

    < / LI>
  5. 在您的野牛行动中使用.cc通常不是一个好主意,但如果您打算使用它(例如,在错误报告中),请确保将其声明为{{1 }}。

  6. 请勿在弹出操作中使用yytext。 Flex有助于将令牌的长度放在char*中,这样您就不必重新扫描以确定令牌的长度。

  7. 语义类型(strlen(yytext))中的大型固定长度缓冲区确实不是一个好主意。 (并且小的固定长度缓冲区甚至更糟。)首先,它们会破坏解析器堆栈的大小,因为每个堆栈槽都必须有缓冲区空间。此外,每次复制堆栈槽时都会复制整个缓冲区,这样会浪费周期。最重要的是,固定长度缓冲区只是要求麻烦:请参阅缓冲区溢出。动态分配内存以容纳令牌副本很容易。 (yyleng在大多数现代系统中已经足够了。虽然标准C库不需要char cad[250],但是Posix需要它。)棘手的部分是知道何时strdup(yytext)分配的内存,但是当你写下你的行为时,它应该是显而易见的。

  8. 通过不保存strdup等令牌的名称,可以节省很多关于动态分配和释放字符串的想法。您知道free()对应于字符串"iniciar",但您的解析器永远不会查询令牌的语义值。您应该只需要标识符令牌和文字常量的语义值(在数字常量的情况下,您可以使用tkiniciar或类似物来生成整数,而不是将字符串传递给bison。)

  9. 在flex中,如果要匹配单个空白字符,可以编写"iniciar"。 (除了空格,换行符和标签之外,它还会匹配strtod[[:space:]],但这不应该是一个问题。)您还可以使用\f,{{ 1}}和\v。 (这些是实际的字符类,因此您可以向类中添加更多内容。例如,[[:alpha:]]是字母,数字或下划线。)您不需要按顺序对字符类进行括号化重复它们;标识符模式可以是[[:digit:]]。有关详细信息,请参阅flex manual section on patterns

  10. 删除[[:alnum:]]。将[[:alnum:]_]中的负整数返回到bison解析器是Undefined Behavior,并且不会执行您想要的操作。相反,将[[:alpha:]_][[:alnum:]_]*声明为#define tkerror -1(没有语义类型),以便yylex能够返回它。 (结果将是语法错误,因为没有生产使用tkerror令牌。)

    如何命名您的令牌和非终端当然完全取决于您,但正常的方式是使用%token代币和yylex代表非终端。 (有些人喜欢像你一样大写,但我们大多数人都使用小写。)因为令牌(但不是非终端)最终成为生成代码中的标识符,所以它们必须符合C命名规则(或C ++规则) (视情况而定),它们不能与代码生成的其他标识符名称冲突(例如,包括flex文件中的启动条件和tkerror等弹性宏)。因此,使用ALL_CAPS之类的内容为令牌名称添加前缀有时很有用;特别是,lower_caseBEGIN非常常见。 TOK_对我来说似乎没用,而且在任何情况下你的flex文件都不会生成带有该名称的令牌,因此你也可以将其删除。