Fordítóprogramok: Lex és YACC

Hasonló dokumentumok
Bisonc++ tutorial. Dévai Gergely. A szabály bal- és jobboldalát : választja el egymástól. A szabályalternatívák sorozatát ; zárja le.

Java II. I A Java programozási nyelv alapelemei

AWK programozás, minták, vezérlési szerkezetek

A C programozási nyelv I. Bevezetés

Programozás alapjai gyakorlat. 4. gyakorlat Konstansok, tömbök, stringek

Flex tutorial. Dévai Gergely

A C programozási nyelv I. Bevezetés

AWK programozás Bevezetés

AWK programozás, minták, vezérlési szerkezetek

Programozás II. 2. Dr. Iványi Péter

Bevezetés a programozásba I.

Programozás alapjai gyakorlat. 2. gyakorlat C alapok

Függvény pointer. Feladat: Egy tömbben soroljunk fel függvényeket, és hívjuk meg valahányszor.

Programozás alapjai C nyelv 8. gyakorlat. Mutatók és címek (ism.) Indirekció (ism)

C programozás. 6 óra Függvények, függvényszerű makrók, globális és

Mutatók és címek (ism.) Programozás alapjai C nyelv 8. gyakorlat. Indirekció (ism) Néhány dolog érthetőbb (ism.) Változók a memóriában

Programozás C és C++ -ban

Fordítóprogramok (A,C,T szakirány) Feladatgy jtemény

Lekérdezések az SQL SELECT utasítással. Copyright 2004, Oracle. All rights reserved.

Java II. I A Java programozási nyelv alapelemei

C programozási nyelv

BASH SCRIPT SHELL JEGYZETEK

Fordítóprogramok. és formális nyelvek

Karakterkészlet. A kis- és nagybetűk nem különböznek, a sztringliterálok belsejét leszámítva!

Programozás 6. Dr. Iványi Péter

7. fejezet: Mutatók és tömbök

C++ programozási nyelv Konstruktorok-destruktorok

A C programozási nyelv V. Struktúra Dinamikus memóriakezelés

Programozás I. gyakorlat

Szoftvertervezés és -fejlesztés I.

Emlékeztető: LR(0) elemzés. LR elemzések (SLR(1) és LR(1) elemzések)

Operációs rendszerek. 11. gyakorlat. AWK - szintaxis, vezérlési szerkezetek UNIVERSITAS SCIENTIARUM SZEGEDIENSIS UNIVERSITY OF SZEGED

1. Alapok. #!/bin/bash

Operációs Rendszerek Gyakorlat Triviális segédfeladatok június PERL Tömbök és hashek Feladat: május 26-i beugró

Bevezetés a programozásba I.

Járműfedélzeti rendszerek II. 3. előadás Dr. Bécsi Tamás

Programozás I gyakorlat

Szkriptnyelvek. 1. UNIX shell

A C programozási nyelv VI. Parancssori argumentumok File kezelés

sallang avagy Fordítótervezés dióhéjban Sallai Gyula

Occam 1. Készítette: Szabó Éva

HORVÁTH ZSÓFIA 1. Beadandó feladat (HOZSAAI.ELTE) ápr 7. 8-as csoport

Vezérlési szerkezetek

Formális nyelvek és automaták

Pénzügyi algoritmusok

Statisztikai függvények

Járműfedélzeti rendszerek II. 2. előadás Dr. Bécsi Tamás

Máté: Assembly programozás

Szövegek C++ -ban, a string osztály

Programozás C++ -ban

Programozás I. 5. Előadás: Függvények

Függvények. Programozás I. Hatwágner F. Miklós november 16. Széchenyi István Egyetem, Gy r

OOP I. Egyszerő algoritmusok és leírásuk. Készítette: Dr. Kotsis Domokos

1. Jelölje meg az összes igaz állítást a következők közül!

Forráskód formázási szabályok

Járműfedélzeti rendszerek II. 4. előadás Dr. Bécsi Tamás

C programozási nyelv Pointerek, tömbök, pointer aritmetika

Kifejezések. Kozsik Tamás. December 11, 2016

12. gyakorlat Enum; Tárolási osztályok Preprocesszor utasítások; Moduláris programozás

Segédanyagok. Formális nyelvek a gyakorlatban. Szintaktikai helyesség. Fordítóprogramok. Formális nyelvek, 1. gyakorlat

1.1. A forrásprogramok felépítése Nevek és kulcsszavak Alapvető típusok. C programozás 3

