A feladatgyüjtemény 3/1-es feladat b) részfeladatát oldjuk meg. C stílusú függvénydeklarációkhoz kell szintaktikus elemzöt csinálni.
Elsö lépés. Hozzuk létre a lexikális elemzöt. A következö lexikális elemekre lesz szükség: azonosító, nyitó zárójel, csukó zárójel, pontosvesszö, vesszö. Az fv.lex fájlba írjuk be ezeknek a definícióját:
%option noyywrap c++
%{
#include "Parser.h"
%}
LETTER [a-zA-Z]
NUMBER [0-9]
WHITESPACE [ \t\n]
%%
("_"|{LETTER})("_"|{LETTER}|{NUMBER})* return Parser::IDENTIFIER;
"," return Parser::COMMA;
"(" return Parser::OPENING_PAREN;
")" return Parser::CLOSING_PAREN;
";" return Parser::SEMICOLON;
{WHITESPACE}+ // Skip.
%%
Az egyes lexikális elemekhez egy "return Parser::XXX" utasítást kell írni. Így kommunikál a lexikális elemzö a szintaktikus elemzövel. Figyeljük meg, hogy be kellett include-olni egy Parser.h nevü fájlt. Ezt a bison fogja generálni.
Második lépés. Hozzuk létre a szintaktikus elemzöt. Az fv.y fájlba írjuk az alábbiakat:
%baseclass-preinclude <iostream>
%token IDENTIFIER COMMA OPENING_PAREN CLOSING_PAREN SEMICOLON
%%
start:
declarationList { std::cout << "Processing start symbol." << std::endl; }
;
declarationList:
|
declaration declarationList
;
declaration:
IDENTIFIER IDENTIFIER OPENING_PAREN parameterList CLOSING_PAREN SEMICOLON
|
IDENTIFIER IDENTIFIER OPENING_PAREN error CLOSING_PAREN SEMICOLON
{
std::cout << "Error in parameter list." << std::endl;
}
;
parameterList:
|
parameter parameterTail
;
parameterTail:
|
COMMA parameter parameterTail
;
parameter:
IDENTIFIER IDENTIFIER
;
A %token utasítás után sorolhatjuk fel a feldolgozható lexikális elemeket, amelyek a nyelvtan terminálisai lesznek. Ezeknek legyen ugyanaz a neve, mint amit a lexikális elemzönél megadtunk. A %% jel után következnek a nyelvtan szabályai. A bisonban a szabályokat általában leíró névvel szoktuk ellátni, nem egy-egy betüvel jelöljük öket, mint általában a formális nyelveknél. Ha a terminálisokat csupa nagybetüvel írjuk, akkor jól elkülönülnek a nemterminálisoktól. Alapértelmezésben az elsö szabály bal oldala lesz a nyelvtan kezdöszimbóluma (ebben a példában a "start"). Ez felülírható a %start direktívával. Az azonos nemterminálishoz tartozó szabályokat a | (pipe) karakter választja el egymástól, a szabályokon belül pedig fehér szóközök szeparálják a mondatforma elemeit.
Az egyes szabályokhoz akciókat is rendelhetünk, amelyeket kapcsos zárójelek között adhatunk meg. A fenti példában a kezdö szabályra való illeszkedéskor egy üzenetet jelenítünk meg a standard kimeneten. Ha egy mondatformában az "error" nemterminális fordul elö, akkor a szabály illeszkedni fog azokra a mondatokra, amelyekben a hiba az errorral jelzett részmondatban van. Így egyrészt az elemzés nem szakad meg, másrészt specifikus hibaüzenetek kiadására van lehetöség.
Harmadik lépés. A program belépési pontjának létrehozása. Hozzuk létrea a main.cc fájlt az alábbi tartalommal.
#include <iostream>
#include <fstream>
#include <sstream>
#include "Parser.h"
#include <FlexLexer.h>
using namespace std;
yyFlexLexer *fl;
int Parser::lex()
{
return fl->yylex();
}
int main( int argc, char* argv[] )
{
if( argc != 2 )
{
cerr << "Egy parancssori argumentum kell!" << endl;
return 1;
}
ifstream in( argv[1] );
if( !in )
{
cerr << "Nem tudom megnyitni: " << argv[1] << endl;
return 1;
}
fl = new yyFlexLexer(&in, &cout);
Parser pars;
pars.parse();
return 0;
}
A fenti belépési pont parancssori argumentumként várja annak a fájlnak a nevét, amelyben az elemzendö szöveg található. Szükség szerint ez a müködés értelemszerüen megváltoztatható.
Negyedik lépés. Fordítsuk egybe az eddigieket:
$ flex fv.lex $ bisonc++ fv.y $ g++ -o fv *.cc
Az elsö sor a lexikális elemzöt, a második sor a szintaktikus elemzöt hozza létre. A harmadik egybefordítja öket egy az "fv" nevü futtatható fájlba.
Ötödik lépés. A program futtatása. Hozzunk létre egy inputfájlt mondjuk input.txt néven, és futtassuk le rá az elemzöt.
$ ./fv input.txt
Részletes leírást a bison kézikönyvében találunk. A 3/1-es feladat mintamegoldásait is érdemes átnézni.