Bison

Bison on programm kontekstivabadele grammatikatele süntaksianalüsaatorite loomiseks. Bisoni sisendiks on kontekstivaba grammatika (transleeeritava programmeerimiskeele süntaksit kirjeldav grammatika): reeglid (produktsioonid), mitteterminalid (grammar groupings) ja terminalid (tokens). Nende põhjal genereerib Bison selle grammatika jaoks süntaksianalüsaatori, s.t. C-keeles kirjutatud programmi, mis iga selle grammatika poolt genereeritud sõna (programmeerimiskeeles kirjutatud programmi) jaoks leiab selle sõna derivatsioonipuu. Grammatika reeglitega saab siduda semantilisi tegevusi, seega on võimalik genereerida (näiteks) programeerimiskeele interpretaator või translaator.

Grammatika kirjeldus tuleb Bisonile esitada formaadis, mis meenutab Flex-i sisendit.

%{
C deklaratsioonid
%}
Bisoni deklaratsiooonid
%%
Grammatika reeglid
%%
Täiendav C-keelne programmiosa

Esimeses eraldajate %{, %} vahelises C deklaratsioonide osas defineeritakse muutujad ja makrod, mida hiljem kasutatakse grammatika reeglitele lisatud semantilistes tegevustes. Need deklaratsioonid kopeerib Bison genereeritava programmi algusesse; kui midagi pole deklareerida, võib eraldajad %{, %} ära jätta.

Bisonile esitatava grammatika terminalid (tokens) on skanneri (leksilise analüsaatori) poolt leitud lekseemid. Need esitatakse Bison-ile võtmesõna %token järel ja nad tuleb kirjutada suurtähtedega. Terminalidena võib kasutada ka tekstikonstante (need kirjutatakse nagu c-keele literalid): '+', '*' jne.

Grammatika mitteterminalid kirjutatakse väiketähdedega; neid eraldi ei loetleta, Bison tunneb nad ära grammatikareeglitest. Grammatika algussümboliks loetakse (vaikimisi) esimeses reeglis vasakul olev mitteterminal.

Vaatleme väikest näidet: interpretaatorit, mis arvutab sisestatud matemaatilise avaldise väärtuse. Avaldises kasutatakse vaid täisarve ja liitmist-lahutamist ja ta on määratud grammatikaga

sisend read #
read read rida | rida
rida   | avaldis ARVUTA
avaldis ARV | avaldis + ARV | avaldis - ARV |

Esimene reegel lubab kuitahes mitu rida; real on avaldis, kuid rida võib olla ka tühi; avaldise väärtus väljastatakse lekseemi (terminali) ARVUTA lugemisel (selle genereerib reavahetus sisendtekstis); sümbol # on töö lõpetamiseks.

Defineerime algul leksilise analüsaatori:

%option noyywrap
%{
%}
digit [0-9]
tyhikud [ |\t]
%%
"+" return(PLUS);
"-" return(MIINUS);
"\n" return(ARVUTA); /*rea lõpp - kalkulaator peab väljastama tulemuse */
{digit}+ return(ARV); /* täisarv */
{tyhikud} { }; /* tühikud ja tabulatsiooonimärgid real kustutatakse */
"#" return (KOIK); /* töö lõpetamiseks */
. return(VIGA);
%%
/* siin ei toimu midagi, peaprogramm on bison-i failis */

Selle teksti põhjal genereerib Flex leksilise analüsaatori teksti lex.yy.c, mis asendab sisendtekstis arvud lekseemiga ARV, märgid +, -, # vastavalt lekseemidega PLUS, MIINUS, KOIK ja reavahetused - lekseemiga ARVUTA (see on käsk real oleva avaldise väärtuse väljastamiseks)

Bisoni sisendtekstis calc.y muutub Flex-i poolt selle kirjelduse alusel genereeritud leksikaanalüsaatori C-keelne tekst lex.yy.c include-failiks:

%{
#include "lex.yy.c"
#include <stdio.h>
#include <math.h>
#define YYSTYPE int /* kõigi mitteterminalide tüüp tulen int */
%}

//Loetleme kõik terminalid (Flex-i lekseemid) :
%token PLUS MIINUS ARV ARVUTA VIGA KOIK

%left MIINUS PLUS /* määrame assotsiatiivsuse - kui järjest on mitu operatsiooni, algab nende sooritamine vasakult */

%%
/*järgnevad grammatika produktsioonid */ sisend: sisend avaldis ARVUTA { printf("Tulemus: %d\n",$2); }
| /* tühi rida */ ;
arv: ARV {$$ = atoi(yytext); /* numbrite jada teisendamine täisarvuks */}
avaldis: arv { $$=$1; /* printf("Integer: %d\n",atoi(yytext));*/}
| avaldis PLUS arv { $$ = $1 + $3; /* printf("Liitmine: %d = %d + %d\n",$$,$1, $3);*/ } | avaldis MIINUS arv { $$ = $1 - $3;/* printf("Lahutamine: %d = %d - %d\n",$$,$1, $3); */ } | KOIK { printf("Valmis.\n");return 0;} // return 0 lõpetab töö | ;