Programozás alapjai C nyelv 4. gyakorlat. Mit tudunk már? Feltételes operátor (?:) Típus fogalma char, int, float, double

Mechatronika és mikroszámítógépek 2017/2018 I. félév. Bevezetés a C nyelvbe

A szemantikus elemzés helye. A szemantikus elemzés feladatai. A szemantikus elemzés feladatai. Deklarációk és láthatósági szabályok

Fordítóprogramok. Aszalós László szeptember 7.

Objektumorientált programozás Pál László. Sapientia EMTE, Csíkszereda, 2014/2015

Kifejezések. Kozsik Tamás. December 11, 2016

Memóriagazdálkodás. Kódgenerálás. Kódoptimalizálás

Programozás alapjai. 5. előadás

Programozás C nyelven FELÜLNÉZETBŐL elhullatott MORZSÁK. Sapientia EMTE

SQL*Plus. Felhasználók: SYS: rendszergazda SCOTT: demonstrációs adatbázis, táblái: EMP (dolgozó), DEPT (osztály) "közönséges" felhasználók

Függvények. Programozás alapjai C nyelv 7. gyakorlat. LNKO függvény. Függvények(2) LNKO függvény (2) LNKO függvény (3)

Programozás alapjai C nyelv 7. gyakorlat. Függvények. Függvények(2)

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

A C programozási nyelv III. Pointerek és tömbök.

INFORMATIKA javítókulcs 2016

A programozás alapjai 1 Rekurzió

3. Osztályok II. Programozás II

Készítette: Nagy Tibor István

Operációs Rendszerek II. labor. 2. alkalom

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

S z á m í t ó g é p e s a l a p i s m e r e t e k

Programozási nyelvek (ADA)

A C programozási nyelv III. Pointerek és tömbök.

Fordító részei. Fordító részei. Kód visszafejtés. Izsó Tamás szeptember 29. Izsó Tamás Fordító részei / 1

Rekurzió. Dr. Iványi Péter

1. Alapok. Programozás II

Bevezetés a programozásba II. 5. Előadás: Másoló konstruktor, túlterhelés, operátorok

A függvény kód szekvenciáját kapcsos zárójelek közt definiáljuk, a { } -ek közti részt a Bash héj kód blokknak (code block) nevezi.

Webprogramozás szakkör

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

Objektumorientált programozás C# nyelven

Mintavételes szabályozás mikrovezérlő segítségével

A C++ nyelvben a függvény nevek túlterhelésével biztonságosabbá tehetnénk az adatok kiírását és beolvasását.

Programozás C++ -ban 2007/7

Objektum elvű alkalmazások fejlesztése Kifejezés lengyel formára hozása és kiértékelése

Turing-gép május 31. Turing-gép 1. 1

Alprogramok, paraméterátadás

file./script.sh > Bourne-Again shell script text executable << tartalmat néz >>

