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. POUšITÉ 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. POUšITÉ 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. POUšITÉ 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í

Článek - Moderní metody vyšetření v neuromuskulární podminekpro dal5i k oubua vytvoieniide5lnich l6febn6postupy [14].

Více

Implementace cásti jazyka XQuery v rámci projektu CellStore JanˇZák

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...

Více