Sveučilište u Zagrebu

Fakultet elektrotehnike i računarstva

Prevođenje programskih jezika

Dokumentacija o izgradnji i radu ostvarenog jezičnog procesora

Voditelj: Leo Osvald

Grupa: 03

Lovro Biočić

Ana Grđan

Josip Juričić

Saša Ledinščak

Hrvoje Novak

Marin Pranjić

Matko Raguž

Marko Sršan

Zagreb, siječanj 2011. godine


UVOD

MOTIVACIJA

Ova tehnička dokumentacija je nastala kao rezultat laboratorijskih vježbi iz kolegija Prevođenje programskih jezika na Fakultetu elektrotehnike i računarstva. Tokom laboratorijskih vježbi se izgrađivao vlastiti jezični procesor koje prevodi zadani podskup jezika C u mnemonički jezik vrlo sličan FRISC-u, a sve s ciljem boljeg razumijevanja kako jezični procesor radi.

RAD STUDENATA NA IZRADI LABORATORIJSKE VJEŽBE

Lovro Biočić - testiranje izgradnje automata na temelju regularnih izraza (pisanje JUnit testova)

Ana Grđan - testiranje leksičke analiza (pisanje primjera ulaz/izlaz), pomoć pri pisanju JUnit testova za testiranje generacije tablice parsiranja

Josip Juričić - testiranje izgradnje regularnih izraza, pisanje testova za parsiranje, testiranje raznih stvari oko generacije koda (uglavnom pisanje JUnit testova), pisanje primjera raznih programa, pomoć u kodiranju

Saša Ledinščak - testiranje leksičke analiza (pisanje primjera ulaz/izlaz), pisanje pisanej JUnit testova za testiranje pretvorbe NKA u DKA kod generatora parsera, debugiranje pretvorbe NKA u DKA

Hrvoje Novak - učitavanje kod generatora leksera, debugiranje leksičkog analizatora, izrada GUI-ja za kompajler

Marin Pranjić - pisanje koda vezanog uz generaciju koda na temelju već izgrađenih opisnika (*Descriptor), pisanje koda za pretvorbu NKA sa LR1 stavkama u DKA sa LR1 stavkama

Matko Raguž - pisanje testova za parsiranje

Marko Sršan - izrada prvog dijela dokumentacije (općenite stvari)

Leo Osvald - ostatak (skoro svo programiranje, ostatak testiranja i dokumentacije)

JEZIČNI PROCESOR

Jezični procesori se dijele u dvije skupine ovisno o tome dali generiraju ciljni program za računalo koje prevodi izvorni program ili za neko drugo računalo. Uobičajeno je da jezični procesor generira ciljni program koji se izvodi na istom računalu. No, premosni prevoditelji su jezični procesori koji prevode izvorni u ciljni program na jednom računalu, a generirani ciljni program se izvodi na drugom računalu.

Jezični procesor je program koji se gradi primjenom jednog od programskih jezika. Većina današnjih jezičnih procesora gradi se primjenom viših programskih jezika ili primjenom posebno izgrađenih programskih alata koji su namijenjeni upravo izgradnji jezičnih procesora.

Jezični procesori (eng. compiler) su računalni programi koji služe prevođenju iz nekog izvornog u istovjetni ciljni programski jezik, što je prikazano sljedećom slikom.

Izrađeni jezični procesor u potpunosti je implementiran u programskom jeziku Java. Izvorni jezik Li je nalik na programski jezik C, međutim, s ograničenom funkcionalnošću. Ciljni jezik Lc jezičnog procesora jest mnemonički jezik vrlo sličan FRISC-u.

PREGLED RADA PREVODITELJA

Slika 1 - Pogled na arhitekturu prevoditelja

LEKSIČKA ANALIZA

Leksička analiza je prvi korak u radu jezičnog procesora. To je proces čitanja znakova izvornog programa te njihovog raščlanjivanja u osnovne elemente jezika koji se nazivaju leksičke jedinke (eng. lexical tokens). Leksička analiza vrši se pomoću programa leksičkog analizatora. Leksički analizator grupira ulazne znakove u leksičke jedinke, provjerava leksička pravila, zapisuje leksičke jedinke u tablicu znakova i generira niz uniformnih znakova leksičkih jedinki, koji se potom predaje na sintaksnu analizu koja čini drugi korak u radu jezičnog procesora.

GENERATOR LEKSIČKOG ANALIZATORA

Za potrebe leksičke analize, bilo je potrebno izgraditi generator leksičkog analizatora po uzoru na program Lex. Razlog za izgradnju općenitog generatora leksičkog analizatora leži u činjenici da sama izgradnja daje bolji uvid u način rada leksičke analize bilo kojeg programskog jezika.

