La siguiente receta explica como construir un analizador léxico con Flex y un analizador sintáctico con Bison, utilizando C++. Además, incluye un pequeño Makefile para el enlazado de todos los archivos y un ejemplo mínimo de uso.

Requisitos

  • bison++

El paquete bison++ instalará también lo necesario de flex.

Construcción

scanner.l

Este archivo es el que va contener la especificación Flex del analizador léxico. Si estudias el código siguiente verás
que el analizador es muy simple: sólo reconoce números enteros.

%option c++
%option noyywrap

%{
#include <sstream>
#include <cstdlib>
#include "parser.h"
using namespace std;
%}

DIGIT   [0-9]
DIGIT1  [1-9]

%%

{DIGIT1}{DIGIT}*  {
                   cout << "Lexer: " << yytext << endl;
                   return Parser::NUMBER;
                  }

.                 {
                   return Parser::UNKNOWN;
                  }

<<EOF>>           {
                   yyterminate();
                  }
%%

Es importante ver que el analizador sintáctico (“Parser”) ya conoce los tokens, por lo que el analizador léxico no tiene más que devolver el token encontrado.

parser.y

A continuación el analizador sintáctico, también sencillo, que sólo consta de una construcción válida para el lenguaje.

%name Parser
%define LSP_NEEDED
%define MEMBERS                 \
    virtual ~Parser()   {} \
    private:                   \
       yyFlexLexer lexer;
%define LEX_BODY {return lexer.yylex();}

%define ERROR_BODY {cerr << "error encountered at line: "<<lexer.lineno()<<" last word parsed:"<<lexer.YYText()<<"\n";}

%header{
#include < ostream >
#include < fstream >
#include < FlexLexer.h >
using namespace std;
%}

%union {
       int i_type;
}

%token UNKNOWN
%token < i_type > NUMBER

%type < i_type > number

%start number

%%
number

: NUMBER { $$ = atoi(lexer.YYText()); cout << "Parser value " << $$ << endl;}

;

%%

test.cc

Finalmente, el archivo de prueba que crea el objeto parser que lanzará el comienzo de análisis (léxico y sintáctico).

#include "parser.h"
#include < iostream >
using namespace std;

int main(int argc, char ** argv)
{
       Parser parser;
       parser.yyparse();
       return 0;
}

Makefile

Para compilar y enlazar todo es necesario seguir un orden específico. Como muestra el siguiente Makefile, es necesario mantener la consistencia de nombres y crear primero el parser, después el scanner y finalmente el programa de prueba.

all: test

parser.cc: parser.y
        bison++ -d -hparser.h -o $@ $<

scanner.cc: scanner.l
        flex++ -d -o$@ $<

test:  parser.o scanner.o test.o
        g++ -o $@ test.o parser.o scanner.o

clean:
        $(RM) *~ *# test *.o *.h
        $(RM) parser.cc scanner.cc

Referencias



blog comments powered by Disqus