%%

yyerror(char *str)
{
printf("Viga: %s kohal %s\n",str,yytext);
/*exit(1);*/ }

main()
{
yyparse();
}

Siin loetletakse algul lekseemid (reserveeritud sõna %token järel) ja määratakse kõigi arvväärtust omavate lekseemide tüübiks int. Seejärel on loetletud grammatika reeglid (produktsioonid); produktsioonide tavalises kirjaviisis kasutatava asemele tuleb koolon : . Reeglid kirjeldatakse koos reeglile vastava semantilise tegevusega, mis on kirjeldatud figuursulgude { } vahel. Reeglile vastavas tegevustes on reeglis esinevate sümbolite (terminalide ja mitteterminalide) väärtused (vasakult paremale) tähistatud $$, $1, $2,.... Seega näiteks reeglis

avaldis : avaldis PLUS arv

on $$ = avaldis (esinemine reegli vasakul pool), $1 = avaldis (esimene esinemine paremal), $2 = PLUS, $3 = arv ja reeglis

arv: ARV { $$ = atoi(yytext); }

tähendab, et produktsiooni vasakul pool oleva mitteterminali arv väärtus saadakse paremal pool olevale lekseemile ARV vastava teksti yytext teisendamisel täisarvuks; mitteterminal arv on sisse toodud vaid selleks, et seda teisendust poleks vaja kirjeldada kõigis produktsioonides.

Reegli

avaldis: avaldis PLUS avaldis { $$ = $1 + $3; }

semantiline tegevus arvutab reegli vasakul pool oleva mitteterminali avaldis väärtuse, liites reegli paremal pool olevate esimese ja kolmanda mitteterminali avaldis väärtused jne.

Kui ülalesitatud Bison-i sisendtekst on salvestatud näiteks failis calc.y, siis käsk

bison calc.y

genereerib uue faili calc_tab.c (Bison lisab failinimele stringi "_tab" algusosa nii, et kogu failinime pikkus oleks < 9 sümbolit). Kui selle faili kompileerimisel gcc-ga määrata võtmega -ocalc parseri nimeks calc

gcc calc_tab.c -ocalc

saame faili calc.exe. Selle testimiseks võib kasutada näiteks faili calc_in.txt:

2+3-1
2-4-6
4
#

Kui käivitada calc.exe selle sisendiga

calc < calc_in.txt > calc_out.txt

tekib väljundfailis calc_out.txt järgmine tekst:

Tulemus: 4
Tulemus: -8
Tulemus: 4
Valmis.


Ülesandeid:
1. Täienda ülalesitatud kalkulaatorit nii, et avaldistes saaks kasutada ka korrutamist, jagamist ja astendamist; kuna jagamisel saadakse tulemuseks tavaliselt reaalarv, peab kõigi lekseemide ja mitteterminalide tüübiks YYSTYPE deklaratsioonis olema double; vastavalt peab muutma ka arvudest lekseemi moodustamist.
2. Täienda ülalesitatud kalkulaatorit nii, et arvutamine algab kas reavahetusel või avaldise lõpus oleva märgi '=' järel; sel juhul võib real olla ka mitu avaldist .
3. Peatüki "Grammatikad" lõpus on ülesandes 2 esitatud tunniplaani formaat. Koosta Flex-i ja Bison-i abil programm, mis sellises formaadis esitatud sisendist filtreeriks vaid tudengi jaoks olulise: päeva, leongu/harjutuse algusaja, õpeaine ja ruumi, seega seal esitatud näidissisend peaks teisenema selliseks:

Esmaspäev 8.00 Loogiline Programmeerimine AK-140  
Esmaspäev 10.00 Loogiline Programmeerimine AK-111  
Kolmapäev 8.00 Translaatorite koostamine AK-140

4. Koosta Flex-i ja Bison-i abil programm, mis arvutaks ühendsüsteemis esitatud arvude (vt peatüki "Grammatika" ülesanded 6-8) ja operatsioonide + - abil koostatud avaldiste väärtused. Sisendis on avaldise lõpus võrdusmärk = ja programm peab pärast võrdusmärgi lugemist väljastama avaldise väärtuse; avaldised on üksteisest eraldatud kas komadega (pärast võrdusmärki = ) või reavahetustega. Kui sisend oleks näiteks

ll + ll = , lllll - lll + l=
llll =

peab programm väljastama

2 + 2 = 4, 5 - 3 + 1 = 3
4 = 4

NB - Kriipsuna ei või kasutada sümbolit "|" - Flex/Bison tahab seda interpreteerida!
5. Koosta Flex-i ja Bison-i abil programm, mis kontrollib peatüki "Grammatikad" ülesandes 5. kirjeldatud linnuvaatleja päevaprotokolli süntaktilist korrektsust ja arvutab iga linnu jaoks vastava rea summa, seega kui sisend oleks

Kuldnokk - 5,||,|,3
Vares - 3,10,|
Jaanalind
Hani - 1000,||

siis peab programm väljastama

Kuldnokk - 11
Vares - 14
Jaanalind - 0
Hani - 1002


Küsimused, probleemid: ©2004 Jaak Henno