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.