Implementace jazyka Ruby pro virtuální stroj Smalltalk/X
Transkript
Implementace jazyka Ruby pro virtuální stroj Smalltalk/X
eské vysoké u£ení technické v Praze Fakulta elektrotechnická Katedra po£íta£· Diplomová práce Implementace jazyka Ruby pro virtuální stroj Smalltalk/X Bc. Jan Karpí²ek Vedoucí práce: Ing. Jan Vraný Studijní program: Elektrotechnika a informatika, strukturovaný, Navazující magisterský Obor: Výpo£etní technika 19. dubna 2009 iv v Pod¥kování P°edem této diplomové práce bych rád pod¥koval Ing. Janu Vranému, jakoºto vedoucímu práce, za spolupráci, podporu a neocenitelné rady. vi vii Prohlá²ení Prohla²uji, ºe jsem práci vypracoval samostatn¥ a pouºil jsem pouze podklady uvedené v p°iloºeném seznamu. Nemám závaºný d·vod proti uºití tohoto ²kolního díla ve smyslu 60 Zákona £. 121/2000 Sb., o právu autorském, o právech souvisejících s právem autorským a o zm¥n¥ n¥kterých zákon· (autorský zákon). V Praze dne 19. dubna 2009 ............................................................. viii Abstract The goal of this master's thesis is Ruby implementation for the Smalltalk/X virtual machine. Abstrakt Cílem této diplomové práce je implementovat jazyk Ruby pro virtuální stroj Smalltalk/X. Implementace bude umoº¬ovat na£íst a zkompilovat zdrojové kódy jazyku Ruby a takto p°eloºený kód vykovat. Formát p°eloºeného kódu bude p°ímo bytecode virtuálního stroje Smalltalk/X. Díky této vlastnosti bude jednodu²e moºné vyuºívat p°eloºené t°ídy Ruby ve Smalltalkovém kódu. Dále bude implementace integrována do Class browseru, který je sou£ástí p°ost°edí Smalltalk/X. Integrace umoºní jednodu²e vytvá°et a spravovat Ruby kód p°ímo v prost°edí Smalltalk/X. Výsledkem bude funk£ní implementace jazyku Ruby vyuºívající p°edností virtuálního stroje Smalltalk/X, nap°. výkonnost, propracovaný garbage collector. ix x Obsah 1 Úvod 1 2 Popis problému 3 3 Existující implementace Ruby 3.1 3.2 3.3 3.4 Matz's Ruby Interpreter . . . Ruby >= v1.9.0 (Yet Another JRuby . . . . . . . . . . . . . Rubinius . . . . . . . . . . . . . . . . . . . Ruby VM) . . . . . . . . . . . . . . 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 . 9 . 11 . 14 T°ídy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.1 Vno°ené t°ídy (Nested classes) . . . . . . . . . . . . . . 5.2 Metody . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Konstanty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4 Globální prom¥nné . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Více hodnotové p°i°azení . . . . . . . . . . . . . . . . . . . . . . 5.6 Více hodnotový návrat z metody . . . . . . . . . . . . . . . . . 5.7 et¥zce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.7.1 Úprava gramatiky . . . . . . . . . . . . . . . . . . . . . 5.7.2 Parser °et¥zce . . . . . . . . . . . . . . . . . . . . . . . . 5.7.2.1 scanForStringToken - nevyhodnocovaný °et¥zec 5.7.2.2 scanForStringToken - vyhodnocovaný °et¥zec . 5.7.2.3 scanForStringToken - pole slov . . . . . . . . . 5.8 Bloky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9 T°ída Range . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.10 P°íkazy °ázení programu . . . . . . . . . . . . . . . . . . . . . . 5.10.1 P°íkaz Case . . . . . . . . . . . . . . . . . . . . . . . . . 5.10.2 P°íkaz if (unless) . . . . . . . . . . . . . . . . . . . . . . 5.10.3 P°íkaz while (until) . . . . . . . . . . . . . . . . . . . . . 5.10.4 P°íkaz for . . . . . . . . . . . . . . . . . . . . . . . . . . 5.11 Operátory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Pouºité nástroje 4.1 4.2 SmaCC - Smalltalk Compiler-Compiler 4.1.1 Generování t°ídy Scanner . . . 4.1.2 Generování t°ídy SmaCC Parser IRBuilder . . . . . . . . . . . . . . . . 5 Implementace 5.1 xi . . . . . . . . 5 6 6 7 9 15 15 16 16 17 17 17 17 17 18 20 23 24 26 28 28 28 28 29 29 29 29 xii OBSAH 6 Testování 31 7 Zhodnocení 33 8 Záv¥r 35 Literatura 37 A Seznam pouºitých zkratek 39 Seznam obrázk· 4.1 4.2 4.3 4.4 4.5 P°íklad denice token· se stejným významem . . . . . . . . Gramatika bez pouºití direktiv a s nevy°e²enými prioritami Gramatika bez pouºití direktiv s vy°eºenými prioritami . . . Gramatika s direktivy . . . . . . . . . . . . . . . . . . . . . P°íklad denice gramatiky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 12 12 12 14 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 Upravená gramatika pro parsování °et¥zce1 . . . . . . . . . . . . . . . Upravená gramatika pro parsování jednotlivých slov na pole slov1 . . . Parser obsahu °et¥zce . . . . . . . . . . . . . . . . . . . . . . . . . . . . scanForStringEndToken - metoda obsluhující na£tení konce °et¥zce . . Parser za£átku vyhodnocovaného výrazu . . . . . . . . . . . . . . . . . P°íklad vygenerovaného kódu p°íkazu "str1 str2" "str3#{1+1}" . . . Parser odd¥lova£e jednotlivých slov . . . . . . . . . . . . . . . . . . . . P°íklad vygenerovaného kódu p°íkazu %W{word1 w#{1+1}ord2 word3 . . . . . . . . . . . . . . . 19 21 22 23 25 26 27 28 xiii . . . . . . . . . . . . . . . . . . . . . . . . . } xiv SEZNAM OBRÁZK Seznam tabulek xv xvi SEZNAM TABULEK Kapitola 1 Úvod Jazyk Ruby je jedním z °ady skriptovacích jazyk·. Vlastností, která ho odli²uje od jeho konkurent·, je £íst¥ objektový návrh. Ruby je pom¥rn¥ mladý programovací jazyk. Jeho autorem je japonský po£íta£ový v¥dec a programátor Yukihiro Matsumoto. S implementací za£al v roce 1993, o dva roky pozd¥ji (tj. roku 1995) zve°ejnil první verzi Ruby. Matsumoto dlouho hledal skriptovací jazyk, který by byl objektov¥ orientován. Na²el jazyk Perl ve verzi 5, který m¥l p°inést moºnost objektového programování. Dal²í alternativou byl jazyk Python, který je interpretovaný, objektov¥ orientovaný jazyk. Ale jak Matsumoto sám poznamenal:I didn't feel like it was a "scripting" language. Navíc se jednalo o k°íºence mezi procedurálním a objektovým programováním. Rozhodl se vytvo°it vlastní jazyk, který by byl mocn¥j²í neº Perl a více objektový neº Python. Z inspirace v jakyzu Perl vznikl i jeho název jazyku. Matsumoto hledal slovo, které by nejlépe vystihovalo, jeden z jeho kamarád· navrhnul, pojmenovat jazyk po rubínu, tak vznikl jazyk Ruby. Matsumoto pozd¥ji na²el, ºe perla je kámen m¥síce £ervna a rubín kámen m¥síce £ervence. Tato skute£nost ho utvrdila, ºe Ruby je vhodný název pro jazyk, inspirovaný jazykem Perl. V nyn¥j²í dob¥ existuje více implementací jazyku Ruby. Jejich p°iblíºení, výhody i nevýhody jsou obsahem kapitoly 3. Referen£ní implementací jazyku Ruby je Matz's Ruby Interpreter, zkrácen¥ MRI. Tv·rcem MRI je zmín¥ný Yukihiro Matsumoto. Ruby je dal²ím programovacím jazykem, který se pi²ní p°ívlastkem £íst¥ objektový, v p·vodní implementaci interpretovaný. Spojení vlastností £ist¥ objektový a interpretovaný d¥lá z Ruby do jisté míry unikátní jazyk. Ve²keré roz²í°ené skriptovací jazyky (nap°. PHP, JavaScript, Perl atd.) mají základ v procedurálním programování s objektovým roz²í°ením. Tato práce se zabývá implementací jazyku Ruby v prost°edí virtuálního stroje jazyku Smalltalk/X. Nemá se jednat o pouhý interpret Ruby, ale o p°eklada£ do strojového kódu virtuálního stroje. Výhodou této alternativní implementace by m¥lo být vyuºití tohoto prost°edí, optimalizací p°i p°ekladu zdrojového kódu a pokro£ilé správy pam¥ti. Jazyk Smalltalk byl vybrát pro svojí podobnost s jazykem Ruby (oba jsou £i²t¥ objektov¥ navrºeny, pouºívají dynamické typování...). První kapitolou je úvod. Následující kapitola formuluje poºadavky a cíle projektu. Ve t°etí kapitole jsou shrnuty alternativní implemenatace jazyku Ruby, spolu s jejich výhodami a nevýhodami. V analytické £ásti je popsána gramatika a základní rysy jazyka. 1 2 KAPITOLA 1. ÚVOD V podkapitole jsou p°iblíºeny dva nástroje pouºité p°i vývoji kompilátoru, tj. SmaCC a IRBuilder. Dal²í, pátá, kapitola se zabývá samotnou implementací p°eklada£e. está kapitola prezentuje výsledky jednotkových test·, test· úplnosti implementace a srovnávacích test· s jinými implementacemi. P°edposlední kapitola sumarizuje výsledky práce, moºnosti dal²ího vývoje implementace, p°ípadná vylep²ení. Poslední kapitolou je záv¥r. Kapitola 2 Popis problému Jak bylo v úvodu zmín¥no, cílem projektu je vytvo°it p°eklada£ jazyka Ruby v prost°edí Smalltalk/X. Hlavním nedostatkem referen£ní implementace Ruby je výkonnost. Tato slabina je d·vodem, pro£ vznikly (a nadále vznikají) alternativní implementace (kapitola 3) tohoto jazyku. Implemenatace ve Smalltalk by tento nedostatek mohla °e²it, virtuální stroj pro tento jazyk je dostate£n¥ optimalizován, kód nebude interpretován ale p°ekládán do strojového kódu virtuálního stroje a aº potom vykonán. Dal²ím d·vodem, pro£ byl vybrán Smalltalk, je jeho syntaktická i sémantická podobnost s Ruby. Pro£ Ruby Ruby velka komunita.... Své p°íznivce získalo Ruby i díky projektu Ruby on Rails. Ruby on Rails je webový framework zam¥°ený na snadný vývoj internetových aplikací. Usnad¬uje práci s Ajaxem a vizuálními efekty. Je hojn¥ vyuºíván i v komer£ní své°e. Syntaxe jazyku Ruby jako takového je vytvo°ena s ohledem na snadné pouºití, co nejmén¥ zbyte£ného psaní kódu. Jako p°íklad bych uvedl pouºití °et¥zc·. irb(main):001:0> $osloveni = 'Honzo' => "Honzo" irb(main):002:0> "Ahoj #$osloveni, vis, ze vysledkem 1+1 je #{1+1}?" => "Ahoj Honzo, vis, ze vysledkem 1+1 je 2?" irb(main):005:0> %W'Ahoj #$osloveni, vis, ze vysledkem 1+1 je #{1+1}?' => ["Ahoj", "Honzo,", "vis,", "ze", "vysledkem", "1+1", "je", "2?"] Prvním p°íkazem se nadenuje globální prom¥nná typu °et¥zec. Druhý p°íkaz vytvo°í °et¥zec, doplní d°íve denované oslovené a vytiskne výsledek sou£tu 1+1. Poslední p°íkaz vytvo°í z jednotlivých slov téºe v¥ty pole °et¥zc·. Pro srovnání je níºe uveden kód v jazyku Java, který d¥lá totéº. String osloveni = "Honzo"; "Ahoj "+osloveni+", vis, ze vysledkem 1+1 je "+(1+1)+"?"; new StringBuffer("Ahoj ").append(osloveni).append(", vis, ze vysledkem 1+1 je ").append(1+1) {"Ahoj", "Honzo,", "vis,", "ze", "vysledkem", "1+1", "je", "2?"}; 3 4 KAPITOLA 2. POPIS PROBLÉMU Pro programátora tak odpadá spojování °et¥zc· pomocí operátor· nebo pouºívání zástupných znak·, které se následn¥ nahradí parametry. Dal²ím znakem pro Ruby je samovypovídající kód. Autor se snaºil vytvo°it syntaxi jazyka tak, aby se následné £tení kódu podobalo b¥ºné mluvené v¥t¥. P°íkladem je zápis print self.email unless self.email.blank?, °e£eno slovy vytiskni email jestliºe není prázdný . Kód se tak stává £iteln¥j²í a srozumiteln¥j²í. Programátor se m·ºe více soust°edit na my²lenku a ne na to, jak ji vyjád°it. Toto je dal²í rys, díky kterému vzr·stá obliba Ruby. Implementace jazyku Ruby verze 1.8.6 je napsána v jazyku Smalltalk/X verze 5.4.2. Sada pravidel gramatiky Ruby vychází ze zdrojového souboru pro generátor p°eklada£· Bison 2.3 ?? s úpravami, které budou diskutovány v kapitole 4. Tento soubor je sou£ástí zdrojových kódu dostupných na stránkách jazyku Ruby[6]. Lexikální elementy jsou vytvo°eny podle specikace jazyka. P°i implementaci bylo pouºito nástroj· SmaCC4.1 a IRBuilder4.2. Kapitola 3 Existující implementace Ruby Jak bylo jiº d°íve °e£eno, referen£ní implementace Ruby 3.1 má dost podstatné nedostatky, které vedly, a stále vedou, ke vzniku alternativních implementací. Kaºdý z tv·rc· t¥chto alternativ se snaºil co nejvíce nedostatky eliminovat a doufal, ºe zrovna jeho implementace se pozd¥ji stane tou referen£ní. Vet²ina alternativních implementací je zam¥°ena na nejpostatn¥j²í nedostatek - zvý²it výkonnost. Následuje vý£et t¥ch nejpodstatn¥j²ích implementací Ruby, spolu s jejich malým popisem. 3.1 Matz's Ruby Interpreter Matz's Ruby Interpreter, zkrázen¥ MRI, je referen£ní implementace jazyku Ruby. Autorem jazyku Ruby je japonský po£íta£ový v¥dec a programátor Yukihiro Matsumoto. S implementací za£al v roce 1993, o dva roky pozd¥ji (tj. roku 1995) zve°ejnil první verzi Ruby. Tato implementace je napsána v jazyku C. Jejími nevýhodami jsou: zp·sob interpetace (výkon), pouºití vláken, omezující návrh Zp·sob interpretace Zdrojový kód se na£te, parser jazyka p°evede tento kód na abstraktní syntaktický strom, který zachycuje strukturu pouºitých konstrukcí. Vyhodnocení skriptu se provede následným procházením jednotlivých uzl· tohoto stromu a vyhodnocováním jeho v¥tví. Tento zp·sob je dost neefektivní, vyhodnocování AST stromu p°ímo za b¥hu programu je pomalé, optimalizace provád¥né v dob¥ p°ekladu se neuplat¬ují a pam¥´ov¥ náro£ný. V¥t²ina jazyk· (Python, PHP, JavaScript,...) strom dále zpracovává a výsledkem je zkompilovaný bytecode (instrukce virtuálního stroje daného jazyku0. Jejich následné spou²t¥ní je podstatn¥ rychlej²í neº práce se stromem. Pouºití vláken MRI pouºívá tzv. green threads, tj. vlákna implemenetovaná nikoliv opera£ním systémem, ale p°ímo programem v user-space. Práce s t¥mito vlákny je sice o n¥co rychlej²í, neº s klasickými vlákny. Implementace green thread v Ruby má ov²em n¥která omezení, nap°. blokující systémové volání zablokuje v²echna vlákna. Matz se pro green threads 5 6 KAPITOLA 3. EXISTUJÍCÍ IMPLEMENTACE RUBY rozhodl na po£átku vývoje v dob¥, kdy je²t¥ mnohé opera£ní systémy spolehliv¥ fungující podporu vláken nem¥ly. Tehdy to bylo správné rozhodnutí, zvý²ilo portabilitu, ale v dne²ních podmínkách jeho negativa p°evaºují nad pozitivy. Kódování V Ruby jsou °et¥zce prosté posloupnosti 8-bitových hodnot. Jakékoliv záleºitosti okolo kódování si musí programátor °e²it sám. Je to pon¥kud p°ekvapivý p°ístup vzhledem k tomu, ºe Ruby pochází z Japonska, kde jsou jist¥ s kódováním mnohem v¥t²í potíºe, neº t°eba u nás. Pro po°ádek je nutno dodat, ºe samotný zdrojový kód lze psát v 7-bitovém ASCII, pomocí Kanji (v kódování EUC nebo SJIS), nebo v UTF-8. Informace o kódování zdrojového textu se interpretu Ruby sd¥luje pomocí parametru -K. Omezující návrh MRI je napsán tak, ºe ho lze za£lenit do aplikace a pouºívat v ní Ruby jako vestav¥ný skriptovací jazyk. Bohuºel ale spousta datových struktur v MRI je globálních, a tak není moºné mít v aplikaci spu²t¥no více interpreter· Ruby zárove¬ (podobn¥, jako má nap°íklad kaºdá webová stránka v prohlíºe£i sv·j kontext pro skripty v JavaScriptu). To vyuºití Ruby jakoºto skriptovacího jazyka pro aplikace zna£n¥ omezuje, aº znemoº¬uje. Zano°íte-li se do kódu MRI, zjistíte, ºe spousta v¥cí je v n¥m ud¥laná jednodu²e ²patn¥. Nevhodné názvy prom¥nných a funkcí, spousta globálních prom¥nných, duplicity v kódu, nevhodn¥ strukturovaná logika, prakticky ºádné komentá°e... [2] Toto jsou hlavní nedostatky referen£ní implementace Ruby, které inspirovaly mnohé spole£nosti (i jednotlivce) k vytvo°ené vlastní implementace jazyku Ruby. 3.2 Ruby >= v1.9.0 (Yet Another Ruby VM) Jak jiº název napovídá, jedná se o virtální stroj pro jazyk Ruby. Autorem tohoto projektu je Koichi Sasada. Názvem projektu jsou po£áte£ní písmena YARV. Cílem tohoto projektu bylo zkrácení výpo£etního £asu p°i vykonání programu v jazyku Ruby. Sasada vyvíjel virtuální stroj odd¥len¥ od implementace Ruby. Aº na za£átku roku 2007 byly oba kódy spojeny a vznikla verze Ruby 1.9. Zdrojový kód je na£ten a pomocí parseru se vytvo°í AST strom (toto je stejné jako u p·vodní implementace - MRI). Nyní se z tohoto stromu vytvo°í kód pro virtuální stroj. Po p°eloºení se AST strom dealokuje a YARV spustí p°eloºený program na virtuálním stroji. [10] 3.3 JRuby JRuby je p°eklada£ jazyku Ruby pro virtální stroj Java. Tv·rci cht¥jí vyuºít existujícího prost°edí (JVM) s propracovanými funkcemi (optimalizace, garbage collector). Implementace JRuby za£ala roku 2001, první verze (rok 2003) byla p°episem kódu Ruby verze 1.6. Tato první verze JRuby byla v interpreta£ním módu místy aº o dv¥ t°etiny pomalej²í neº referen£ní implementace MRI. V dal²ích verzích tv·rci zapracovali p°edev²ím na výkonnosti. Poslední verze JRuby jsou v porovnání s implementací MRI 1.8.6 3.4. RUBINIUS 7 aº t°ikrát rychlej²í (v Just-In-Time modu), v porovnání s YARV ov²em o 10 procent zaostávají[9]. JRuby umoº¬uje obousm¥rné provázání kódu (tzn. pouºít Ruby kód v Java kódu a naopak). 3.4 Rubinius Rubinius je experimentální implementace virtuálního stroje pro jazyk Ruby. Cílem projektu je, podobn¥ jako YAVM, poskytnout lep²í, výkonn¥j²í prost°edí pro spou²t¥ní Ruby kód·. Jeho vývoj za£al na p°elomu let 2006 a 2007. Architektura tohoto viruálního stroje kopíruje architekturu virtuálního stroje Smalltalk 80 [11]. Implementace virtuálního stroje za£ala úpln¥ od za£átku. Tato skute£nost m·ºe být výhodou, vývojá°i nejnov¥j²í poznatky v oblasti návrhu softwaru, na druhou stranu m·ºe vývoj trvat dlouho, je nutné implementovat optimaliza£ní mechanizmy, manaºer pam¥ti atd. Dal²í výhodou m·ºe být rozsáhlá komunita vývojá°·, commit access dostane kaºdý, kdo po²le jen jediný patch. Dosavadní výsledky výkonnosti Rubinia jsou rozporuplné. 8 KAPITOLA 3. EXISTUJÍCÍ IMPLEMENTACE RUBY Kapitola 4 Pouºité nástroje Zdrojové kódy jazyku Ruby jsou ve Smalltalk/X parsovány pomocí t°íd Scanner a Parser. Tyto t°ídy byly vygenerovány nástrojem SmaCC 4.1. Pro následnou kompilaci AST stromu, který je vytvo°en pomocí SmaCC, byl pouºit nástroj IRBuilder. Ten, pomocí denovaných instrukcí, vygeneruje poºadovaný bytecode. Hlavním d·vodem, pro£ byly vybrány tyto nástroje, je, ºe ºádné jiné nástroje v prost°edí Smalltalk/X neexistují a tyto, ve zna£né mí°e, vyhovují na²im poºadavk·m. P°esto bylo nutné, v n¥kterých p°ípadech, p°izp·sobit chování na²im pot°ebám. Dokumentace pouºitých nástroj· je v n¥kterých sm¥rech dost stru£ná a k vysv¥tlení provedených úprav nedosta£ující. Proto se následující dv¥ podkapitoly v¥nují popisu t¥chto nástoj·. 4.1 SmaCC - Smalltalk Compiler-Compiler SmaCC je smalltalkový generátor p°eklada£·. Je obdobou nástroj· Flex [4], Bison [3] nebo JavaCC [5]. Projekt SmallRuby ho pouºívá pro parsování vstupních dat (zdrojového kódu Ruby) a vytvo°ení abstraktního syntaktického stromu. Stru£nou dokumentaci k nástroji lze nalézt na stránkách SmaCC [7]. Gramatika, ze které SmallRuby vychází (gramatika referen£ní implementace), si vyºádala nemalé úpravy kódu generovaného pomocí SmaCC. Pro jejich pochopení je nutné znát, jak vygenerované t°ídy postupují p°i parsování vstupu. Proto jsou zde popsány jednotlivé funkcionality nástroje. Nástroj SmaCC vygeneruje pro danou gramatiku dv¥ t°ídy - Scanner a Parser. 4.1.1 Generování t°ídy Scanner SmaCC vygeneruje podle vstupních dat t°ídu Scanner, která následn¥ vytvá°í jednotlivé lexikální element (terminální symboly gramatiky). Vstupními daty pro vytvo°ení Scanneru je kolekce dvojic 'název tokenu' - 'specikace tokenu'. Název tokenu m·ºe být cokoli, co ve Smalltalku ozna£uje název prom¥nné, °íkejme identikátor. Tento identikátor je uzav°en mezi znaky '<>' a toto celé je název tokenu, který se pouºívá p°i denici gramatiky. Specikace tokenu je vyjád°ena pomocí regulárního výrazu a denuje obsah tokenu. Denice v²ech token· je zapsána v externím souboru (p°ípadn¥ p°ímo v okn¥ aplikace, záloºka Scanner). Formát denice je následující 9 10 KAPITOLA 4. POUITÉ NÁSTROJE <IDENTIFIER> : [a-zA-Z] w* ; <TYPE_NAME> : [a-zA-Z] w* ; \end{center} Obrázek 4.1: P°íklad denice token· se stejným významem NazevTokenu : SpecifikaceTokenu; Znak dvojte£ky odd¥luje název tokenu od jeho specikace, za specikací tokenu následuje st°edník, který odd¥luje denice token·. Vzhledem k rozdílnosti specikací regulárních výrazu je na stránkách v¥novaným SmaCC Scanneru[8] vý£et pravidel s jejich popisem. Hlavní rozdíl je v pouºití znaku '#', je povaºován jako speciální znak, ve SmaCC uvozuje komentá°. Overlapping SmaCC umoº¬uje tzv. Overlapping, £esky p°ekrývání. Tento mechanismus umoº¬uje denovat dva tokeny se stejným obsahem. kdyº Scanner nalezne znak 'a', vyhodnotí ho jako kolekci token· <IDENTIFIER> a <TYPE_NAME>. V p·vodní implementaci SmaCC (a vygenerovaném Parseru) se tento efekt nijak neuplat¬uje a je akceptován pouze první denovaný token, v na²em p°íkladu to je token <IDENTIFIER>. Tuto implementaci je moºné roz²í°it, aby byl overlapping ú£inný. Následující kód prochází v²echny identikátory, které vyhovojí nalezenému °et¥zci, a zkou²í, jestli pro n¥který z nich existuje v gramatice p°echod. Pro první takový se výpo£et ukon£í a nalezená akce se vrátí. Jestliºe se pro ºádný identikátor nenalezne p°echod, vrátí se chybový stav. Kolekce identikátor· v prom¥nné currentToken je uspo°ádaná podle po°adí ve vstupním souboru. actionForCurrentToken |action| currentToken id do: [:id| action := self actionFor: id. action ~= self errorAction ifTrue:[^action]]. ^self errorAction Obsluºné metody Dal²í vlastností nástroje SmaCC je moºnost denovat pro kaºdý token obsluºnou metodu. Tato metoda se musí jmenovat p°esn¥ stejn¥ jako identikátor tokenu, tedy bez znak· '<>'. Obsluºná metoda se vykoná ihned po nalezení tokenu. Tímto zp·sobem je moºné ovliv¬ovat chování Scanneru. Na konci obsluºné metody je pot°eba vrátit nalezený token, který se nemusí shodovat s tokenem, který nalezl Scanner a podle kterého zavolal obsluºnou metodu. Vlastnost denice obsluºných metod a overlapping se z £ásti navzájem vylu£ují. Existují-li pro vstupní proud dva tokeny a pro n¥který znich i obsluºná metoda, tato metoda se nezavolá. Ta se zavolá pouze v p°ípad¥, ºe nalezený token je unikátní. 4.1. SMACC - SMALLTALK COMPILER-COMPILER 4.1.2 11 Generování t°ídy SmaCC Parser SmaCC podle dodané gramatiky vygeneruje t°ídu Parser. Stejn¥ jako pro generování Scanneru se gramatika zapisuje do externího souboru nebo p°ímo do okna aplikace, záloºky Parser. Vstupní soubor dat pro generování Parseru se d¥lí na dv¥ £ásti, nepovinnou £ást s direktivy a £ást se samotnými pravidly. Direktivy SmaCC denuje p¥t moºných direktiv: identikátory token·, °e²ení nejednozna£nosti gramatiky (t°i moºnosti), startovní symbol. Kaºdá direktiva za£íná znakem '%' a je ukon£ena st°edníkem. Identikátory termínálních symbol· Denované obluºné metody Scanneru vyºadují jako návratovou hodnotu token (terminální symbol). Kaºdý token má p°i°azeno identika£ní £íslo. Pro vytvo°ení tokenu je tento identikátor vyºadován. Direktiva id zajistí, ºe se do t°ídy Scanner vygeneruje metoda se jménem ve tvaru NázevTokenuId. Scanner Parser - <IDENTIFIER> : [a-zA-Z] w*; %id <IDENTIFIER>; Tímto zp·sobem se SmaCC °ekne, ºe má vygenerovat metodu IDENTIFIERId do t°ídy Scanner, která bude vracet identika£ní £íslo tokenu IDENTIFIER. Toto £íslo by se dalo zjistit z kódu, ale p°i zm¥n¥ gramatiky by se mohlo zm¥nit, direktiva id tuto nep°íjemnost °e²í. Dal²ím efektem je, ºe se pomocí této direktivy dá denovat nový token. Ten nemá ºádný denovaný výraz (není denován ve vstupním souboru token·), ale lze ho pouºít v pravidlech gramatiky a v obsluºných metodách (jako návratovou hodnotu). e²ení nejednozna£nosti pravidel P°i návrhu gramatiky se dbát na jednozna£nost. V nejednozna£né gramatice dochází ke konikt·m a p°eklada£ v tu chvíli "neví" jaké pravidlo má pouºít. SmaCC rozeznává konikty reduce/reduce a shift/reduce. Konikt reduce/reduce se nedá vy°e²it jinak, neº úpravou gramatiky. V¥t²inu konikt· typu shift/reduce lze vy°e²it pouºitím direktiv 'left','right' a 'nonassoc' - preceden£ní pravidla. U t¥chto direktiv záleºí na po°adí zápisu. Nejniº²í prioritu mají tokeny uvedené na za£átku. Navíc lze ur£it, jaké terminály jsou associované z jaké strany. Direktiva left ur£uje, ºe tokeny uvedené za touto direktivou jsou lev¥ asociativní. Direktiva right ur£uje, ºe tokeny uvedené za touto direktivou jsou prav¥ asociativní. Direktiva nonassoc up°es¬uje jen prioritu token·, bez specikace associativity. Výsledkem výrazu '2+3*4' gramatiky bez preceden£ních pravidel4.2 by byla hodnota 20, ale správn¥ je 14. Výsledkem výrazu '2+3*4' gramatiky bez preceden£ních pravidel4.3, ale s vy°e²enými prioritami, by byla hodnota 14, ten je správný. Ale gramatika je p°íli² sloºitá a náchylná na chyby v návrhu. Výsledkem výrazu '2+3*4' gramatiky s preceden£ními pravidly4.4 by byla hodnota 14, ten je správný. Gramatika je shodná jako v prvním p°ípad¥, priority °e²í preceden£ní pravidla. Takovou gramatiku jiº není problém navrhnout. 12 KAPITOLA 4. POUITÉ NÁSTROJE Expression : Expression "+" | Expression "-" | Expression "*" | Expression "/" | Number; Number : <number>; Expression Expression Expression Expression Obrázek 4.2: Gramatika bez pouºití direktiv a s nevy°e²enými prioritami Expression : Term | Expression "+" Term | Expression "-" Term; Term : Number | Term "*" Number | Term "/" Number; Number : <number>; Obrázek 4.3: Gramatika bez pouºití direktiv s vy°eºenými prioritami %left "+" "-"; %left "*" "/"; Expression : Expression "+" | Expression "-" | Expression "*" | Expression "/" | Number; Number : <number>; Expression Expression Expression Expression Obrázek 4.4: Gramatika s direktivy 4.1. SMACC - SMALLTALK COMPILER-COMPILER 13 Startovní symbol Direktiva 'start' ur£uje, který neterminál nebo neterminály budou startovními symboly. Pokud není tato direktiva uvedena, je startovním symbolem první uvedený neterminál. Typickým p°ípadem je p°eklada£ Smalltalkového kódu. Ve Smalltalku je moºné p°eloºit kód metody nebo pouze výraz. Gramatiky t¥chto dvou kód· je tém¥° shodné a nemá cenu vytvá°et dv¥ tém¥° totoºné. Direktivou %start method, expression; °ekneme SmaCC, ºe gramatika m·ºe za£ít neterminálem method nebo neterminálem expression. Klí£ová slova V denici gramatiky se mohou pouºívat p°ímo klí£ová slova daného jazyku. P°íkladem m·ºe být klí£ové slovo 'if'. Lze ho vyjmenovat mezi tokeny pro gererování Scanneru nebo ho zapsat p°ímo do gramatiky mezi dvojité uvozovky. Nevýhodou je, ºe pro takto denované klí£ové slovo nelze vytvo°it obsluºnou metodu. Denice pravidel gramatiky Formát denice je následující Neterminál : (PosloupnostElement· { smalltalkVýraz }? (| PosloupnostElement· { smalltalkVýraz }?)*)?; Kaºdé pravidlo je vyjád°eno ve tvaru neterminální symbol, následuje znak ':' a pak list posloupností element· (terminálních a neterminálních symbol· a klí£ových slov), kde kaºdá posloupnost element· je odd¥lena znakem '|'. Celé pravidlo za ukon£eno st°edníkem. Za kaºdou posloupností element· mohou následovat smal¨talkový kód, uzav°ený do sloºených závorek. Tento kód se vykoná p°i redukci tohoto pravidla a návratovou hodnotou tohoto bloku je automaticky poslední p°íkaz, není-li d°íve pouºit znak ''. Kód pro redukci pravidla SmaCC p°i vyhodnocování pravidla ukládá výsledky jednotlivých terminál·, neterminál· a klí£ových slov do kolekce. Tato kolekce se pak pouºívá v reduk£ním kódu. Pro získání elementu se uvádí jen jeho index uzav°ený do apostrof·, £íslováno od 1. Elementem je bu¤ instance t°ídy Token, která má poloºku value (na£tený °et¥zec), nebo návratový objekt získaný p°i redukci vnit°ního pravidla4.5. Pojmenované £ásti pravidel V p°ede²lém odstavci se pouºívaly indexy v reduk£ním kódu. SmaCC umoº¬uje tzv. pojmenované výrazy v pravidlech, pro lep²í orientaci. Za kaºdým elementem se m·ºe mezi jednoduché apostrofy uvést pojmenování tohoto elementu. Tento identikátor se pak pouºívá pro odkazování v reduk£ním smalltalkovém kódu 4.5. 14 KAPITOLA 4. POUITÉ NÁSTROJE %left "+" "-"; %left "*" "/"; Expression : Expression "+" Expression {'1' + '3'} | Expression "-" Expression {'1' - '3'} | Expression 'exp1' "*" Expression 'exp2' {exp1 * exp2} | Expression 'exp1' "/" Expression 'exp2' {exp1 / exp2} | Number 'number' {number}; Number : <number> 'numberToken' {numberToken value asNumber}; Obrázek 4.5: P°íklad denice gramatiky 4.2 IRBuilder IRBuilder je generátor smalltalkového bytekódu. Projekt SmallRuby generuje pomocí IRBuilderu pro jednotlivé uzly AST stromu p°íslu²ný bytekód. konstrukce Ruby, rozbor gramatiky (v£etn¥ diskuse r·zných alternativ a volby implementa£ního prost°edí). Kapitola 5 Implementace 5.1 T°ídy Ruby je £ist¥ objektový jazyk, jako takový je zaloºen na objektech (instancích t°íd). Ve²kerá volání jsou zprávy posílané objekt·m. Názvy t°íd za£ínají v Ruby velkým písmenem, jinak p°eklada£ hlásí chybu a zdrojový kód není moºné p°eloºit. Názvu t°ídy m·ºe p°edcházet název t°ídy odd¥lený dv¥mi dvojte£kami (£ty°te£nou), v takovém p°ípad¥ se tato t°ída vytvo°í jako tzv. vno°ená t°ída. Vno°ené t°ídy jsou více popsány v kapitole 5.1.1. class A end class Ruby::A end class Smalltalk::A end Jazyk Ruby (podobn¥ jako Smalltalk) umoº¬uje p°idávat metody do jiº existujích t°íd, a to dv¥ma zp·soby: • p°íkazem pro vytvo°ení t°ídy (v p°ípad¥, ºe t°ída existuje, aktualizuje se, jinak se vytvo°í t°ída nová), • p°íkazem pro vytvo°ení metody, kde názvu metody p°edchází t°ída odd¥lená £ty°te£kou (takto lze vytvá°et pouze t°ídní metody). (name startsWith: 'Smalltalk::') ifTrue: [^Smalltalk at: (name copyFrom: 12) asSymbol]. (name startsWith: 'Ruby::') ifTrue: [^(Smalltalk at: #Ruby) at:(name copyFrom: 7) asSymbol]. ^(Smalltalk at: #Ruby) at: name asSymbol ifAbsent: [Smalltalk at: name asSymbol]. 15 16 KAPITOLA 5. IMPLEMENTACE Algoritmus dostane jako argument pln¥ kvalikujicí smalltalkové jméno t°ídy (nap°. Trida::Podtrida). Nejprve se kontroluje, zda jméno neza£íná prexem Smalltalk::. Tento prex V p°ípad¥, ºe t°ída existuje, pracuje s ní, jinak vytvo°í novou a pracuje s touto novou t°ídou. Smallruby vytvá°í nové t°ídy do vlastního jmenného prostoru (Namespace Ruby), protoºe nebyl navrºen zp·sob, jak p°eklada£i jednozna£n¥ sd¥lit, aby pouºil jiný (zadaný) jmenný prostor. Jediná moºnost, jak vytvá°et Ruby metody do t°íd mimo jmenný prostor Ruby, je pouºít jiº d°íve diskutovanou vlastnost Ruby - p°idávání metod do existujících t°íd. Smalltalk prex 5.1.1 Vno°ené t°ídy (Nested classes) Jazyk Smalltalk jako takový vloºené t°ídy nepodporuje, jeho implementace Smalltalk/X ano. Jednotlivé t°ídy jsou v syntaxi Ruby odd¥leny dvojnásobnou dvojte£kou (£ty°te£kou). Dal²í variantou je, ºe název t°ídy za£íná £ty°te£nou. V takovém p°ípad¥ se jako mate°ská t°ída bere t°ída Object. class A end class A::B end class A class B end end class 1.class::C end t = Trida.new class t.vratTridu::D end class ::E end nutnost pamatovat si kontext trid (classNameStack) pamatuje si nadrazenou tridu 5.2 Metody název metod (bez parametru, s jednim parametrem, s vice nez jedn) 5.3. KONSTANTY 5.3 Konstanty 5.4 Globální prom¥nné 5.5 Více hodnotové p°i°azení 5.6 Více hodnotový návrat z metody 5.7 et¥zce 17 V Ruby existují dva druhy °et¥zc·, vyhodnocované a nevyhodnocované. Nevyhodnocované °et¥zce Nevyhodnocovaný °et¥zec m·ºe za£ínat znakem apostrofu nebo sekvencí znak·, zapsané regulárním výrazem ^\%q[^\w\)\]\}>]\$. Znak, který následuje za znaky %q, je zárove¬ i ukon£ujícím znakem celého °et¥zce. Výjimkou jsou znaky levých závorek ('(' '[' ''), takové °et¥zce musí kon£it znakem odpovídající pravé závorky (')' ']' ''). Ve²keré znaky mezi za£átkem a koncem nevyhodnocovaného °et¥zce jsou brány jako obsah °et¥zce. Jediným vyhodnocením je, ºe dv¥ zp¥tná lomítka za sebou jsou vyhodnocena jako jedno zp¥tné lomítko a zp¥tné lomítko následované apostrofem je vyhodnoceno jako apostrof. Vyhodnocované °et¥zce Vyhodnocovaný °et¥zec m·ºe za£ínat znakem uvozovek nebo sekvencí znak·, zapsané regulárním výrazem ^\%Q?[\^\w\)\]\}>]\$. Znak, který následuje za znaky %Q (p°ípadn¥ jen za znakem %), chápán stejn¥ jako u nevyhodnocovanýho °et¥zce. Mezi znaky za£átku a koncem vyhodnocovaného °et¥zce se m·ºe vyskytovat prostý °et¥zec nebo výraz, který je následn¥ vyhodnocen. Vyhodnocovaný výraz za£íná znakem '#', následuje název prom¥nné nebo p°íkazy uzav°ené mezi sloºené závorky. Výsledná hodnota tohoto výrazu je p°evedena na °et¥zec a vytisknuta. Speciální °et¥zce Ruby mimo textových °et¥zc· rozli²uje je²t¥ tzv. speciální °et¥zce. Speciálním se myslí, ºe výsledným objektem není textový °et¥zec, ale n¥který z následn¥ uvedených objekt·. 18 KAPITOLA 5. IMPLEMENTACE Pole jednotlivých slov Ruby umoº¬uje vytvo°it pole °et¥zc· nejen klasickým zp·sobem (vytvo°it instanci pole a p°idat jednotlivá slova), ale i speciálním p°íkazem. P°íkaz za£íná sekvencí znak·, zapsané pomocí regulárního výrazu ^\%[wW][^\w\)\]\}> ]\$, následují jednotlivá slova odd¥lená mezerou nebo novým °ádkem. P°íkaz je zakon£en stejným znakem, který následuje za znaky %w nebo %W (s výjimkou znak· závorek). Pro vyjád°ení mezery ve slov¥ se pouºívá escape sekvence (tj. znak '«ásledovaný mezerou). Jako u °ez¥zc·, tak i zde se rozli²uje mezi vyhodnocovaným a nevyhodnocovaným °et¥zcem. P°íkaz %w zna£í nevyhodnocovaný °et¥zec (podobnost s nevyhodnocovanými °et¥zci 5.7), p°íkaz %W naopak uvozuje vyhodnocovaný °et¥zec (viz 5.7). Výsledkem celého p°íkazu je pole slov, která byla odd¥lena mezerou (novým °ádkem). Regulární výraz Jazyk Ruby hojn¥ vyuºívá porovnání °et¥zc· a regulárních výraz· (operátor = ). Tento operátor vrací, p°i nalezení shody regulárního výrazu n¥kde v °et¥zci, index prvního znaku shody. V p°ípad¥, ºe nenastala ºádná shoda, vrací NIL. Jelikoº Ruby implicitn¥ umí konverzi jakéhokoli objektu na objekt boolean (NIL a false se konvertuje na false, ostatní je true), ale operátor = pouºít pro v¥tvení programu, v£etn¥ p°íkazu CASE [1]. Regulární výraz je chová stejn¥ jako vyhodnocovaný °et¥zec, lze v jeho denici pouºívat výrazy jazyku Ruby (viz 5.7). Zápis regulárního výrazu má dv¥ podoby. Bu¤ je uvozen a zakon£en znakem '/' nebo je uvozen sekvencí znak· ^\%r[^\w\)\]\}>]\$ a ukon£en znakem, který následoval za znaky %r (s výjimkou znak· závorek). P°íkaz opera£ního systému V prost°edí Ruby lze spou²t¥t p°íkazy opera£ního systému. Návratovou hodnotou takového p°íkazu je text, zapsaný spu²t¥ným p°íkazem na standardní výstup. Stejn¥ jako zápis regulárního výrazu i p°íkaz opera£nímu systému má dva moºné zápisy. Jeden za£íná i kon£í znakem '`', druhý podle vzoru °et¥zc· za£íná sekvencí znak· ^\%x[^\w\)\]\}>]\$ a kon£í znakem, který následoval za znaky %x (s výjimkou znak· závorek). Stejn¥ jako u regulárních výraz· je p°íkaz opera£nímu systému vyhodnocovaným °et¥zcem 5.7. 5.7.1 Úprava gramatiky Na úrovni gramatiky referen£ní implementace nejsou oba typy °et¥zc· (vyhodnocované a nevyhodnocované) rozli²eny. Rozdílné chování je naprogramováno p°ímo v parseru. Pro zjednodu²ení parsování nevyhodnocovaného °et¥zce byla pozm¥n¥na p·vodní gramatiku 5.1 - byla p°idána pravidla pro parsování nevyhodnocovaného °et¥zce. Terminální symboly STRING_BEG a STRING_END se vyhodnocovaly dynamicky podle stavu parseru. Jak bylo napsáno vý²e, uvedená gramatika nerozli²uje mezi vyhodnocovaným a nevyhodnocovaným typem °et¥zce. Proto i terminál STRING_CONTENT byl vyhodnocován dynamicky, a to aº v p°ípad¥, ºe byl nalezen za£átek °et¥zce. Nov¥ byl zaveden terminál charakterizující za£átek nevyhodnocovaného °et¥zce (SIMPLE_STRING_BEG) a up°esn¥n, p·vodn¥ dynamický, terminál za£átku vyhodnocovaného °et¥zce (STRING_BEG). Terminály WORDS_BEG, QWORDS_BEG, XSTRING_BEG a REGEXP_BEG uvozují speciální °et¥zce. 19 5.7. ET ZCE string1 : <STRING_BEG> string_contents <STRING_END> {Ruby::StringNode with:'2'} | <SIMPLE_STRING_BEG> <STRING_CONTENT> <STRING_END> {Ruby::SimpleStringNode content:'2'} | <SIMPLE_STRING_BEG> <STRING_END> {Ruby::SimpleStringNode noContent}; string1_OLD : <STRING_BEG> string_contents <STRING_END>; string_contents : {OrderedCollection new} | xstring_contents string_content {'1' add:'2'; yourself}; string_content : <STRING_CONTENT> { Ruby::StringContentNode with:'1' } | <STRING_DVAR> string_dvar { Ruby::StringDVarNode with:'2' } | <STRING_DBEG> compstmt <RCURLY> { Ruby::StringStatementNode with:'2' }; string_dvar : | | | <GVAR> {Ruby::VariableGlobalNode value: '1'} <IVAR> {Ruby::VariableInstanceNode value: '1'} <CVAR> {Ruby::VariableClassNode value: '1'} backref {'1'}; Obrázek 5.1: Upravená gramatika pro parsování °et¥zce1 20 KAPITOLA 5. IMPLEMENTACE <QWORDS_BEG>: <WORDS_BEG>: <REGEXP_BEG>: <STRING_BEG>: <XSTRING_BEG>: <SIMPLE_STRING_BEG>: %w[^\w\)\]\}> ]; %W[^\w\)\]\}> ]; /|(%r[^\w\)\]\}>]); "|(%Q?[^\w\)\]\}>]); `|(%x[^\w\)\]\}>]); '|(%q[^\w\)\]\}>]); Pro v²echny tokeny byly vytvo°eny oblu²né metody. Úkolem t¥chto metod je • nastavit p°íznak pro parsování °et¥zce (parseString) - °ídí výb¥r parseru, výchozí hodnota true • nastavit p°íznak, signalizující, ºe se parser nachází v prost°edí °et¥zce (inString) pro op¥tovné nastavení p°íznaku parseString p°i návratu z vyhodnocování výrazu, výchozí hodnota true • nastavit znak, ukon£ující °et¥zec (endOfString) - ovliv¬uje chovaní parseru prostého °et¥zce5.7.2, nastaví se podle aktuáln¥ na£teného znaku (pouze pro znaky '(', '[', '{', '<' se nastaví jejich prot¥j²ky) • nastavit p°íznak, zda se jedná o vyhodnocovaný nebo nevyhodnocovaný °et¥zec (evalStringExpression) - ovliv¬uje chovaní parseru prostého °et¥zce5.7.2 • v p°ípad¥, ºe se jedná o terminály WORDS_BEG a QWORDS_BEG, nastaví se p°íznak wordsScope - parsování slov • p°íznak rstWordDelimiter/lastWordDelimiter se nastaví na false. Mají význam jen p°i parsování slov. Ur£ují, zda byl vytvo°en první/poslední odd¥lova£ slov vyºadováno gramatikou, výchozí hodnota false Dal²í zm¥na se týkala p°epsání pravidel pro parsování °et¥zce, který vytvá°í z jednotlivých slov pole slov (viz 5.2). Parser p·vodní gramatiky ignoroval bílé znaky p°ed prvním slovem. Naopak ukon£ovacímu znaku musí p°edcházet alespo¬ jeden bílý znak, který je vyjád°en terminálem WORDS_DELIMITER. musí p°edcházet znamená, ºe tento terminál se p°ípadn¥ um¥le vygeneruje, kdyº následuje rovnou ukon£ující znak °et¥zce. Gramatika, kterou pouºívá Smallruby, zohled¬uje i první bílé znaky (terminál WORDS_DELIMITER). Tato úprava vyºaduje p°ípadné um¥lé vygenerování prvního terminálu WORDS_DELIMITER. 5.7.2 Parser °et¥zce Kv·li dynamicky se m¥nícímu významu terminálu STRING_CONTENT bylo pot°eba vytvo°it zvlá²tní parser pro na£tení obsahu prostého °et¥zce5.7.2. Nástroj SmaCC generuje metodu scanForToken. Tato metoda £te jednotlivé znaky vstupního °et¥zce a podle vnit°ního stavu, pravidel parseru a na£tené znaku vyhodnocuje, zda v·bec a jaký terminál (token) má nyní vrátit. Protoºe se jedná o generovanou metodu, není p°ípustné jí p°ímo modikovat. Následné generování kódu, po zm¥n¥ souboru gramatiky, by tuto Pravidla kon£ící znaky _OLD jsou p·vodní pravidla, Smallruby pouºívá jejich upravené varianty (tj. bez tohoto suxu) 1 21 5.7. ET ZCE words : <WORDS_BEG> word_list <STRING_END> {Ruby::WordsNode with:'2'}; words_OLD : <WORDS_BEG> <WORDS_DELIMITER> <STRING_END> | <WORDS_BEG> word_list <STRING_END>; word_list : <WORDS_DELIMITER> {OrderedCollection new} | word_list word <WORDS_DELIMITER> {'1' add:'2'; yourself}; word_list_OLD : | word_list word <WORDS_DELIMITER>; word : string_content {Ruby::WordNode with:'1'} | word string_content {'1' addNode:'2'; yourself}; qwords : <QWORDS_BEG> qword_list <STRING_END> {Ruby::QWordsNode with:'2'}; qwords_OLD : <QWORDS_BEG> <WORDS_DELIMITER> <STRING_END> | <QWORDS_BEG> qword_list <STRING_END>; qword_list : <WORDS_DELIMITER> {OrderedCollection new} | qword_list <STRING_CONTENT> <WORDS_DELIMITER> {'1' add:(Ruby::StringContentNode with:'2'); yourself}; qword_list_OLD : | qword_list <STRING_CONTENT> <WORDS_DELIMITER>; Obrázek 5.2: Upravená gramatika pro parsování jednotlivých slov na pole slov1 22 KAPITOLA 5. IMPLEMENTACE scanForStringToken | peekChar step escape | step := false. escape := false. [ peekChar := self stream peek. (wordsScope and:[firstWordDelimiter]) or:[escape not and:[((evalStringExpression and:[peekChar == \$#]) or:[peekChar == endOfString]) or:[wordsScope and:[peekChar isSeparator]]]] ] whileFalse:[ self step. step:= true. firstWordDelimiter := false. lastWordDelimiter := false. escape := escape not and:[currentCharacter == $\]. ]. step ifFalse:[ (wordsScope and:[(peekChar isSeparator or:[ firstWordDelimiter not])]) ifTrue:[ firstWordDelimiter := false. self scanForWordDelimiterToken ]. peekChar == endOfString ifTrue:[self scanForStringEndToken ]. peekChar == \$# ifTrue:[ self scanForDynamicStringToken ]. ] ifTrue:[ self recordAndReportMatch: (self setMatchActions: self STRING_CONTENTId) ]. Obrázek 5.3: Parser obsahu °et¥zce úpravu p°epsalo. SmaCC na²t¥stí k této metod¥ nep°istupuje p°ímo, ale p°es metodu next. Volání metody scanForToken bylo nahrazeno voláním metody rubyScanForToken, která rozli²uje, zda se parsuje obsah °et¥zce nebo Ruby p°íkaz ( = volba parseru). rubyScanForToken parseString ifTrue:[ self scanForStringToken ] ifFalse:[ self scanForToken ]. Volba parseru závisí na prom¥nné parseString, která se nastavuje p°i na£tení tokenu se za£átkem °et¥zce (v jiº zmín¥ných obsluºných metodách). Parserem °et¥zce je metoda scanForStringToken, která se volá v p°ípad¥ nastavení prom¥nné parseString. Hlavním úkolem tohoto miniparseru je na£ítat obsah terminálu STRING_CONTENT v závislosti na druhu °et¥zce (vyhodnocovaný, nevyhodnocovaný, pole slov). 5.7. ET ZCE 23 scanForStringEndToken (wordsScope and:[ lastWordDelimiter not ]) ifTrue:[ self scanForWordDelimiterToken ] ifFalse:[ self step. self resetStringFlags. self recordAndReportMatch: (self setMatchActions: self STRING_ENDId). ]. Obrázek 5.4: scanForStringEndToken - metoda obsluhující na£tení konce °et¥zce 5.7.2.1 scanForStringToken - nevyhodnocovaný °et¥zec Tento typ °et¥zce za£íná terminálem SIMPLE_STRING_BEG, jehoº obsluºná metoda nastaví prom¥nné evalStringExpression a wordsScope na hodnotu false (tj. nevyhodnocovaný °et¥zec, ned¥lit podle slov), ostatní prom¥nné jsou nastaveny na výchozí hodnoty 5.7.1. Postup parseru je pak následující: 1. cyklicky na£ítá v²echny znaky, dokud nenarazí na znak, který je shodný s hodnotou prom¥nné endOfString (konci °et¥zce znaku nesmí p°edcházet znak ', ozna£ující escape sekvenci, která znak bere jako nevýznamný). Následn¥ se vyhodnotí, zda byl na£ten alespo¬ jeden znak, pokud ano, pokra£uje se k bodu 2, jinak k bodu 3 2. Parser vrátí terminál STRING_CONTENT. Algoritmus pokra£uje bodem 1 (dal²ím znakem v po°adí je zaru£en¥ ukon£ovací znak °et¥zce) 3. V tuto chvíli není p°íznak step. Následná porovnání zavolají metodu scanForStringEndToken 5.4. Ta resetuje v²echny p°íznaky týkající se °et¥zc· a vrátí token STRING_END. Algoritmus parsování °et¥zce kon£í (p°íznak parseString má hodnotu false) P°eklad Vyhodnocení nevyhodnocovaného °et¥zce je provede vloºením obsahu °et¥zce na zásobník..............upravit obsah........zatim nejde - chyby po znacich ' Metoda pro vyhodnocení instance t°ídy SimpleStringNode: visitSimpleStringNode:anObject irBuilder pushLiteral:anObject content. 24 KAPITOLA 5. IMPLEMENTACE 5.7.2.2 scanForStringToken - vyhodnocovaný °et¥zec Vyhodnocovaný typ °et¥zce je výrazn¥ sloºit¥j²í. Parser musí kontrolovat nejen znak konce °et¥zce, ale i za£ínající výraz. Vyhodnocovaný °et¥zec za£íná terminálem STRING_BEG (resp. XSTRING_BEG, REGEXP_BEG), jeho obsluºná metoda nastaví hodnotu prom¥nné evalStringExpression na hodnotu true, ostatní stejn¥ jako u nevyhodnocovaného °et¥zce 5.7.2.1. Parser vykonává následující akce: 1. Postupn¥ na£ítá znaky °et¥zce, dokud nenarazí na ukon£ovací znak nebo znak '#' uvozující výraz (znaku nesmí p°edcházet escape sekvence). Na£tením alespo¬ jednoho znaku °et¥zce se nastaví p°íznak step. Je-li nastaven p°íznak step, algoritmus pokra£uje bodem 2, v opa£ném p°ípad¥ bodem 3 2. Byl na£ten alespo¬ jeden znak °et¥zce, parser vrátí terminál STRING_CONTENT a pokra£uje bodem 1 3. Parser na£etl znak, který nepat°í do obsahu °et¥zce (p°íznak step má hodnotu false). Postupn¥ vyhodnocuje následující moºnosti (a) na£tený znak se shoduje se znakem ukon£ujícím °et¥zec. Zavolá se metoda scanForStringEndToken 5.4 a parsování °et¥zce kon£í. (b) na£tený znak ('#') uvozuje výraz. Zavolá se metoda scanForDynamicStringToken 5.5. Tato metoda kontroluje následující znak. O£ekává se znak uvozující blok výrazu ('{') nebo znaky ozna£ující n¥kterou z prom¥nných ($ - globální, @ - instan£ní nebo t°ídní). Jinak se vráti terminál STRING_CONTENT a pokra£uje se bodem 1. P°i vrácení terminálu, který uvozuje za£átek bloku (STRING_DBEG), se na£te následující znak a resetuje p°íznak parseString. P°i vrácení terminálu o za£átku prom¥nné (STRING_DVAR) se jen resetuje p°íznak parseString. V p°ípad¥, ºe se v rámci °et¥zce vyhodnocuje výraz, je pot°eba po jeho rozparsování zp¥tn¥ nastavit p°íznak parseString, aby se mohlo pokra£ovat v parsování °et¥zce. To zaji²´uje volání setParseString na instanci scanneru. Metoda podle p°íznaku inString zjistí, zda se parsuje °et¥zec, jestliºe ano, nastaví p°íznak parseString, jinak ho ponechá beze zm¥ny. Metoda setParseString se volá p°i redukci pravidla pro sloºený výraz (compstmt) a v rámci obsluºné metody pro globální (GVAR), t°ídní (CVAR) a instan£ní (IVAR) prom¥nnou. Kontrola p°íznaku inString je d·leºitá. Pravidlo compstmt se nevolá jen p°i parsování °et¥zce, ale pro kaºdý blok Ruby p°íkaz·. P°íznak nastavují pouze obsluºné metody terminál· za£átku °et¥zce a resetuje pouze metoda terminálu konce °et¥zce. Tím se zajistí korektní p°epínání parser·. Nastavení p°íznaku parseString není moºné ud¥lat aº p°i redukci pravidla <STRING_DVAR> string_dvar nebo <STRING_DBEG> compstmt <RCURLY>. Parser pot°ebuje znát následující terminál, aby rozhodl, zda má redukovat nebo srovnávat. V na²em p°ípad¥ by na£etl následující £ást °et¥zce pomocí klasického parseru a nevyhodnotil ho jako terminál, který je v °et¥zci p°ípustný. Protoºe byly vytvo°eny obsluºné metody pro prom¥nné (GVAR, CVAR, IVAR), které pot°ebují znát na£tené znaky (= název prom¥nné), bylo nutné modikovat p·vodní metody SmaCC scanneru (metoda reportLastMatch). Tato metoda podle instance na£teného terminálu rozhoduje, jestli p°ímo vrátí nalezený terminál (prom¥nná matchActions scanneru typu Collection) nebo zavolá obsluºnou metodu (prom¥nná matchActions scanneru 25 5.7. ET ZCE scanForDynamicStringToken |peekChar| self step. peekChar := self stream peek. peekChar == \$\{ ifTrue:[ parseString := false. self step. self recordAndReportMatch: (self setMatchActions: self STRING_DBEGId) ] ifFalse:[ (peekChar == \$\$ or:[ peekChar == \$@ ]) ifTrue:[ parseString := false. self recordAndReportMatch: (self setMatchActions: self STRING_DVARId) ] ifFalse:[ self recordAndReportMatch: (self setMatchActions: self STRING_CONTENTId) ] ] Obrázek 5.5: Parser za£átku vyhodnocovaného výrazu typu Symbol). P·vodní metoda p°i volání obsluºné metody na£tený °et¥zec zahazuje, proto byla upravena tak, ºe na£tený vºdy uloºí do instan£ní prom¥nné lastString. Tato prom¥nná se následn¥ pouºije p°i vytvá°ení token· GVAR, CVAR a IVAR v obsluºných metodách. P°eklad Gramatika tohoto typu °et¥zce je uvedena na obrázku 5.1. Za kaºdým pravidlem je reduk£ní pravidlo, které vrací daný uzel AST stromu. Výsledným uzlem AST stromu p°i parsování vyhodnocovaného °et¥zce je instance t°ídy StringNode. Ta obsahuje kolekci jednotlivých uzl· (StringContentNode, StatementNode, StringDVarNode). P°i vyhodnocování uzlu StringNode se na zásobník vloºí prázdný °ez¥zec. Následn¥ se prochází jednotlivé vnit°ní uzly. V kaºdé iteraci se daný uzel vyhodnotí a zavolá se metoda z°et¥zení. Vyhodnocení instance t°ídy StringContentNode spo£ívá v uloºení obsahu °et¥zce na zásobník. P°i vyhodnocení instancí t°íd StringStatementNode a StringDVarNode se nejprve vyhodnotí výraz a pak se zavolá metoda asString, jejímº výsledkem bude textová reprezentace výsledku výrazu. Takto se postupn¥ z°et¥zcí v²echny vnit°ní uzly °et¥zce a výsledkem je vyhodnocený °et¥zec na vrholu zásobníku. Metoda pro vyhodnocení instance t°ídy StringNode: 26 KAPITOLA 5. IMPLEMENTACE pushLiteral: pushLiteral: pushLiteral: str1 str2 send: #',' numArgs: 1 send: #',' numArgs: 1 pushLiteral: pushLiteral: str3 send: #',' numArgs: 1 pushLiteral: 1 pushLiteral: 1 send: #'+' numArgs: 1 send: #asString numArgs: 0 send: #',' numArgs: 1 send: #',' numArgs: 1 Obrázek 5.6: P°íklad vygenerovaného kódu p°íkazu "str1 str2" "str3#{1+1}" visitStringNode:anObject irBuilder pushLiteral:''. anObject nodes do:[ :node | self visit:node. irBuilder send:#,. ] Metoda pro vyhodnocení instance t°ídy StringContentNode: visitStringContentNode:anObject irBuilder pushLiteral:anObject content. Metoda pro vyhodnocení instance t°ídy StringStatementNode: visitStringStatementNode:anObject self visit: anObject statement. irBuilder send:#asString. Metoda pro vyhodnocení instance t°ídy StringDVarNode: visitStringDVarNode:anObject self visit: anObject variable. irBuilder send:#asString. 5.7.2.3 scanForStringToken - pole slov Pole slov se vytvá°í speciálním p°íkazem 5.7. Pro parsování tohoto p°íkazu bylo pot°eba roz²í°it parser °et¥zce. P°i vygenerování terminálu WORDS_BEG (resp. QWORDS_BEG) se krom¥ p°íznak· vyhodnocovaného (resp. nevyhodnocovaného) °et¥zce nastaví p°íznak 27 5.7. ET ZCE scanForWordDelimiterToken [ self stream peek isSeparator ] whileTrue:[ self step ]. lastWordDelimiter := true. self recordAndReportMatch: (self setMatchActions: self WORDS_DELIMITERId). Obrázek 5.7: Parser odd¥lova£e jednotlivých slov wordsScope. Tento p°íznak parseru °íká, aby cyklické na£ítání znak· °et¥zce ukon£il nejen p°i spln¥ní pravidel daného typu °et¥zce, ale i p°i nalezení bílého znaku, kterému nep°edchází escape sekvence. Sekvenci bílých znak· parser vyhodnotí jako terminál WORDS_DELIMITER. To zaji²´uje metoda scanForWordDelimiterToken 5.7, která navíc nastavuje p°íznak lastWordDelimiter. Tento p°íznaku ozna£uje, ºe posledním terminálem byl WORDS_DELIMITER. Na£tení minimáln¥ jednoho znaku °et¥zce p°íznak resetuje. Gramatika vyºaduje, aby p°íkaz pro vytvo°ení pole slov za£ínal i kon£il terminálem WORDS_DELIMITER. To zajistí p°íznaky rstWordDelimiter a lastWordDelimiter. P°íznak rstWordDelimiter se nastaví p°i prvním vrácení terminálu WORDS_DELIMITER a zajistí i um¥lé vygenerování tohoto terminálu. Následn¥ se st°ídav¥ vyhodnocují jednotlivá slova a terminály WORDS_DELIMITER. Jestliºe p°i na£tení ukon£ujícího znaku °et¥zce není nastaven p°íznak lastWordDelimiter, parser zavolá metodu scanForWordDelimiterToken 5.7, která bez na£tení jediného znaku vrátí pot°ebný terminál. Pak jiº parser m·ºe vrátit terminál konce °et¥zce. P°eklad Parsováním tohoto výrazu se vygeneruje instance typu WordsNode. Ta obsahuje kolekci objekt· WordNode, která reprezentuje jednotlivá slova p°íkazu. Výsledkem p°íkazu má být kolekce slov, proto se nap°ed vytvo°í prázdná kolekce, která se vloºí na zásobník a zárove¬ uloºí do lokální prom¥nné. Následn¥ se v iteraci vyhodnotí objekt slova a p°idá se do kolekce. Metoda add: vrací p°idávaný objekt, proto je nutné tento objekt odebrat ze zásobníku a vloºit na n¥j op¥t výslednou kolekci. Iterace prochází celou kolekci slov. Metoda pro vyhodnocení instance t°ídy WordsNode: visitWordsNode:anObject | coll | coll := OrderedCollection new. irBuilder pushLiteral: coll. anObject wordNodes do:[ :wordNode | self visit: wordNode. irBuilder send: #add:. irBuilder popTop. irBuilder pushLiteral: coll. ]. Metoda pro vyhodnocení instance t°ídy WordNode: 28 KAPITOLA 5. IMPLEMENTACE pushLiteral: OrderedCollection() pushLiteral: pushLiteral: word1 send: #',' numArgs: 1 send: #add: numArgs: 1 popTop pushLiteral: OrderedCollection() pushLiteral: pushLiteral: w send: #',' numArgs: 1 pushLiteral: 1 pushLiteral: 1 send: #'+' numArgs: 1 send: #asString numArgs: 0 send: #',' numArgs: 1 pushLiteral: ord2 send: #',' numArgs: 1 send: #add: numArgs: 1 popTop pushLiteral: OrderedCollection() pushLiteral: pushLiteral: word3 send: #',' numArgs: 1 send: #add: numArgs: 1 popTop pushLiteral: OrderedCollection() Obrázek 5.8: P°íklad vygenerovaného kódu p°íkazu %W{word1 w#{1+1}ord2 word3 } visitWordNode:anObject irBuilder pushLiteral:''. anObject nodes do:[ :node | self visit:node. irBuilder send:#,. ] 5.8 Bloky 5.9 T°ída Range 5.10 5.10.1 P°íkazy °ázení programu P°íkaz Case P°íkaz case má v Ruby t°i moºné podoby. === navesti 5.11. OPERÁTORY 5.10.2 P°íkaz if (unless) návesti 5.10.3 P°íkaz while (until) navesti 5.10.4 P°íkaz for navesti 5.11 Operátory and, or, ostatní 29 30 KAPITOLA 5. IMPLEMENTACE Kapitola 6 Testování testy funk£nosti (kompletnosti), výkonostní testy 31 32 KAPITOLA 6. TESTOVÁNÍ Kapitola 7 Zhodnocení zhodnocení spln¥ní cíl· práce, doporu£ení dal²ího pokra£ování práce atd. 33 34 KAPITOLA 7. ZHODNOCENÍ Kapitola 8 Záv¥r záv¥re£né slovo "m¥°ení se mi líbilo" Autor Smalltalk/X Claus Gittinger tento projekt uvítal, skriptovací jazyk mu v dosavadní implementaci Smalltalk/X chybí. Toto je dal²í motivací tohoto projektu a d·vod pro£ byl vybrán práv¥ jazyk Ruby. 35 36 KAPITOLA 8. ZÁV R Literatura [1] AbcLinux - Ruby pro zacátecníky (regulární výrazy). http://www.abclinuxu.cz/ clanky/programovani/ruby-pro-zacatecniky-6-regularni-vyrazy. [2] Alternativní implementace Ruby. alternativni-implementace-ruby.html. http://www.majda.cz/download/ [3] Bison home page. http://www.gnu.org/software/bison/. [4] Flex home page. http://flex.sourceforge.net/. [5] JavaCC home page. https://javacc.dev.java.net/. [6] Ruby home page. http://www.ruby-lang.org/en/. [7] SmaCC home page. http://www.refactory.com/Software/SmaCC/index.html. [8] SmaCC Scanner. http://www.refactory.com/Software/SmaCC/Scanner.html. [9] Wikipedia - JRuby. http://en.wikipedia.org/wiki/JRuby. [10] Yet Another Ruby VM. http://www.atdot.net/yarv/. [11] A. Goldberg and D. Robson. Smalltalk 80: Addison Wesley, Reading, Mass., May 1983. 37 the language and its implementation. 38 LITERATURA Dodatek A Seznam pouºitých zkratek 2D Two-Dimensional ABN Abstract Boolean Networks ASIC Application-Specic Integrated Circuit .. . 39
Podobné dokumenty
Článek - Moderní metody vyšetření v neuromuskulární
podminekpro dal5i
k oubua vytvoieniide5lnich
l6febn6postupy [14].
Implementace cásti jazyka XQuery v rámci projektu CellStore JanˇZák
This work concerns a subject of design and implementation of part of the XQuery language
in environment of native XML database CellStore. It analyses features of the language and
describes a way of...