Informatika szigorlat. A lexikális elemző feladatai közé tartozik a whitespace karakterek (a

Átírás:

Fordítóprogramok: Lex és YACC Készítette: Bartos Gábor Budapest, 2005

Tartalom 1. Bevezetés... 3 2. Lex... 3 2.1 Metakarakterek... 3 2.2 Szerkezet... 4 2.2.1 Definíciós rész... 4 2.2.2 Fordítási szabályok... 5 2.2.3 Felhasználói programok... 5 2.3 Számológép példa... 5 2.4 Tokenek... 6 2.5 Egy string lexikális elemzése... 6 2.6 Kimenet... 7 2.7 Fontosabb hibaüzenetek... 8 3. YACC... 8 3.1 A számológépes példa folytatása... 8 3.2 yylval típusa... 10 3.3 Deklaráció... 11 3.4 Hibakezelés... 11 3.5 Fontosabb kapcsolók... 12 3.6 Fontosabb hibaüzenetek... 12 4. Lex és YACC együttmőködése... 13 5. Egy-két tanács a kezdetekhez... 13

1. Bevezetés A lexikális elemzés egy determinisztikus véges automatával valósítható meg. A lex egy olyan program, amely a reguláris kifejezések formális leírásából hozza létre az automata implementációját, vagyis egy lexikális elemzı programot generál. A lex C kódot generál egy lexikális elemzıhöz. Mintákat használ, hogy a bemenı stringekbıl tokeneket készítsen. A token a string számszerő reprezentációja, amely megkönnyíti a feldolgozást. A YACC C kódot generál szintaktikus elemzıhöz. Nyelvtani szabályokat használ a lex tokenjeinek elemzéséhez és a szintaxisfa felépítéséhez. A szintaxisfa segítségével történik a kódgenerálás. 2. Lex Ha egy nyelvet akarunk írni bármihez, akkor elıször meg kell határozzuk, hogy milyen szavakból, jelekbıl épül fel a készítendı nyelv. Ezeket reguláris kifejezésekkel határozzuk meg. 2.1 Metakarakterek A reguláris kifejezés leírásához használt karakterek és jelentésük: \n új sor karakter. bármilyen karakter, kivéve az új sor karakter * 0,1,2 szeres ismétlése az elıtte levı kifejezésnek + 1,2 szeres ismétlése az elıtte levı kifejezésnek? 0, vagy 1-szeres ismétlése az elıtte levı kifejezésnek ^ sor kezdete $ sor vége a b a vagy b aaa string literál [] karakter osztály Például: delim [ \t\n] A jobb oldalon egy karakter osztályt látunk, ami most azt jelenti, hogy a szóközt, a tabulátort és a sorvége jelet együttesen a bal oldali szóval akarjuk elnevezni, ez esetben delim -mel. Ennek segítségével definiáljuk például a whitespace-t, ami a nyelvünk szavait elválaszthatja: whitespace {delim}* Tehát a whitespace a fenn definiált karakterek tetszıleges hosszú sorozatából állhat (akár 0 hosszú sorozatából is!). A {}-nek van egy másik jelentése is: a{1,5} az a 1 és 5 közötti számú elıfordulásait jelenti A ^ karakterosztályban negálást jelent, ha az az elsı karakter tehát: [^ab] = bármi, kivéve a, vagy b, DE

[a^b] = a vagy ^ vagy b! Figyelni kell még a karakter osztályban használt - -ra is: Pl.: [-ab] Ha a -t, b -t, vagy - -t foglalja egybe. A mínusz szándékosan került elıre, mivel az [a-z] azt jelentené, hogy a-tól z-ig minden karakter megengedett. Példa a sor vége jel használatára (mármint a $): SzövegASorVégén szöveg$ Ez az jelenti, hogy ha a szöveget közvetlenül a sor vége elıtt találja meg, akkor SzövegASorVégén token ad tovább. 2.2 Szerkezet A lex bemenete három részbıl áll: definíciók fordítási szabályok felhasználói programok 2.2.1 Definíciós rész A definíciós rész név és reguláris kifejezés párokból áll, ahol a név a reguláris kifejezés azonosítója. A nevet és a reguláris kifejezést whitespace kell elválassza egymástól (vagyis szóköz vagy tab). Pl.: digit [0-9] delim [ \t\n] whitespace {delim}* number {digit}+(\.{digit}+)?(e[\+\-]?{digit}+)? E utóbbi lehet, hogy már magyarázatra szorul: {digit}+ legalább egy számjeggyel kezdıdik (\.{digit}+)? (A kérdıjel miatt ez a rész elhagyható.) Ha nem hagyjuk el, akkor a. után legalább egy számjeggyel folytatódik. (E[\+\-]?{digit}+)? (Szintén nem kötelezı.) E -vel folytatódik, igény szerint elıjel tehetı, majd ismét számjegyek sorozata jön. Vigyázzunk arra, hogy ne lehessen üres, 0 hosszú kifejezéseket létrehozni vele, pl.: {delim}* (Itt nulla hosszúságú whitespace is megengedett lenne, amit a lex nem fog szeretni.) A definíciós részben a minták az elsı oszlopban kell kezdıdjenek. Minden, ami nem az elsı oszlopban kezdıdik, az átmásolódik generált C file-ba. A név reguláris párokon kívül még tartalmazhat olyan részeket, amelyet a lex automatikusan beleír majd az általa generált C kódba. Az átmásolandó kódrészleteket %{ és %} közé kell elhelyezni.

2.2.2 Fordítási szabályok A fordítási szabályok rész reguláris kifejezés és akció párokból épül fel. Pl.: {whitespace} {/*semmit nem csinálunk*/} (i I)(f F) {return IF;} Lex által használt változók: yytext egy nulla végő string yyleng az egyezı string hossza yyout a kimeneti file, alapértelmezésben stdout yyin a fentihez hasonlóan a bemeneti file yylval a tokenhez rendelt érték Ezeket a változókat felhasználhatjuk a fordítási szabályokban, pl.: [0-9]+ { yylval = atoi(yytext); } Van egy fontos makró a lexben, ami még a segítségünkre lehet: ECHO. Az ECHO segítségével a bekapott karaktert változtatás nélkül egyszerően továbbküldjük a kimenetre: /* match everything except newline */. ECHO; /* match newline */ \n ECHO; 2.2.3 Felhasználói programok A harmadik részben a második részben meghívott C függvények implementációját valósíthatjuk meg. 2.3 Számológép példa Szeretnénk írni egy számológépet. Kéne legyen benne összeadás, kivonás, szorzás, osztás, jó lenne engedni a zárójelezést. Nem csak számokat, de változókat tegyünk bele! %{ %} [a-z] { } #include <stdlib.h> void yyerror(char *); #include "y.tab.h" /* variables */ yylval = *yytext - 'a'; return VARIABLE; /* integers */

[0-9]+ { yylval = atoi(yytext); return INTEGER; } /* operators */ [-+()=/*\n] { return *yytext; } /* skip whitespace */ [ \t] ; /* anything else is an error */. yyerror("invalid character"); int yywrap(void) { return 1; } Korábban említettük, hogy a %{ és %} olyan kódot írunk, amelyet szeretnénk, ha a lex automatikusan beszúrna a generálandó kódba. Ezúttal néhány #include kerül beszúrásra, valamint az yyerror() függvény deklarációja. Ez lesz majd az a függvény, amit az elkészült fordítónk meghív, ha a kapott szöveggel baj van. Ezután a fordítási szabályok részben ledefiniáljuk azt, hogy mit nevezünk változónak, számnak, operátornak (a definíciós részben nem neveztük el külön ıket, mert nem volt rá szükségünk) és meghatározzuk, hogy mit tegyen a fordítónk, ha ilyeneket talál. A definíciónk szerint a változó pontosan egy kis betőbıl állhat. Meghatározzuk az értékét és VARIABLE kulcsszót küldünk tovább a kimenetre. A számaink tetszıleges (nem nulla) számú számjegybıl állhatnak. Felsoroltuk még az operátorokat. Hozzávettük még a sorvége jelet is, de nem azért mert az operátor lenne, hanem azért, mert csakúgy mint az operátorokat, a sorvége jelet is szó nélkül továbbküldjük a kimenetre. Ha tabbal találkozunk, akkor azt kihagyjuk, arra semmi szükségünk. A. segítségével elérjük, hogy ha bármi mást kapunk, azt hibának vegye majd a fordítónk. Végül a felhasználói programok részben egyetlen függvényt definiálok. Az yywrap() függvény EOF-nál hívódik meg és esetleg megnyithatunk másik file-t olvasásra (és visszatérünk 0-val), vagy jelezzük, hogy ennyi volt és return 1-gyel térünk vissza. 2.4 Tokenek A lexer az összes készített tokennek azonosítót ad. Ha a verem tetején talált karaktersorozat több definiált reguláris kifejezésnek is megfelel, akkor a leghosszabb egyezést választja, ha a hosszak is megegyeznek, akkor az elsıt. A generált tokenek kb. 258-tól indulnak, mert minden karakternek le van foglalva az ASCII kódjának megfelelı azonosító, ezenkívül még van egy-két lefoglalt token, pl.: EOF. 2.5 Egy string lexikális elemzése Ha a fordítónkkal szeretnénk stringeket is kezelni, akkor meg kell vizsgálni, hogy a bemeneti fileba írt string nem lóg-e át a következı sorba és ha macskakörömmel kezdıdik, akkor azzal fejezıdik-e be. Erre ad lehetıséget a következı példa: %{

char buf[100]; char *s; %} %x STRING \" { BEGIN STRING; s = buf; } <STRING>\\n { *s++ = '\n'; } <STRING>\\t { *s++ = '\t'; } <STRING>\\\" { *s++ = '\"'; } <STRING>\" { *s = 0; BEGIN 0; printf("found '%s'\n", buf); } <STRING>\n { printf("invalid string"); exit(1); } <STRING>. { *s++ = *yytext; } Elıször definiálunk egy 100-as karakter tömböt, és egy char*-ot a tömbben való lépegetéshez. Ezután létrehozunk egy STRING nevő állapotot. Késıbb ha macskakörmöt találunk, akkor átváltunk ebbe az állapotba. Megadhatjuk, hogy lex hogyan viselkedjen ebben a megváltozott állapotában. Ha ezután újabb macskakörömmel találkozunk, akkor a string helyes volt, visszatérhetünk az eredeti állapotba, egyébként valami hibaüzenetet adunk. Lássuk ezt részletesebben: %x STRING utasítás definiál egy új állapotot. Ha \ et olvasunk, akkor át kéne lépni egy az általunk definiált új állapotba. Ezt tesszük a BEGIN STRING utasítással. <STRING>\n feltétel akkor teljesül, ha a STRING állapotban vagyunk és \n -t olvastunk. Ekkor természetesen hibát kell kiírnunk, mert a karaktersorozat közepén nem engedünk sortörést. Ha STRING állapotban vagyunk és macskakörmöt olvasunk, akkor tesszük amit tennünk kell, valamint visszaállítjuk az eredeti állapotot, hogy a kód többi részét normálisan elemezhessük. Megjegyzés A %x valami szabállyal elérhettük, hogy az új állapotban csak az állapot feltételeire figyeljen. %s valami szabállyal aktiválhatjuk úgy a valami állapotot, hogy a régi is megmarad. Ezután azok a feltételek is kiértékelésre kerülnek, ahol nincs állapot meghatározva, az is, ahol van. Ezt fokozni is lehet: %s state1 state2 állapotok deklarálása után lehet akár <state1, state2> feltételt megadhatunk. 2.6 Kimenet A lex tehát egy C-s forráskódot készít az lex.yy.c fileba. A kimeneti file nevét megváltoztathatjuk, ha a lexet a t kapcsolóval hívjuk.

2.7 Fontosabb hibaüzenetek Rule cannot be matched: Ez az üzenet akkor jelenik meg, amikor egy szabály alkalmazásához soha nem fog elérni, mert egy enyhébb szabály elızi meg. Pl: [a-z]+ {return valami;} szoveg {return valami_más} Ebben az esetben a szoveg -re soha nem fog eljutni, mivel elıtte az [a-z]+ szabályt alkalmazza, akkor is ha szoveg szót talál. 3. YACC A YACC a megadott nyelvtan file-ból létrehoz egy táblázatot egy LALR(1) számára. A nyelvtan lehet nem egyértelmő, ezt a precedencia szabályok teszik egyértelmővé (remélhetıleg). A bemeneti file itt is 3 részre bomlik, pont mint a lexben: definíciók fordítási szabályok felhasználói programok A definíciós részben token definíciók (lásd a számológépes példát néhány sorral lejjebb) és a generált file-ba helyezendı C kód kerülhet (a C kód ismét %{ és %} közé). A fordítási szabályok részbe kerül a nyelvtan leírása. 3.1 A számológépes példa folytatása Most írjuk meg a nyelvtant is a szintaktikus elemzéshez. %token INTEGER VARIABLE %left '+' '-' %left '*' '/' %{ #include <stdio.h> void yyerror(char *); int yylex(void); int sym[26]; %} program: program statement '\n' ; statement: expr { printf("%d\n", $1); } VARIABLE '=' expr { sym[$1] = $3; }

; expr: INTEGER VARIABLE { $$ = sym[$1]; } expr '+' expr { $$ = $1 + $3; } expr '-' expr { $$ = $1 - $3; } expr '*' expr { $$ = $1 * $3; } expr '/' expr { $$ = $1 / $3; } '(' expr ')' { $$ = $2; } ; void yyerror(char *s) { fprintf(stderr, "%s\n", s); return 0; } int main(void) { yyparse(); return 0; } Elıször definiálunk két tokent, a változót és az egészt (csak egész számokkal dolgozunk). Ezután beállítjuk az operátorok asszociativitását: %left bal asszociatív %right jobb asszociatív %nonassoc nem asszociatív Az sorrend fontos! Az utolsónak definiált operátorok a legnagyobb precedenciájúak. Ezután a kódba automatikusan generálandó függvény deklarációk és változók definiálása következik. (stdio.h-t az yyerror függvényben használt fprintf miatt). A fordítási szabályok feldolgozása az órán tanultaknak megfelelıen egy dupla veremmel történik, (vagyis két veremmel, amelyek mindig konzisztensek maradnak egymáshoz képest, de a lényegen ez nem változtat). Tehát van például egy expr: expr * expr {$$ = $1 * $3;} szabályunk. Ha talál a terminálisokat és nyelvtani jeleket tartalmazó verem tetején ennek megfelelı elemeket, akkor kiveszi az elsı expr-et, a * -ot és a másik expr-et, majd egy expret rak vissza (hiszen ez van a bal oldalán a kettıspont elıtt). Mi pedig megadjuk a kapcsos zárójelek között, hogy a verembıl kivett elsı és harmadik értéket szorozza össze (a második a *, ezzel semmit nem teszünk), majd rakja vissza a verembe a szorzat eredményét (ezt teszi a $$= $1 * $3 utasítás). Az elsı fordítási szabály balrekurzió segítségével azt fejezi ki, hogy a program 0, 1, 2 állításból áll, az állítások végén sorvége jellel. Amikor a sorvége jelet észrevesszük, akkor kiírjuk az állítást. Megjegyzés: Technikai okokból fordítási szabály bal oldalán ne vizsgáljunk soha \0 (NULL) karaktert! Megjegyzés:

Ha választhatunk bal vagy jobb rekurzió között, mindig balrekurziót válasszunk, mert a jobb rekurzió nekiáll verembe pakolni, majd amikor kilép a legbelsı rekurzióból, akkor áll neki redukálni, ami elég megerıltetı szegény vermünk számára. Az állítás lehet egy kifejezés, ekkor kiírjuk az értékét, de lehet egy változó = kifejezés is. Emlékszünk mit tettünk a változókkal a lexnél? A változót egy egybetős karakternek definiáltuk. A lexben az azonosítóját úgy adtuk meg, hogy a változó ASCII kódjából kivontuk az a -t. Ezzel pont egy 0 és 25 közti értéket kaptunk, majd most a változóknak értékeinek tárolására létrehoztunk egy tömböt és ha az elıbb említett értékadást olvassuk ki a verembıl, akkor a változó azonosítójának megfelelı helyre beírjuk a tömbbe a változónak adandó értéket. A kifejezés lehet egy szám, ekkor semmit nem teszünk vele. Lehet egy változó is, ekkor az értékét adjuk vissza. Ha valamilyen mőveletet találunk, akkor elvégezzük. Ha zárójelben raboskodó kifejezést találunk, akkor kiszabadítjuk. Végül a felhasználói programok részben megírjuk, a hibakezelı függvényt, egyenlıre csak a megkapott hibaüzenetet kiírja, valamint megírjuk a main függvényt. A main egyszerően meghívja az yyparse() függvényt, ami az egész lexikális és szintaktikai ellenırzést végzi. Megjegyzés Természetesen a main függvényben ennél jóval többet tehetünk, például gondoskodhatunk arról, hogy az argumentumban kapott file-t megnyissunk és a fordítónk majd onnan olvassa be a fordítandó kódot: {% extern FILE *yyin; %} int main(int argc, char *argv[]) { yyin = fopen(argv[1], "r"); yyparse(); fclose(yyin); return 0; } Ráadásul az yyparse() 1-gyel tér vissza, ha valami kezelhetetlen hibát talált. 3.2 yylval típusa Az yylval típusa alapértelmezésben egész szám. Gyakran van azonban olyan, hogy nem csak egészekkel kívánunk dolgozni, sıt több típusú információt is el kéne tudni tárolni. Természetesen ekkor az yylval típusát meg kéne változtatni. Ezt könnyedén megtehetjük, ha létrehozunk egy unió típust, pl.: %union { int ivalue; /* integer value */ char sindex; /* symbol table index */ nodetype *nptr; /* node pointer */ }; Ez után az unió elemeit hozzáköthetjük a megfelelı tokenekhez és típusokhoz, pl.:

%token <ivalue> INTEGER %type <nptr> expr Az elsı sor az INTEGER tokent hozzáköti az unió ivalue eleméhez, míg a második sor hasonlóan mőködik az expr nem token kifejezéssel. A kötés fontos például az alábbihoz hasonló utasításokhoz: expr: INTEGER {printf( value: %d, $1);} INTEGER-t találtunk a verem tetején és csak azért hivatkozhatunk az értékére ($1-re) számként (vagyis %d-vel), mert a kötést megtettünk. Megjegyzés: Akik nem ismerik olyan jól a C nyelvet, azok számára érdemes megemlíteni, hogy pl. bool típust ne is próbáljunk létrehozni, használjunk helyette int-et. 3.3 Deklaráció Tegyük fel, hogy a fordítóprogramunkban a deklarációt úgy képzeljük el, hogy típus változólista. Ekkor, a változó listát ki kell fejteni és minden változónak meg kell tudjuk adni a típusát, valahogy így: decl: type varlist type: INT FLOAT varlist: VAR { settype($1, $0); } varlist ',' VAR { settype($3, $0); } Amikor változó listát találunk a veremben (varlist), akkor az rekurzióval kifejtjük, de hol van addigra már, hogy mi volt a típus? Közvetlenül a kivett elemek elıtt, hiszen típus változólista formában határoztuk meg. Emiatt ha az elsı tag a varlist, második a vesszı, harmadik pedig a VAR, akkor a 0. a típusa! Tehát $3-nak a $0 típust kell adni. 3.4 Hibakezelés Amikor a fordító programunk valamilyen hibát észlel, akkor nem célszerő, hogy azonnal leálljon. Jobb, ha megpróbálja túltenni magát a hibán és végignézi a fordítandó kód végét is. Pl.: utasítás: változó értékadás kifejezés ; {DoSomething();} ; Ha a fordítónknak beadott kódban ezek után írunk egy utasítást, de elfelejtünk pontosvesszıt tenni a végére, akkor kiírja nekünk fordítás közben, hogy syntax error, majd abbahagyja a fordítást. Ahhoz, hogy tovább fordítson, nekünk kell lekezelni a hibát, például a következı képpen: utasítás: változó értékadás kifejezés ; {DoSomething();} változó értékadás kifejezés {yyerror( Hiányzik a ;. );} ; Ha egy hasonló helyzetben találunk egy hibát, de nem a szöveg végén, hanem mondjuk kimarad az értékadás jel, akkor az utasítást már nem kell ellenırizni (hiszen hibás), írjunk ki hibaüzenetet, de a további vizsgálódást csak az utasítást követı pontosvesszıtıl folytassuk. Erre használható az e célból fenntartott error token. utasítás: változó értékadás kifejezés ; {DoSomething();} változó értékadás kifejezés {yyerror( Hiányzik a ;. );} error ;

; Ez esetben külön lekezeljük a pontosvesszı hiányát, viszont minden más szintaktikai hiba esetén az error meghívja az yyerror függvényt (a sokatmondó syntax error string bemenettel), majd a következı pontosvesszıig nem elemzi a szöveget. Természetesen a ; helyére tetszıleges tokent írhatunk, például egy blokk végéig az error } utasítással hanyagolhatjuk az elemzést. Megjegyzés Az errorral az a nagy baj, hogy meghívja önkényesen az yyerrort, ami egy idegesítı és használhatatlan syntax error üzenetet eredményez. Ezt persze kikerülhetjük úgy, hogy amikor megírjuk az yyeror függvényt, akkor nem feltétlenül a paraméterként beadott stringet íratjuk ki vele, ezt már mindenkinek a saját képzeletére bízom. 3.5 Fontosabb kapcsolók -o output: y.tab.c helyett új kimenet megadása. -v: y.output file is létrehoz, ami tartalmazza a párosító táblák leírását és a nyelvtan kétértelmőségeit. -d: y.tab.h file-t is létrehozza, amelyben #define-nal összepárosítja a YACC által létrehozott token kódokat a felhasználó által adott token nevekkel. 3.6 Fontosabb hibaüzenetek symbol valami is undefined: Ezt az üzenetet akkor adja, ha olyan szimbólumot használunk a szabályok jobb oldalán (a kettıspont után), amit bal oldalon nem. Pl.: %token true false expr: boolexpr intexpr ; boolexpr: true false ; Láthatóan, az intexpr kifejezést nem deklaráltuk, nem fogja ismerni. a token appears on the lhs of a production: Egy szabály bal oldalára elızıleg tokennek definiált kifejezést írtunk. Ilyet nem tehetünk, mert mi a nyelvtanunk alapján a tokenekbıl kiindulva rövidítgetjük a szövegünket amíg el nem fogy. reduce/reduce conflict, shift/reduce conflict és shift/shift conflict: Ezek az üzenetek az jelentik, hogy a nyelvtanunk nem egyértelmően lett megírva, vagyis vagy a lépéskor, vagy a redukáláskor nem tudja egyértelmően eldönteni, hogy melyik szabályt alkalmazza. Ez nem fatális hiba, ettıl a C kódot még legenerálja, mert a YACC egyértelmővé teszi a mi nyelvtanunkat és ezt érdemes kihasználni (feltéve, hogy a gykvez. megengedi, hogy ilyen figyelmeztetés mellett jól mőködik a programunk).

A következı szabályok szerint jár el, ha nem egyértelmő a nyelvtanunk: 1. shift/reduce conflict esetén a shiftet hajtja végre, vagyis ha redukálás és lépés között kell válasszon, akkor mindig lépni fog. 2. reduce/reduce conflict esetén a korábban megírt szabály szerint jár el. 4. Lex és YACC együttmőködése Jelöljük a lex számára feldolgozandó file-okat.l kiterjesztéssel, a YACC által feldolgozandót.y kiterjesztéssel. Ez alapján tegyük fel, hogy az általunk megírt bemenete a lexnek a valami.l, és megírtuk már a YACC számára szükséges kódot is, ez a valami.y-ban található. Ekkor a YACC elolvassa a nyelvtani szabályokat a valami.y file-ból és elkészíti az yyparse párosító függvényt az y.tab.c-be. A d opció hatására a tokeneket ledefiniálja az y.tab.h file-ba. A lex kiolvassa a minták leírásait a valami.l file-ból, amibe bele kell foglaljuk az y.tab.h-t. Elkészíti az yylex lexikális elemzıt a lex.yy.c file-ba. Ezután nekünk már csak össze kell fordítani az egészet, valahogy így: cc lex.yy.c y.tab.c o valami. Ha a YACC-ot használók hibát kapnak, hogy nem találja az yylval-t, akkor az alábbi sort írjuk az #include <y.tab.h> alá: extern YYSTYPE yylval; Erre szüksége van a yaccnak, viszont a bison már automatikusan megteszi helyettünk. Látványosan a következı történik: YACC(szintaktikus_elemzınk.y) => y.tab.h, y.tab.c lex(lexikális_elemzınk.l) => lex..yy.c cc(y.tab.c, lex..yy.c) => fordítóprogramunk fordítóprogramunk(saját_programnyelvünkön_írt_kód) => büszkeségünk Amennyiben büszkeségünk egy asm kód, akkor nasm(büszkeségünk.asm) => büszkeségünk.o cc(büszkeségünk.o) => futtatható_állományunk 5. Egy-két tanács a kezdetekhez Érdemes elıször megírni a nyelvtan file-t, vagyis a YACC bemenetét, már csak azért is, mert a lexbe include-olni kell a YACC által generált y.tab.h-t. Viszont nem érdemest fejjel menni a falnak, vagyis elıször a nyelvtani szabályokat megírjuk, de nem kell erıltetni a szabályokhoz tartozó C kód megírását valamint a Hibakezelés pontban említett plusz sorokat is hanyagoljuk egyenlıre. Lesz még azokkal elég baj késıbb, pl: kifejezés: kifejezés + kifejezés {} kifejezés - kifejezés {} ; A kapcsos zárójelek kitöltése még ráér. Viszont amint meg van a nyelvtan csontváza, írjuk meg a lex bemenetét, vagyis a lexikális elemzıt. Rögtön írjunk a félkész fordítónkhoz egy gagyi példát, és fordítsuk le a fordítóprogramunkat, ha ez sikerült, akkor a fordítóprogramunkkal fordítsuk le a példánkat. Mivel nem írtunk C kódot, ne várjunk tıle kimeneti file-t, viszont arra jó, hogy megtudjuk: a szerintünk jónak ítélt példát a fordító programunk is jónak látja-e. Általában nem. Ekkor

nyilall belénk a felismerés, hogy a syntax error üzenetek, amelyek még a hiba sorát sem adják meg semmire nem jók. Eddig még nem foglalkoztunk hibakezeléssel, azonban ezt rögtön pótoljuk: a lex bemenetének írt fileban számoljuk a sorokat vagyis kell egy változó, ami 0-ról indul és minden \n hatására nı. Ezt látnunk kell a YACC bementében is, ott extern kulcsszóval tegyük láthatóvá (mivel ott a main, amiben induláskor le kell nullázni, és ott írjuk meg az yyerror-t, amiben használjuk a változót). Az yyerror-t mindenki saját szája szerint írja meg, de a hibaüzenetekhez feltétlenül csapja hozzá az aktuális sor számlálónk értékét. Ez létfontosságú. Ennél több hibakezelés nem szükséges kódolás elıtt. Most már elkezdhetjük megírni a C kódokat, hogy legyen kimenete a fordítónknak. Ha a fordítónk példakódot jónak talál, amit mi is jónak szeretnénk minısíteni, és mindent hibásnak, amit mi is hibásnak szeretnénk jelezni, akkor lehet kifinomult hibaüzenetekkel és a Hibakezelés pontban leírt módszerekkel alakítgatni a fordítóprogramunkat. Mindenkinek sok sikert kívánok a kódolással töltött álmatlan éjszakákhoz.

Irodalomjegyzék Csörnyei Zoltán: Bevezetés a fordítóprogramok elméletébe I. http://www.epaperpress.com/lexandyacc/index.html (mindenkinek ajánlom, aki egy kicsit részletesebb, tömör leírás iránt érdeklıdik) http://www.cs.man.ac.uk/~pjj/complang/lexyacc.html