Slika 2 – Način rada leksičkog analizatora

Način rada generatora leksičkog analizatora prikazan je Slikom 2. Generator leksičkog analizatora kojeg je potrebno ostvariti treba prihvaćati opis procesa leksičkog analizatora zadan tekstualnom datotekom (dalje Ulazna Datoteka). Izlaz iz generatora leksičkog analizatora treba biti izvorni kod leksičkog analizatora napisan u jeziku izgradnje (koji je proizvoljan). Ulazna Datoteka bit će zadana u sljedećem formatu:

regularne definicije

%X stanja leksičkog analizatora

%L imena leksičkih jedinki

pravila leksičkog analizatora

LEKSIČKI ANALIZATOR

Izlaz iz generatora leksičkog analizatora je izvorni kod leksičkog analizatora pisan u jeziku izgradnje jezičnog procesora. Dio koda leksičkog analizatora potpuno je neovisan o Ulaznoj Datoteci.

SINTAKSNA ANALIZA

Sintaksna analiza je središnji dio analize programa. Sintaksni analizator čita uniformne znakove leksičkih jedinki, grupira ih u sintaksne cjeline, stvara hijerarhiju sintaksnih cjelina te provjerava sintaksna pravila. Osnovne sintaksne cjeline su izrazi, naredbe, blokovi naredbi i program. Sintaksna pravila odreduju dopuštene strukture naredbi, izraza te čitavog programa. Ukoliko program ne zadovoljava sintaksna pravila, sintaksni analizator odredi mjesto pogreške, ispiše opis pogreške te nastavi s daljnjom analizom.

GENERATOR SINTAKSNOG ANALIZATORA

Generator sintaksnog analizatora se izgradio po uzoru na program Yacc, ali u bitno pojednostavljenom obliku. Generirani sintaksni analizator za parser koristi LR(1) kanonski parser koji je odabran zbog jednostavnije izgradnje generatora. Slično kao kod leksičke analize, izgrađenim generatorom sintaksnog analizatora se uz odgovarajuću ulaznu datoteku generira sintaksni analizator za podskup jezika C.

Slika 3 – način rada sintaksnog analizatora

Generator sintaksnog analizatora prihvaća opis procesa sintaksnog analizatora zadan tekstualnom datotekom dok je izlaz iz generatora sintaksnog analizatora izvorni kod sintaksnog analizatora napisan u jeziku izgradnje. Pritom je ulazna datoteka zadana u sljedećem formatu:

%V nezavršni znakovi gramatike

%T završni znakovi gramatike

%Syn sinkronizacijski završni znakovi

produkcije gramatike

SINTAKSNI ANALIZATOR

Uloga sintaksnog analizatora je parsiranje niza uniformnih znakova - sintaksni analizator treba provjeravati zadovoljava li napisani program sintaksna pravila zadana gramatikom u Ulaznoj Datoteci te konstruirati generativno stablo za dani program. Konstruirano generativno stablo će se koristiti u daljnjim fazama rada jezičnog procesora. Tablica znakova se neće mijenjati, već će se iz nje isključivo dohvaćati grupirani nizovi znakova koji odgovaraju određenom uniformnom znaku u nizu uniformnih znakova.

SEMANTIČKA ANALIZA

Semantički analizator ključan je dio postupka prevođenja programskog jezika. Dvije su glavne zadaće semantičkog analizatora – provjera semantičkih pravila napisanog programa te obilježavanje sintaksnih cjelina semantičkim obilježjima. Semantička obilježja koriste se prilikom faze generiranja međukoda koja prethodi fazi generiranja ciljnog programa. Semantička analiza je most koji povezuje fazu analize izvornog programa s fazom sinteze ciljnog programa. Pritom se najvažnijim korakom smatra određivanje značenja svih elemenata jezika, počevši od onih najjednostavnijih leksičkih jedinki, do najsloženijih sintaksnih struktura kao što su izrazi i naredbe.

UPUTE ZA KORIŠTENJE JEZIČNOG PROCESORA

U ovom poglavlju biti će pokazane upute za korištenje jezičnog procesora.

Kompajliranje

Instalacija potrebnih alata

Da bi se cjelokupni projekt mogao kompajlirati, potrebno je imati instalirano sljedeće:

Na operacijskim sustavima Ubuntu Linux, Debina GNU/Linux (ili nekim drugim *NIX-om koji koristi apt sustav ), moguće je oboje instalirati sa sljedećom naredbom:

$ sudo apt-get install sun-java6-jdk maven2

dok je na ostalim operacijskim sustavim mogu skinuti potrebni alati na sljedećim web adresama:

Izgradnja binarnih datoteka (izvršnih programa)

Pretpostavimo da se gore navedeni alati nalaze u sistemskoj putanje (engl. system path-u).

  1. Raspakirati arhivu s izvornim kodom negdje na disk.

$ unzip spocc-src.zip

  1. Pokrenuti komandu “mvn package”

        $ mvn package

        Ovaj korak može potrajati i do par minuta, budući da se najprije kompajlira, zatim se                 pokreću svi testovi, i konačno se pripremaju izvršne .jar datoteke. Ako je sve dobro prošlo,                 ispis bi trebao završavati sa nečim poput sljedećeg:

----------------------------------------------------------------

[INFO] BUILD SUCCESSFUL

[INFO] ----------------------------------------------------------------

[INFO] Total time: 58 seconds

[INFO] Finished at: Sun Jan 09 19:35:31 CET 2011

[INFO] Final Memory: 61M/325M

[INFO] ----------------------------------------------------------------

Ovime su se izgenerirale izvršne .jar datoteke unutar odgovarajućeg target direktorija za svaki od programa koji se može pokretati. Međutim, bez specificiranja classpath-a na biblioteke i ostale dependency-je moguće je pokretati samo one koje završavaju sa .one-jar.jar, dakle:

One predstavljaju program jezičnog procesora za podskup jezika C (komandno-linijski), grafičko sučelje za njega, program generator leksičkog analizatora, program generator sintaksnog analizatora, respektivno.

  1. Proporuča se kopiranje gore navedenih datoteka na proizvoljnu lokaciju, npr. u spocc-core.jar, spocc-ide, spocc-lag.jar, spocc-sag.jar, respektivno.

Pokretanje

Bilo da smo kompajlirali po gore napisanoj uputi ili jednostavno uzeli gotove izvršne .jar datoteke, sada imamo na raspolaganju sljedeće programe koji se mogu pokrenuti na uobičajen način pokretanja Java programa zapakiranih u .jar arhive:

Primjer pokretanja jezičnog procesora (spocc-core):

        $ java -jar spocc-core.jar

Usage: Spocc source-file [{-a, --assembly-file} file] [--symbol-table file] [--token-list file] [--parse-tree file] [--parse-tree-human-readable] [--ignore-lexical-errors] [{-v, --verbose}]

Note: instead of file "-" (without quotes) can be put to print to standard out

Na analogan način se pokreću i ostala dva programa (generatori).

Generator leksičkog analizatora (spocc-lag)

U sklopu projekta napravljen je samostalni program generator leksičkog analizatora.

To je komandno linijski program koji ima sličnu namjenu kao program LEX, s razlikom da ovaj generira izvorni kod u programskom jeziku Java.

Upute za korištenje

Opcije na komandne liniji

$ java -jar spocc-lag.jar

Usage: Main lexical-rules-file [{-o, --output-dir} path]

Primjer generacije koda leksičkog analizatora

$ java -jar spocc-lag.jar /tmp/srbljic-151-lexical-rules.def -o /tmp/src

Ovime će se u direktoriju /tmp/src izgenerirati kod za ostavrenje leksičkog analizatora, koristeći sintakstna pravila definirana u datoteci /tmp/srbljic-151-lexical-rules.def.

/tmp/src

`-- hr

        `-- fer

            `-- spocc

                |-- lexer

                |   |-- action

                |   |   |-- _Action1.java

                |   |   |-- _Action2.java

                |   |   |-- _Action3.java

                |   |   |-- _Action4.java

                |   |   |-- _Action5.java

                |   |   |-- _Action6.java

                |   |   |-- _Action7.java

                |   |   `-- _Action8.java

                |   |-- _GeneratedMain.java

                |   |-- _LexerDescriptorFactory.java

                |   |-- nfa

                |   |   |-- _NfaFactory1.java

                |   |   |-- _NfaFactory2.java

                |   |   |-- _NfaFactory3.java

                |   |   |-- _NfaFactory4.java

                |   |   |-- _NfaFactory5.java

                |   |   |-- _NfaFactory6.java

                |   |   |-- _NfaFactory7.java

                |   |   `-- _NfaFactory8.java

                |   `-- _State.java

                `-- _TokenType.java

Generator će prije generiranja koda počistite sve poddirektorije direktorije odredišnog direktorija unutar kojeg će stvoriti bar jednu izvornu datoteku. Ovo služi da ako se više puta generira u isti direktorij kod za jezik s drugim leksičkim pravilima, da nema konflikata.

GeneratedMain_ je generirani program koji obavlja leksičku analizu, ali obzirom na svoju ograničenost najčešće nije od pretjerane koristi. Od ključnog značaja je izgenerirana klasa _LexerDescriptorFactory - upravo instancu tog objekta traži apstraktna implementacija leksičkog analizatora AbstractLexer u svom konstruktoru. Jednom kad se stvori objekt ove klase, može se pomoću implementacija sučelja Lexer (npr. DefaultLexer koji nasljeđuje AbstractLexer) ostvariti leksički analizator za jezik opisan leksičkim pravilima u datoteci koja je predana putem komandne linije u generatoru.

Generator sintaksnog analizatora (spocc-sag)

U sklopu projekta napravljen je samostalni program generator sintaksnog analizatora, odnosno njegovih dijelova. To je komandno linijski program koji ima sličnu namjenu kao program YACC,, s razlikom da ovaj generira izvorni kod u programskom jeziku Java.

Upute za korištenje

Opcije na komandne liniji

$ java -jar spocc-sag.jar

Usage: Main syntax-rules-file [{-o, --output-dir} path] [{-t, --generate-token-types}] [{-c, --use-c-precomputed}] [{-v, --verbose}]

Primjer generacije koda sintaksnog analizatora

$ java -jar spocc-sag.jar /tmp/srbljic-151-syntax-rules.def -o /tmp/src --generate-token-types

Ovime će se u direktoriju /tmp/src izgenerirati kod za ostavrenje sintaksnog analizatora, koristeći sintaksna pravila definirana u datoteci /tmp/srbljic-151-lexical-rules.def.

U slučaju da već imamo izgenerirane izvorne datoteke za potrebe ostvarenja leksičkog analizatora, sada bismo imali sljedeće datoteke:

/tmp/src/

`-- hr

        `-- fer

            `-- spocc

                |-- lexer

                |   |-- action

                |   |   |-- _Action1.java

                |   |   |-- _Action2.java

                |   |   |-- _Action3.java

                |   |   |-- _Action4.java

                |   |   |-- _Action5.java

                |   |   |-- _Action6.java

                |   |   |-- _Action7.java

                |   |   `-- _Action8.java

                |   |-- _GeneratedMain.java

                |   |-- _LexerDescriptorFactory.java

                |   |-- nfa

                |   |   |-- _NfaFactory1.java

                |   |   |-- _NfaFactory2.java

                |   |   |-- _NfaFactory3.java

                |   |   |-- _NfaFactory4.java

                |   |   |-- _NfaFactory5.java

                |   |   |-- _NfaFactory6.java

                |   |   |-- _NfaFactory7.java

                |   |   `-- _NfaFactory8.java

                |   `-- _State.java

                |-- parser

                |   |-- _Filler0.java

                |   `-- _ParsingTableFactory.java

                `-- _TokenType.java

Kad ne bismo prethodno generirali kod i s generatorom leksičkog analizatora, dodali bi opciju “-t” (odnosno “--generate-token-types”)

$ java -jar spocc-sag.jar /tmp/srbljic-151-syntax-rules.def -o /tmp/src --generate-token-types

pa bi nam to osiguralo da imam sljedeće datoteke:

/tmp/src/

`-- hr

        `-- fer

            `-- spocc

                |-- parser

                |   |-- _Filler0.java

                |   `-- _ParsingTableFactory.java

                `-- _TokenType.java

Ključna klasa koja se izgenerira jest _ParsingTableFactory - to je implementacija sučelja ParsingTableFactory. Pozivom metode

ParsingTable<TokenType> createParsingTable()

Dobivamo tablicu parsiranja koju koristi LR1Parser (ili bilo koja implementacija sučelja Parser koja nasljeđuje apstraktnu (AbstractParser)) kako bi parsirao u skladu s sintaksnim pravilima definiranim u ulaznoj datoteci čije smo ime predali u generatoru kod poziva iz komandne linije.

Jezični procesor SPOCC (spocc-core)

Budući da jezični procesor SPOCC još nema implementiranu semantiku i generaciju koda, on je naprosto generički kompajler kojemu se po-želji može mijenjati leksički analizator i sintaksni analizator. Dovoljno je pomoću gore opisana dva generatora izgenerirati kod u direktorij src/generated/java od ovog projekta, te ga ponovno rekompajlirati. Jedino mjestu u izvornom kodu ovog jezičnog procesora (spocc-core) je u implementaciji kompajlera - klasa DefaultCompiler:

class DefaultCompiler extends AbstractCompiler {

    @Override

    protected LexerDescriptorFactory createLexerDescriptorFactory() {

            return new _LexerDescriptorFactory();

    }

   

    @Override

    protected ParsingTableFactory createParsingTableFactory() {

            return new _ParsingTableFactory();

    }

    ...    

}

Opcije na komandne liniji

$ java -jar spocc-core.jar

Usage: Spocc source-file [{-s, --symbol-table} file] [{-t, --token-list} file] [{-p, --parse-tree} file] [{-h, --parse-tree-human-readable}] [{-i, --ignore-lexical-errors}] [{-v, --verbose}]

Primjeri korištenja za jezik izgenrirano izvođenjem primjera za generatore

Sljedeći primjeri dobiveni su ubacivanjem sintaksnih i leksičkih pravila za jezik definiran gramtikom sa dna strane 148 u knjizi od prof. Srbjlića (151 potječe od toga što je tablica parsiranja na 151. strani).

Primjer ispisa sintaksnog stablo u obliku čitljivom za ljude:

$ java -jar spocc-core.jar ../src/test/resources/151-02.source --parse-tree - --parse-tree-human-readable

 <A>( <B>( a <B>( b ) ) <A>( ) )

Ako želimo isto stablo iscrtati (npr. pomoću programa GraphViz koji crta grafove u jeziku DOT), onda ćemo napisati:

$ java -jar spocc-core.jar ../src/test/resources/151-02.source -parse-tree /tmp/151-02.gv

Naknadnim pokretanjem naredbe

$ dot -Tpng /tmp/151-02.gv > /tmp/151-02.png

dobivamo .png sliku koja izgleda ovako:

Evo i nešto kompliciranijeg primjera u istom jeziku:

$ java -jar spocc-core.jar ../src/test/resources/151-03.source --parse-tree - --parse-tree-human-readable

 <A>( <B>( a <B>( a <B>( b ) ) ) <A>( <B>( a <B>( a <B>( b ) ) ) <A>( <B>( b ) <A>( ) ) ) )

Primjeri korištenja za jezik koji je podskup jezika C

U ovom poglavlju biti će pokazani primjeri programa jezičnog procesora.

java -jar spocc-core.jar c-simple-01.source --parse-tree c-simple-01.gv

Primjer kompajliranja programa:

c-simple-02.source

struct P {

        char m, tp;

        int fk;

};

$ java -jar spocc-core.jar c-simple-02.source -p - -h

 <prijevodna_jedinica>( <vanjska_deklaracija>( <deklaracija>( <specifikatori_deklaracije>( <specifikator_tipa>( <struct_specifikator>( KR_STRUCT IDN L_VIT_ZAGRADA <struct_lista_deklaracija>( <struct_lista_deklaracija>( <struct_deklaracija>( <lista_specifikatora_kvalifikatora>( <specifikator_tipa>( KR_CHAR ) ) <struct_lista_deklaratora>( <struct_lista_deklaratora>( <struct_deklarator>( <deklarator>( <izravni_deklarator>( IDN ) ) ) ) ZAREZ <struct_deklarator>( <deklarator>( <izravni_deklarator>( IDN ) ) ) ) TOCKAZAREZ ) ) <struct_deklaracija>( <lista_specifikatora_kvalifikatora>( <specifikator_tipa>( KR_INT ) ) <struct_lista_deklaratora>( <struct_deklarator>( <deklarator>( <izravni_deklarator>( IDN ) ) ) ) TOCKAZAREZ ) ) D_VIT_ZAGRADA ) ) ) TOCKAZAREZ ) ) )

Spocc-ide

Spocc-ide je minimalističko grafičko sučelje za kompajler koje u sebi sadrži i uređivač teksta. Trenutna verzija ima dosta bugova, pa se ne preporuča osim za demonstracijske svrhe (za razliku od spocc-core komandno-linijskog programa).

Primjer korištenja

$ java -jar spocc-ide.jar

Primjer korištenja opcije crtanja sintaksnog stabla za jednostavan jezik.

PRIMJERI PROGRAMA JEZIČNOG PROCESORA

Pogledati u arhivu u “sample-programs” direktorij i u prethodno poglavlje.

LITERATURA

[1] Siniša Srbljić: «Prevođenje programskih jezika», 1. izdanje, Zagreb: Element, 2007.

[2] «Jezični prevoditelj », «Jezični prevoditelj – Wikipedia», http://hr.wikipedia.org/wiki/Jezi%C4%8Dni_prevoditelj, 5.1.2011.