gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela...
TRANSCRIPT
![Page 1: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/1.jpg)
Primož Kokol
Gradnja domensko specifičnih jezikov s
programskim jezikom Scala
Diplomsko delo
Maribor, julij 2012
![Page 2: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/2.jpg)
![Page 3: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/3.jpg)
I
Diplomsko delo univerzitetnega študijskega programa
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
Študent: Primož Kokol
Študijski program: UN ŠP Računalništvo in informatika
Smer: Programska oprema
Mentor: red. prof. dr. Marjan Mernik
Somentor: doc. dr. Tomaž Kosar
Maribor, julij 2012
![Page 4: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/4.jpg)
II
![Page 5: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/5.jpg)
III
ZAHVALA
Mentorju in somentorju se zahvaljujem za
strokovno pomoč in koristne napotke pri
opravljanju diplomskega dela.
Posebna zahvala velja staršem, ki so mi omogočili
študij.
Prav tako iz srca hvala Maji, ki me je pri delu
nenehno vzpodbujala.
![Page 6: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/6.jpg)
IV
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
Ključne besede: domensko specifični programski jeziki, programski jezik Scala
UDK: 004.434(043.2)
Povzetek
Scala je razmeroma nov, splošno namenski, statično tipiziran programski jezik, ki združuje
principe objektnega in funkcijskega programiranja. Predvsem principi funkcijskega
programiranja namigujejo na to, da bi omenjen jezik lahko predstavljal dobro alternativo
za implementacijo domensko specifičnih jezikov. V sklopu diplomskega dela smo preučili
ustreznost omenjenega programskega jezika za implementacijo domensko specifičnih
programskih jezikov na primeru jezika FDL (ang. Feature Description Language).
Primerjave ter implementacije omenjenega jezika so že bile realizirane v programskih
jezikih Java, Lisa, Haskell, C++, Smacc, C# v strokovnem članku A preliminary study on
various implementation approaches of domain-specific language avtorjev, Tomaž Kosar,
Pablo E. Martínez López, Pablo A. Barrientos, Marjan Mernik, kar predstavlja podlago za
primerjavo z implementacijo programskega jezika FDL v programskem jeziku Scala.
![Page 7: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/7.jpg)
V
Building domain-specific languages in Scala
Key words: domain-specific programming languages, programming language Scala
UDK: 004.434(043.2)
Abstract
Scala is a relatively new, general purpose, statically typed programming language that
smoothly integrates features of object-oriented and functional languages. Especially
features of functional languages are those that might present a strong foundation for
building domain-specific languages. The diploma work is about the suitability of Scala for
building domain-specific programming languages. The domain-specific language we have
implemented is called FDL – Feature Description Language. A comparison of the
implementation approaches of FDL language in Java, Lisa, Haskell, C++, Smacc and C#
has already been done in article A preliminary study on various implementation
approaches of domain-specific language by Tomaž Kosar, Pablo E. Martínez López, Pablo
A. Barrientos, Marjan Mernik. The results of this article were compared against our
implementation of FDL in Scala.
![Page 8: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/8.jpg)
VI
VSEBINA
1 UVOD ........................................................................................................................... 1
2 SPLOŠNO NAMENSKI PROGRAMSKI JEZIKI .................................................. 3
3 DOMENSKO SPECIFIČNI PROGRAMSKI JEZIKI ........................................... 7
3.1 RABA...................................................................................................................... 8
3.2 PREDNOSTI IN SLABOSTI ......................................................................................... 9
3.3 NAČINI IMPLEMENTACIJE ..................................................................................... 11
3.4 UPORABLJENA DOMENSKO SPECIFIČNA JEZIKA ..................................................... 14
3.4.1 EBNF ............................................................................................................... 14
3.4.2 FDL ................................................................................................................. 15
4 PROGRAMSKI JEZIK SCALA ............................................................................. 23
4.1 NASTANEK ........................................................................................................... 24
4.2 SPLOŠNO O PROGRAMSKEM JEZIKU SCALA ........................................................... 25
4.3 NAMESTITEV ........................................................................................................ 27
5 IMPLEMENTACIJA DOMENSKO SPECIFIČNEGA JEZIKA FDL V
PROGRAMSKEM JEZIKU SCALA.............................................................................. 29
5.1 SEMANTIČNI MODEL ............................................................................................. 30
5.1.1 Uporabljeni principi ........................................................................................ 30
5.1.2 Implementacija ................................................................................................ 33
5.2 ALGEBRA DIAGRAMOV LASTNOSTI SEMANTIČNEGA MODELA ............................... 36
5.2.1 Uporabljeni principi ........................................................................................ 36
5.2.2 Implemetacija .................................................................................................. 39
5.3 VGRAJENI PRISTOP ............................................................................................... 44
5.3.1 Uporabljeni principi ........................................................................................ 44
5.3.2 Implementacija ................................................................................................ 45
5.4 PRISTOP PREVAJALNIK/TOLMAČ ........................................................................... 48
5.4.1 Uporabljeni principi ........................................................................................ 48
5.4.2 Implementacija ................................................................................................ 51
![Page 9: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/9.jpg)
VII
6 MERITVE IN PRIMERJAVE ................................................................................. 54
6.1 PRIMERJAVA ŠTEVILA VRSTIC IMPLEMENTACIJ ..................................................... 54
6.2 PRIMERJAVA ČASOVNE ZMOGLJIVOSTI ................................................................. 58
7 ZAKLJUČEK ............................................................................................................ 60
![Page 10: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/10.jpg)
VIII
KAZALO SLIK
Slika 2-1: Klasifikacija programskih jezikov ........................................................................ 4
Slika 3-1: Primerjava stroškov ............................................................................................ 10
Slika 3-2: Graf značilnosti................................................................................................... 16
Slika 5-1: Uporaba semantičnega modela ........................................................................... 30
Slika 5-2: Semantični model namenjen opisu lastnosti ....................................................... 33
Slika 5-3: Semantični model namenjen opisu omejitev ...................................................... 34
KAZALO GRAFOV
Graf 6-1: eLOC iz primerjanega članka .............................................................................. 56
Graf 6-2: Število vrstic implementacije – vgrajeni pristop ................................................. 57
Graf 6-3: Število vrstic implementacije – pristop prevajalnik/tolmač ................................ 57
KAZALO TABEL
Tabela 1: Implementacijski pristopi .................................................................................... 13
Tabela 2: Nabor spremenljivk ............................................................................................. 20
Tabela 3: Spremenljivke okolja ........................................................................................... 27
Tabela 4: Notacija PEG operatorjev programskega jezika Scala ........................................ 50
Tabela 5: Rezultati časovnih zmogljivosti iz primerjalnega članka .................................... 58
Tabela 6: Časovna zmogljivost implementacij ................................................................... 59
![Page 11: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/11.jpg)
IX
UPORABLJENE KRATICE
BNF – Backus–Naur Form
COTS – Commercial Off-The-Shelf
DSL – Domain-Specific Language
DSVL – Domain-Specific Visual Language
EBNF – Extended Backus–Naur Form
ELOC – Effective Lines of Code
FDL – Feature Description Language
GPL – General Purpose Language
JVM – Java Virtual Machine
LOC – Lines of Code
PEG – Parsing Expression Grammars
SQL – Structured Query Language
XML – Extensible Markup Language
![Page 12: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/12.jpg)
![Page 13: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/13.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 1
1 UVOD
Scala je razmeroma nov, vendar hitro se razvijajoč programski jezik. Zanimiv je predvsem
zaradi objektno in hkrati funkcijsko usmerjenega pristopa ter interoperabilnosti s
programskim jezikom Java. Scala je sicer v začetkih svojega obstoja pridobila titulo
akademskega programskega jezika, ki naj ne bi bil primeren za industrijo oziroma
reševanje realnih problemov, kar po našem prepričanju nikakor ni bila korektna oznaka.
Zagotovo je, da omenjena oznaka z današnjim dnem ne drži več, saj je Scala med drugim
že bila uporabljena v podjetjih kot so LinkedIn, Twitter, FourSquare in podobnih. Scala
namreč z naprednimi principi, velikim naborom knjižnic ter že vgrajenimi domensko
specifičnimi jeziki olajša prenekatero nalogo. Prav funkcijski pristopi ter že vgrajeni
domensko specifični jeziki pa namigujejo na to, da bi programski jezik Scala lahko
predstavljal dobro alternativo za gradnjo le teh. Slednjo predpostavko smo v sklopu tega
diplomskega dela skušali potrditi.
V programskem jeziku Scala smo implementirali domensko specifični programski jezik
FDL. Gre za domensko specifičen jezik, ki je bil v sklopu članka A preliminary study on
various implementation approaches of domain-specific language, avtorjev Tomaž Kosar,
Pablo E. Martínez López, Pablo A. Barrientos, Marjan Mernik, v nekaterih programskih
jezikih že implementiran. V omenjenem članku so prav tako bile opravljene meritve v
smislu števila potrebnih vrstic za implementacijo, hitrost izvajanja algoritma in podobno.
Omenjena raziskava je predstavljala odlično osnovo za primerjavo naše implementacije v
programskem jeziku Scala.
V sklopu diplomskega dela najprej opišemo splošne in domensko specifične programske
jezike. Slednje predstavimo podrobneje, saj njihov predstavnik, programski jezik za opis
diagramov lastnosti FDL, ki ga prav tako podrobneje opišemo, predstavlja jedro našega
diplomskega dela.
V naslednjem poglavju predstavimo sam programski jezik Scala. Dotaknemo se nastanka
omenjenega programskega jezika, prav tako pa opišemo splošne značilnosti ter
![Page 14: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/14.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 2
najočitnejše razlike v primerjavi z veliko bolj razširjenim ter poznanim programskim
jezikom Java, s katerim je programski jezik Scala interoperabilen. V podrobnejše principe
ter samo razlago programskega jezika Scala se ne spuščamo, saj bi s tem zagotovo presegli
število strani, ki je za diplomsko delo še sprejemljivo, zagotovo pa predstavimo vse
principe, ki so bili uporabljeni pri praktičnem delu in sicer sami implementaciji domensko
specifičnega jezika.
Opis implementacije je ločen na štiri podpoglavja. V prvih dveh podpoglavjih opišemo
model, ter algebro diagramov lastnosti. Opisani dve rešitvi sta skupni dvema različnima
pristopoma implementacije domensko specifičnih jezikov, in sicer vgrajenemu pristopu ter
pristopu prevajalnik/tolmač, ki ju opišemo v tretjem ter četrtem podpoglavju. Pred vsakim
opisom implementacije za lažje razumevanje predstavimo uporabljene principe.
V naslednjem poglavju predstavimo rezultate meritev, prav tako pa po kriterijih, ki so za
nas zanimivi, opravimo primerjavo z rezultati dobljenimi v [10].
![Page 15: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/15.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 3
2 SPLOŠNO NAMENSKI PROGRAMSKI JEZIKI
Začetke programskih jezikov bi lahko označili s predlogom John von Neumann-a, da mora
biti računalnik trajno ožičen sistem z možnostjo hranjenja programov in podatkov v
pomnilniku, podprt z majhnim naborom splošno namenskih operacij, ki v določenem
sosledju rešujejo kompleksnejše naloge [1]. Pred tem je namreč veljalo, da je bilo potrebno
za »vnos« programa fizično preklapljanje stikal, kakor tudi povezovanje komponent
logičnih ter aritmetičnih enot z električnimi prevodniki za vsak program posebej [18].
Takšen »vnos« programa je lahko trajal tudi po več dni.
Kot primer programa v pomnilniku poglejmo strojno kodo arhitekture LC-3, ki je sicer bila
uporabljena le v pedagoške namene [14]:
0010001000000100
0010010000000100
0001011001000010 program
0011011000000011
1111000000100101
0000000000000101
0000000000000110 podatki
0000000000000000
V tem programu vsaka vrstica, zapisana s 16 biti, predstavlja po eno inštrukcijo ali eno
podatkovno vrednost. Prvi štirje biti predstavljajo operacijsko kodo (ang. opeartion code),
ki jo mora prepoznati prevajalnik, da se lahko izvajanje programa prične. Preostalih 12
bitov predstavlja podatke o operandih. Kot primer poglejmo naslednjo inštrukcijo:
0010 001 000000100
Prvi štirje biti 0010 predstavljajo ukaz za prenos vrednosti iz pomnilnika v enega izmed
osmih splošno namenskih registrov. Naslednji trije biti 001 označujejo prvi splošno
namenski register, preostanek bitov pa označuje celoštevilčni zamik od naslova
naslednjega ukaza na podatek, ki ga želimo prenesti.
Pisanje takšnih programov bi bilo prezahtevno, zato je sledil logičen naslednji korak,
uvedba mnemonikov. Mnemonik je lahko zapomnljiva beseda, ki predstavlja inštrukcijo,
![Page 16: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/16.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 4
operand ali pa pomnilniško lokacijo. Tako bi lahko zgoraj napisan binarni ukaz s pomočjo
mnemonikov zapisali takole:
LD R1, FOURTH
Takšen zapis programa imenujemo zbirna koda. Naloga zbirnika je, da mnemonike
enolično pretvori v strojno kodo. Omenjena zbirna koda je v času nastanka zelo
poenostavila pisanje programov v primerjavi s strojno kodo, vendar pa se tukaj razvoj
splošno namenskih programskih jezikov šele začne.
Strojna in zbirna koda spadata med nižje programske jezike in ju zaradi pomanjkanja
abstrakcije nima smisla klasificirati med splošno namenske ali domensko specifične jezike.
Zato pa je omenjeno klasifikacijo smiselno opraviti nad višjimi programskimi jeziki.
Definirajmo višji programski jezik kot jezik, ki ustreza kriterijem uporabljenim v [16] z
manjšimi spremembami:
Poznavanje strojne kode ni pogoj za uporabo.
Zagotovljena je prenosljivost programa.
En stavek oziroma inštrukcija se prevede v več operacij na nivoju strojne kode.
Obstaja notacija bližja problemu, ki ga rešujemo, kot sam zbirni jezik.
Manjšo množico programskih jezikov bi po zgornji definiciji lahko klasificirali kot je to
prikazano na sliki 2-1.
Slika 2-1: Klasifikacija programskih jezikov
Iz slike 2-1 je razvidno, da je nekatere jezike težko opredeliti kot samo specifične ali
splošne. Razlog tiči v tem, da nekateri jeziki ustrezajo tako splošno namenski, kakor tudi
domensko specifični definiciji jezikov, čeprav nobene izmed omenjenih v celoti ne
izpolnjujejo. Takšen jezik je npr. COBOL, ki ga nekateri uvrščajo med splošno namenske
![Page 17: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/17.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 5
programske jezike, spet drugi med domensko specifične jezike [15]. Naslednje poglavje je
namenjeno podrobnejšemu opisu domensko specifičnih jezikov, kar nekoliko olajša
razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske
jezike vseh programskih jezikov v splošnem ni možna, še prej pa si poglejmo zakaj
programski jezik Scala lahko zagotovo prištevamo med splošno namenske programske
jezike.
Programski jezik Scala je popolnoma interoperabilen s programskim jezikom Java, ki je
eden izmed bolj znanih predstavnikov splošno namenskih jezikov. Za razliko od
programskega jezika Java omogoča napredne principe objektnega ter funkcijskega
programiranja, prav tako pa ustreza vsem lastnostim splošno namenskih jezikov:
Poznavanje strojne kode ni potrebno.
Prenosljivost programa je zagotovljena, saj teče v okolju Javinega virtualnega
stroja (ang. java virtual machine).
En stavek oziroma inštrukcija se prevede v več operacij na nivoju strojne kode.
Notacija za reševanje problemov je zaradi izrazne moči programskega jezika Scala
zelo približana reševanju problema.
Programski jezik Scala lahko uporabljamo za manjše skripte, kakor tudi za večje sisteme,
kar klasifikacijo še olajša. Kot primer si lahko ogledamo primer izvedljive skripte napisane
v programskem jeziku Scala za štetje vrstic datoteke, ki jo podamo preko argumenta:
::#!
@echo off
call scala %0.bat %*
goto :eof
::!#
val numOfLines = io.Source.fromFile(args(0)).getLines.size
println("Stevilo vrstic (" + args(0) + "): " + numOfLines)
Takšno skripto bi iz ukazne vrstice v okolju Windows zagnali tako:
C:\>CLines FDL.lang
Stevilo vrstic (FDL.lang): 48
![Page 18: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/18.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 6
Kot primere uporabe programskega jezika Scala za večje sisteme bi med drugim lahko
izpostavili naslednja podjetja [21]:
LinkedIn – Scala uporabljena za asinhroni sporočilni sistem med sistemi v oblaku.
Twitter – Scala uporabljena za sporočilno vrsto velikega števila sporočil.
Novell – Uporaba programskega jezika Scala za spletne servise.
The Guardian – Javni spletni servis v celoti razvit s pomočjo programskega jezika
Scala.
Med drugim Scalo uporabljajo tudi Xerox, FourSquare, Sony, Siemens in še bi lahko
naštevali.
Samemu programskemu jeziku Scala bomo v nadaljevanju namenili samostojno poglavje.
Prav tako pa bomo tekom opisa v praktičnem delu predstavili uporabljene principe.
![Page 19: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/19.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 7
3 DOMENSKO SPECIFIČNI PROGRAMSKI JEZIKI
Domensko specifičen jezik lahko definiramo kot jezik, po navadi manjšega obsega, ki s
primerno notacijo ter abstrakcijo nudi izrazno moč usmerjeno predvsem na določeno
domeno [2]. Omenjena definicija je spet odvisna od načina, kako interpretiramo domeno.
Za lažjo predstavo si predstavljajmo delitev programskih jezikov na splošne in specifične
kot postopno lestvico z zelo domensko specifičnimi jeziki kot na primer BNF na levi in
splošno namenskimi jeziki kot npr. C++ na desni [15]. Nekateri izmed bolj poznanih
predstavnikov domensko specifičnih jezikov so kot že rečeno: BNF, HTML, JavaScript,
formule razpredelnic, SQL in podobni. Omenjeni jeziki so predstavniki zunanjih domensko
specifičnih jezikov, ki uporabljajo svojo sintakso in kot takšni potrebujejo namenskega
tolmača (ang. interpreter) oziroma prevajalnik (ang. compiler). Druga skupina so vgrajeni
domensko specifični jeziki. Takšni jeziki uporabljajo konstrukte gostujočega jezika in so s
pomočjo abstrakcije videti kot jeziki namenjeni le določeni domeni. Takšni vgrajeni jeziki
nimajo posebnega imena [5]. Kot primer v programskem jeziku Scala lahko omenjamo
knjižnico Scala Actors (ang Scala Actors library), ki je del Scala distribucije. Gre za
paradigmo sočasnega programiranja, pri čemer je akter (ang. Actor) proces, ki si z ostalimi
procesi/akterji, ki tečejo sočasno, deli sporočila. Gre za asinhrono komunikacijo; sporočila
se namreč hranijo v nabiralniku akterja, ki sporočilo prejme. Akter kot odgovor na
asinhrono sporočilo lahko ustvari novega akterja, pošlje sporočilo ostalim vidnim akterjem
(vključno s samim seboj) ali pa spremeni interno logiko oziroma način, kako se bo le ta
odzval na naslednje sprejeto sporočilo [11]. Izsek kode omenjenega domensko
specifičnega jezika izgleda takole:
actor {
loop {
receive{
case "Dober dan" => sender ! "Lepo pozdravljeni tudi vi"
case "Lahko noč" => sender ! "Lahko noč tudi vam"
}
}
}
![Page 20: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/20.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 8
Drug takšen predstavnik, ki je del distribucije programskega jezika Scala, pa je princip
razpoznavalnik-kombinator (ang. parser-combinator), ki je prav tako implementiran kakor
vgrajen domensko specifičen jezik in ga bomo podrobneje predstavili v nadaljevanju, saj je
uporabljen v praktičnem delu diplomskega dela.
Skupino domensko specifičnih jezikov, ki je ne bomo posebej obravnavali, sestavljajo tako
imenovani vizualni domensko specifični jeziki ali DSVL (ang. domain-specific visual
languages). Gradniki takšnih jezikov so vizualne predstavitve oziroma komponete, s
katerimi dosežemo višji nivo abstrakcije, ki v večini primerov omogoča uporabo takšnih
jezikov že z osnovnim znanjem rokovanja z osebnim računalnikom [17].
3.1 Raba
Razloge za uporabo domensko specifičnih jezikov lahko v splošnem razdelimo na štiri dele
[5]:
1. Izboljšanje produktivnosti: Domensko specifični jezik teži k temu, da je sintaksa
tega čim bolj podobna domeni problema, ki ga rešuje, prav tako pa s pomočjo
abstrakcije omogoča, da se osredotočimo na reševanje problema in ne na način
kako ga rešiti. Posredno takšen jezik vpliva na lažjo berljivost programske kode.
Posledica je lažje odkrivanje ter manj napak v fazi razvoja ̧ prav tako pa je
olajšano spreminjanje že obstoječe programske kode. Kakor je splošna praksa, da
se poslužujemo smiselnega poimenovanja spremenljivk, pisanja komentarjev,
dokumentacije, tako je dobra praksa tudi uporaba domensko specifičnega jezika,
kjer je to smiselno.
2. Komunikacija s strokovnjaki domensko specifičnega področja: Pogost vzrok
takšnih in drugačnih napak povzroča izguba oziroma deformacija informacij pri
komunikaciji s strokovnjaki določenega domensko specifičnega področja, ki na tak
ali drugačen način podajajo specifikacije. V takšnem primeru lahko domensko
specifični jezik služi kot komunikacijski kanal. Obseg problema, ki ga domensko
specifični jezik rešuje, pogosto odloča tudi o tem ali ga bodo strokovnjaki z
domensko specifičnega področja privzeli kot jezik za zapis dotične domene. Torej,
![Page 21: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/21.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 9
ožji je spekter, ki ga pokriva domensko specifični jezik, večja je verjetnost, da
bodo jezik uporabljali strokovnjaki z določenega domensko specifičnega področja.
3. Sprememba konteksta izvajanja: Velikokrat potreba konfiguracije sistema v
času prevajanja (ang. compile time) ne zadostuje. V takšnem primeru je nekako
potrebno zagotoviti, da lahko konfiguracijo sistema spreminjamo v času izvajanja
(ang. run time). Takšno funkcionalnost bi lahko dosegli z uporabniškim
vmesnikom, ki pa žal za primere, ki vsebujejo zapleteno logiko, ni najboljša izbira,
saj se za te bolje obnese domensko specifičen jezik. Eden izmed predstavnikov
domensko specifičnih jezikov, ki je pogosto uporabljen v te namene, je XML.
4. Alternativni model: Večina trenutno najpogosteje uporabljenih programskih
jezikov povzemajo imperativni način programiranja. Imperativni pristop pomeni,
da za rešitve določenega problema podamo natančen postopek, kako določen
problem rešiti. Z deklarativnim načinom pa, v nasprotju z imperativnim načinom,
podamo samo obnašanje sistema. Alternativni model bi torej lahko namesto
imperativnega omogočal deklarativni način opisa določenega modela. Gre torej za
alternativno obliko polnjenja semantičnega modela. Primer alternativnega modela
je viden v nadaljevanju na praktičnem primeru, ki je del diplomskega dela, kjer se
lahko semantični model vnese preko metod ali pa alternativno s pomočjo ene
izmed implementacij domensko specifičnega jezika.
3.2 Prednosti in slabosti
Raba domensko specifičnih jezikov ima določene prednosti, kakor tudi slabosti. Dobro
zasnovan domensko specifičen jezik in premišljena uporaba poizkušata najti razmerje v
korist prednostim. Za lažjo predstavo še enkrat na kratko opišimo prednosti domensko
specifičnih jezikov [2]:
Omogočajo opis problema na višjem abstraktnem nivoju, ki sovpada z domeno
problema. Posledično lahko strokovnjaki na določenem področju opis takšne
rešitve lažje razumejo, ovrednotijo, urejajo ali pa so celo soudeleženi pri nastajanju
le-te.
So jedrnati, do neke mere samo opisni, v smislu, da dokumentacija zaradi sintakse
ter semantične razdalje, ki je zelo približana domeni problema, ni potrebna.
![Page 22: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/22.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 10
Izboljšajo produktivnost, zanesljivost, olajšajo vzdrževanje in na nek način
pripomorejo k lažji prenosljivosti.
Ker je tak jezik močno povezan z domeno, ki jo rešuje, je torej za uporabo
takšnega jezika potrebno dobro poznavanje področja, ki ga rešuje, kar olajša
komunikacijo med vsemi akterji pri nastajanju določenega programskega
produkta.
Omogočajo ovrednotenje in optimizacijo na domenskem nivoju.
Izboljšajo način testiranja, saj teste lahko pišejo ljudje, ki so v določeni domeni
podkovani.
Druga, sicer posredna prednost so pa zagotovo, dolgoročno gledano, nižji stroški. Kot je
razvidno iz slike 3-1 so začetni stroški sicer višji, vendar se ta vložek po daljšem obdobju
zaradi zgoraj naštetih prednosti obrestuje [12].
Slika 3-1: Primerjava stroškov [12]
Kot slabosti domensko specifičnih jezikov bi lahko izpostavili [12]:
Predvsem začetni stroški razvoja domensko specifičnega jezika.
Stroški izobraževanja.
Majhno število že implementiranih domensko specifičnih jezikov.
Težavna določitev obsega domene, ki jo naj pokriva domensko specifičen jezik.
Določitev razmerja med konstrukti domensko specifičnega in splošno namenskega
jezika (predvsem pri vgrajenem pristopu).
Odpor razvijalcev do učenja novih jezikov.
![Page 23: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/23.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 11
Možna manjša zmogljivost programa napisanega z domensko specifičnim jezikom
v primerjavi s splošno namenskim.
3.3 Načini implementacije
V fazi analize je potrebno zbrati vse potrebne podatke za izdelavo domensko specifičnega
jezika [15]. Jasno moramo definirati problem domene, ki ga rešujemo. Potrebno je
pridobiti znanje o problemu domene. V pomoč so lahko strokovnjaki, ki so v določeni
domeni podkovani, tehnični dokumenti, članki ter obstoječe že napisane rešitve v splošno
namenskih programskih jezikih. Rezultat takšne analize je v splošnem težko določiti.
Velikokrat pa končni izdelek analize zajema potrebno terminologijo ter semantiko za
reševanje problema v bolj ali manj abstraktni obliki.
Velikokrat je dobro preveriti, ali morda domensko specifičen jezik za naš problem že
obstaja, kar se lahko zgodi predvsem pri domensko specifičnih problemih, ki so kljub
specifičnosti dokaj splošni v smislu problema, ki ga rešujejo. Kot primer lahko vzamemo
domensko specifičen jezik za izračun matematičnih izrazov, pri čemer izraz v obliki niza
znakov poda uporabnik. Problem, ki ga v tem primeru rešujemo, je dokaj splošen. Sintaksa
je poznana vsem. Rešitev pa glede na to, da moramo upoštevati prioriteto operatorjev, ni
ravno trivialna. Ena izmed možnih že obstoječih rešitev v programskem jeziku Java, od
verzije 6 naprej, bi bila uporaba paketa javax.script s pripadajočimi knjižnicami, ki
omogoča ovrednotenje skriptnih jezikov, na primer skriptnega programskega jezika
JavaScript. Poglejmo primer:
ScriptEngine engine =
new ScriptEngineManager().getEngineByName("javascript");
// Uporabniški vnos,
// ki ga lahko preberemo iz datoteke, uporabniškega vmesnika…
String expression="sqrt((3+4)*2 + 2)";
// Olajšamo vnos izrazov
// Uporabniku npr. ni potrebno pisati Math.sqrt
expression="with(Math) " + expression;
// Rezultat na standardnem izhodu: 4
System.out.println(engine.eval(expression));
![Page 24: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/24.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 12
Rešitev bi lahko izboljšali, če seveda obstaja potreba po tem, na primer z
razpoznavalnikom, ki dopušča vnos le dovoljenih oziroma želenih matematičnih
konstruktov. S tem dosežemo boljšo kontrolo nad uporabniškim vnosom ter sporočanjem
sintaktičnih napak, še vedno pa bi velik del (ovrednotenje izrazov z velikim naborom
operatorjev, gradnja semantičnega modela) prepustili že obstoječemu domensko
specifičnemu jeziku.
Ko opravimo analizo domensko specifičnega jezika in nobena od že obstoječih rešitev ni
ustrezna, je potrebno določiti način implementacije. V praksi se uporablja več pristopov
(Tabela 1).
![Page 25: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/25.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 13
Tabela 1: Implementacijski pristopi [10]
Pristop Opis
Predprocesiranje
(ang. preprocessing)
Konstrukti domensko specifičnega jezika so preslikani v izbran jezik s pomočjo
predprocesiranja.
Vgrajeni pristop1
(ang. embedded
approach)
Domensko specifičen jezik je vgrajen v izbran gostujoč jezik. Vgrajen jezik
uporablja konstrukte gostujočega jezika, s katerimi se tvori abstraktni nivo, ki se
skuša čim bolj približati domeni. Prednost takšnega pristopa je uporaba že
obstoječega prevajalnika/tolmača gostujočega jezika ter sorazmerno lahka
realizacija. Pomanjkljivost takšnega pristopa je, da se sintaksi domensko
specifičnega jezika po navadi lahko le približamo.
Prevajalnik/tolmač1
(ang.
compiler/interpreter)
V primeru tolmača, so konstrukti domensko specifičnega jezika ovrednoteni s
standardnim ciklom pridobi-dekodiraj-izvedi (ang. fetch-decode-execute cycle).
Tak pristop je primeren za jezik, kjer hitrost izvajanja ni pomembna.
V primeru prevajalnika so konstrukti domensko specifičnega jezika preslikani v
konstrukte gostujočega jezika.
Prednost omenjenih pristopov je ta, da se lahko zelo približamo želeni notaciji,
slabost pa zahtevna implementacija.
Generator prevajalnika
(ang. compiler
generator)
Ta pristop je podoben prejšnjemu, s to razliko, da za gradnjo prevajalnika
uporabimo za to namenjeno orodje. S tem je implementacija nekoliko olajšana.
Razširjeni
prevajalnik/tolmač
(ang. extensible
compiler/interpreter)
Prevajalnik/tolmač gostujočega splošno namenskega jezika se razširi s pravili
domensko specifičnega jezika. Omenjen način je precej zahteven, saj je za
pravilno delovanje potrebno dobro poznavanje prevajalnika splošno namenskega
jezika.
Komercialni produkti -
COTS
(ang. commercial off-
the-shelf)
Domensko specifičen jezik je zgrajen z naborom že obstoječih orodij oziroma
notacij. Tak primer bi lahko bil XML jezik, čeprav je le ta za končnega uporabnika
nepraktičen.
1 Omenjen pristop podrobneje predstavimo v praktičnem delu.
![Page 26: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/26.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 14
Zgoraj omenjeni pristopi so bili v sklopu članka [10] implementirani v naslednjih
programskih jezikih:
Java (predprocesiranje, prevajalnik/tolmač)
Lisa (predprocesiranje, generator prevajalnika)
Haskell (predprocesiranje, vgrajen pristop)
C++ (predprocesiranje)
Smacc (generator prevajalnika)
C# (razširjen prevajalnik)
Domensko specifični jezik, ki je bil implementiran v naštetih pristopih, se imenuje FDL.
Omenjen domensko specifičen jezik implementiramo v okviru diplomske naloge še v
programskem jeziku Scala (vgrajeni pristop ter pristop prevajalnik/tolmač) in ga
primerjamo z zgoraj omenjenimi implementacijami.
Preden nadaljujemo, si oglejmo še jezika, ki sta tesno povezana z implementacijo
končnega izdelka diplomske naloge, in sicer omenjeni jezik FDL, ter jezik EBNF, ki je
povezan z implementacijo pristopa prevajalnik/tolmač v programskem jeziku Scala.
3.4 Uporabljena domensko specifična jezika
3.4.1 EBNF
Bakcus-Naurova oblika ali BNF je metoda, ki na formalni način omogoča definicijo
splošnejših in bolj zapletenih konstruktov jezika. Gre za standardno obliko zapisovanja
sintakse ali gramatike programskih jezikov. Medtem ko z regularnimi izrazi določamo
besede jezika, z BNF povezujemo besede v stavke.
BNF določa sintakso z množico pravil oziroma produkcij, s katerimi definiramo stavke
jezika. Sestavni deli, s katerimi zapisujemo produkcije so terminali, neterminali in prazni
simboli. Terminali ali končni simbol so besede jezika, neterminali pa so vmesni simboli, ki
označujejo posamezne dele stavka.
Vsaka produkcija je sestavljena iz dveh delov, ki sta medsebojno ločena z metasimbolom
::=, ki ga beremo »definirano je«. Na levi strani je samo en terminal, na desni pa je
![Page 27: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/27.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 15
zaporedje terminalov in neterminalov. Na splošno ima produkcija obliko A ::= B, kar
pomeni, da je neterminal A definiran z desno stranjo B.
Več produkcij za isti neterminal oziroma terminal, lahko zapišemo z eno produkcijo in s
pomočjo metasimbola |. Metasimbol | pomeni, da imamo na desni strani produkcije
alternativo, ki jo beremo »ali«. Iz tega sledi, da je
A ::= B
A ::= C
enako kot
A ::= B | C
Prevajalnik razpoznava stavke tako, da iz BNF ugotovi, ali je stavek sestavljen pravilno ali
ne.
Poznamo pa tudi tako imenovano razširjeno Bakcus-Naurovo obliko, ki zraven
omenjenega operatorja vpeljuje še dva metasimbola tako, da desno stran produkcije
zapišemo v zavite ali oglate oklepaje. Z oglatimi oklepaji določimo opcijo, z zavitimi pa
iteracijo [19].
Prav tako je potrebno naznaniti, da je vsak izraz zapisan v EBNF možno enakovredno
zapisati v BNF obliki, kar pomeni, da je EBNF le prikladnejši zapis.
3.4.2 FDL
Kot smo že omenjali je domensko specifičen programski jezik, s pomočjo ustrezne
sintakse ter abstrakcije, vezan na domeno oziroma določen problem. Za nadaljnjo razlago
jezika FDL je potrebno odgovoriti ter hkrati izpostaviti, kaj je produkt oziroma rezultat
domensko specifičnega jezika. Rezultat je torej lahko program, ki rešuje problem določene
domene. Tako domensko specifičen jezik FDL služi kot orodje za definicijo variabilnih ter
skupnih lastnosti družine programskih produktov ali družine domensko specifičnih jezikov.
Preden se lotimo nadaljnje razlage, najprej razjasnimo kaj je domenska analiza (ang.
domain analysis). Domensko analizo bi lahko primerjali s sistemsko analizo (ang. system
analysis), pri čemer je razlika ta, da je sistemska analiza namenjena zajemanju zahtev
enega sistema, dočim je domenska analiza namenjena zajemanju zahtev ter potreb družine
programskih produktov oziroma domensko specifičnih jezikov.
![Page 28: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/28.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 16
Najpomembnejši rezultat domenske analize je tako imenovani model lastnosti (ang.
feature model). Omenjen model opisuje skupne ter variabilne značilnosti članov družine
programskih produktov, prav tako pa opredeljuje relacije med variabilnimi značilnostmi.
Sestavni del modela lastnosti je diagram lastnosti (ang. feature diagram). Gre za grafično
predstavitev odvisnosti variabilnih značilnosti sistemov. Takšen graf torej opiše vse možne
konfiguracije končnih programskih produktov. Prav ti diagrami pa predstavljajo domeno,
ki se je loteva jezik FDL. Omenjen graf si lahko za lažjo predstavo ogledamo na sliki 3-2.
Slika 3-2: Graf značilnosti
Diagram prikazuje naslednje lastnosti avtomobila: šasija, Menjalnik, Motor, Moč ter
priklop. Naštete lastnosti, razen lastnosti priklop, so obvezne, kar nakazuje obarvan
krožec v grafu nad vsako izmed lastnosti. Prazen krožec označuje opcijsko lastnost. Iz
grafa je razvidno, da so določene lastnosti zapisane z veliko začetnico. Gre za tako
imenovane sestavljene lastnosti, kar pomeni, da lastnost napisano z veliko začetnico
sestavlja več lastnosti, ki so lahko spet sestavljene ali pa atomarne – lastnosti zapisane z
malo začetnico. Notacija, ki je še nismo opisali, so prazni in obarvani trikotniki pri
sestavljenih lastnostih. Prazen trikotnik pomeni ekskluzivni ali. Menjalnik je tako lahko
avtomatski ali ročni, nikakor pa ne oboje. Obarvan trikotnik ponazarja možnost izbire
ene ali več lastnosti. Tako je npr. Motor lahko električni, bencinski ali oboje hkrati.
Instanco diagrama lastnosti določa dejanski izbor atomarnih lastnosti, ki ustrezajo
zahtevam diagrama. Takšna instanca definira enega izmed družine produktov. Če
pogledamo diagram na sliki 3-2 še enkrat, opazimo, da je možno število različnih instanc
avtomobilov, ki jih lahko tvorimo, enako 36:
![Page 29: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/29.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 17
1 (šasija) × 2 (Menjalnik) × 3 (Motor) × 3 (Moč) × 2 (priklop) = 36
Grafična notacija v obliki diagrama sicer lepo opisuje lastnosti sistema, vendar je
neučinkovita za komunikacijo. Za spreminjanje diagrama je namreč potrebno grafično
orodje, prav tako pa takšna oblika ni primerna za nadaljnje procesiranje. V izogib
omenjenemu se je pojavila potreba po tekstovnem zapisu. Prav ta problem pa rešuje jezik
FDL, ki ga lahko opišemo takole:
FDL definicija sestoji iz več definicij lastnosti, ki jih sestavljajo imena lastnosti, katerim
sledi dvopičje ter izraz za opis lastnosti. Izraz za opis lastnosti lahko sestoji iz:
Atomarne lastnosti
Sestavljene lastnosti: lastnost, katere definicija je podana nekje drugje
Opcijske lastnosti: lastnost znotraj opt( )
Obveznih lastnosti: seznam izrazov za opis lastnosti znotraj all( )
Eksluzivnih lastnosti: seznam izrazov za opis lastnosti znotraj one-of( )
Ne-ekskluzivnih lastnosti: seznam izrazov za opis lastnosti znotraj more-of()
Privzete lastnosti1: default =, čemur sledi atomarna lastnost
Nedefiniranih lastnosti1: …
Prav tako pa definicijo FDL sestavljajo tudi omejitve, ki jih delimo na uporabniške
omejitve ter omejitve diagrama. Zapišemo jih takole:
A1 requires A2: če je prisotna A1, potem mora biti prisotna tudi lastnost A2
A1 excludes A2: če je prisotna A1, potem lastnost A2 ne sme biti prisotna
include A: lastnost A mora biti prisotna
exclude A: lastnost A ne sme biti prisotna
Pri čemer sta prvi dve omejitvi diagrama in drugi dve uporabniški omejitvi.
1 Omenjen izraz ni vsebovan v implementaciji članka [10], zato ga tudi pri praktičnem delu ne upoštevamo.
![Page 30: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/30.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 18
Zapišimo torej diagram lastnosti na sliki 3-2 v tekstovni obliki, ki ustreza zgoraj napisanim
pravilom:
Avto: all(šasija, Menjalnik, Motor, Moč, priklop)
Menjalnik: one-of(avtomatski, ročni)
Motor: more-of(električni, bencinski)
Moč: one-of(nizkaMoč, srednjaMoč, velikaMoč)
Za lažjo predstavo poglejmo še formalno definicijo zapisano s pomočjo malce prirejene
EBNF notacije. Za opcijo in iteracijo namreč ne uporabljamo zavitih ter oglatih oklepajev,
ampak naslednje tri metasimbole:
(…)? – ponazarja, da je simbol oziroma množica simbolov znotraj oklepaja
opcijska
(…)* – ponazarja nič ali več ponovitev
(…)+ – ponazarja eno ali več ponovitev.
Z uporabo zgoraj omenjenih metasimbolov lahko sintakso jezika FDL zapišemo kot:
featureDiagram : (featureDefinition)+ (constraint)*;
featureDefinition : FeatureName ':' featureExpression;
featureExpression :
all
| oneof
| moreof
| defaultAtomicFeature
| feature
| optFeatureExpression;
defaultAtomicFeature : 'default' '=' AtomicFeature;
optFeatureExpression : 'opt''('featureExpression')';
all : 'all''(' featureExpressionList ')';
oneof : 'one-of''(' featureExpressionList ')';
moreof : 'more-of''(' featureExpressionList ')';
featureExpressionList : featureExpression (',' featureExpression )*;
feature : FeatureName | AtomicFeature;
constraint : userConstraint | diagramConstraint;
diagramConstraint
: (AtomicFeature 'requires' AtomicFeature)
| (AtomicFeature 'excludes' AtomicFeature);
![Page 31: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/31.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 19
userConstraint
: 'include' AtomicFeature
| 'execlude' AtomicFeature;
Opt : '?';
FeatureName : ('A'..'Z') ('a'..'z' | 'A'..'Z' | '1'..'9')*;
AtomicFeature: ('a'..'z') ('a'..'z' | 'A'..'Z' | '1'..'9')*;
Ws : (' ' | '\t' | '\n' | '\r' | '\f')+;
Zgoraj zapisana formalna definicija je osnova implementacije pristopa prevajalnik/tolmač,
saj jo z le majhnimi spremembami pretvorimo v domensko specifičen jezik namenjen
razpoznavanju, ki je del programskega jezika Scala. V nadaljevanju prikažemo, kako malo
je potrebno, da se omenjena notacija pretvori v notacijo omenjenega domensko
specifičnega jezika. Operacije, oziroma tako imenovano algebro diagramov lastnosti (ang.
feature diagram algebra), ki se izvede nad domensko specifičnim jezikom FDL, oziroma
tekstualno predstavitvijo, sestavljajo:
Pravila normalizacije – namen je poenostavitev izrazov za opis lastnosti.
Pravila razširitve – namen je razširitev normalizirane oblike izrazov lastnosti v
disjunktivno normalno obliko.
Pravila ustreznosti – namen je iz disjunktivno normalne oblike na podlagi
omejitev diagrama in uporabniških omejitev izločiti neustrezne izraze. Pravila
ustreznosti torej omogočajo poizvedovanje po instancah, ki ustrezajo zahtevam
sistema, kakor tudi uporabniškim zahtevam.
Pravila variabilnosti – namen je ugotavljanje števila izhodnih možnosti diagrama
lastnosti [3].
![Page 32: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/32.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 20
Pravila normalizacije
Najprej si oglejmo pravila, ob upoštevanju katerih pridemo do normaliziranega izraza. Za
lažjo razlago definirajmo nabor spremenljivk prikazanih v tabeli 2, ki nam je v pomoč pri
formalnih definicijah algebre diagramov lastnosti:
Tabela 2: Nabor spremenljivk
SPREMENLJIVKA TIP
F FeatureExpression
Fs { FeatureExpression "," }*
Ft { FeatureExpression "," }+
A AtomicFeature
Z definiranim naborom spremenljivk, lahko pravila za normalizacijo (N1 – N12)
predstavimo na naslednji način:
[N1] Fs, F, Fs', F?, Fs'' = Fs, F, Fs', Fs''
[N2] Fs, F, Fs', F, Fs'' = Fs, F, Fs', Fs''
[N3] F?? = F?
[N4] all(F) = F
[N5] all(Fs, all(Ft), Fs') = all(Fs, Ft, Fs')
[N6] one-of(F) = F
[N7] one-of(Fs, one-of(Ft), Fs') = one-of(Fs, Ft, Fs')
[N8] one-of(Fs, F?, Fs') = one-of(Fs, F, Fs')
[N9] more-of(F) = F
[N10] more-of(Fs, more-of(Ft), Fs') = more-of(Fs, Ft, Fs')
[N11] more-of(Fs, F?, Fs') = more-of(Fs, F, Fs')
[N12] default = A = A
Zgoraj našteta pravila torej služijo normalizaciji izrazov. Kot primer poglejmo prvo pravilo
normalizacije. Spodnji izraz
all(električni, bencinski, opt(električni))
se tako po prvem pravilu normalizacije preoblikuje v spodnjo obliko
all(električni, bencinski)
Pravila normalizacije prikazana zgoraj so precej samo-opisna, zato jih posebej ne
opisujemo.
![Page 33: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/33.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 21
Pravila razširitve
Ko smo celoten FDL izraz normalizirali, je naslednji smiselni korak razširitev izraza v tako
imenovano disjunktivno normalno obliko, ki izgleda takole:
one-of( all(A11,…,A1n1),
…,
all(Am1,…,Amn1))
Takšna oblika je v bistvu prikaz vseh možnih kombinacij, ki jih je možno sestaviti. Pravila,
ki normaliziran izraz preoblikujejo v disjunktivno normalno obliko, opišemo tekom razlage
same implementacije.
Pravila ustreznosti
Zadnji korak je upoštevanje pravil omejitev diagrama in upoštevanje uporabniških
omejitev. Gre za to, da izmed vseh možnih kombinacij lastnosti izločimo tiste, ki ne
ustrezajo vsaj eni izmed podanih omejitev. Prav tako tudi ta pravila opišemo hkrati z
opisom izvorne kode programa.
Pravila variabilnosti
Pravila za določitev variabilnosti:
[V1] var(A) = 1
[V2] var(F?) = var(F) + 1
[V3] var(all(F, Ft)) = var(F) * var(all(Ft))
[V4] var(all(F)) = var(F)
[V5] var(one-of(F, Ft)) = var(F) + var(one-of(Ft))
[V6] var(one-of(F)) = var(F)
[V7] var(more-of(F, Ft)) = var(F) + (var(F) + 1) * var(more-of(Ft))
[V8] var(more-of(F)) = var(F)
Variabilnost določa število možnih izidov, ki jih lahko sestavimo iz FDL izraza. Tako bi
po zgornjih pravilih ugotovili, da lahko za naslednjo normalizirano obliko:
Avto: all( šasija,
one-of( avtomatski, ročni),
more-of( električni, bencinski),
one-of( majhnaMoč, srednjaMoč, velikaMoč),
opt( priklop))
sestavimo 36 različnih kombinacij. Kot vidimo zgornji izraz ne uporablja nobene
sestavljene lastnosti, kar je posledica substitucije le-teh z izrazi, ki vsebujejo le atomarne
lastnosti.
![Page 34: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/34.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 22
Več podrobnosti domensko specifičnega jezika FDL je mogoče najti v članku avtorjev
Arie van Dersena in Paul Klinta v članku Domain-Specific Language Design Requires
Feature Descriptions [3].
![Page 35: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/35.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 23
4 PROGRAMSKI JEZIK SCALA
Scala označuje programski jezik, ki je skalabilen (ang. scalable language). Ime je dobil
zaradi zasnove, ki je primerna za izdelavo manjših skript, kakor tudi za izdelavo večjih
sistemov; v obeh primerih pa omogoča dostop oziroma je interoperabilen z gostujočim
programskim jezikom Java. Tehnično gledano je Scala skupek zasnov funkcijskih ter
objektno usmerjenih pristopov. Podatek, da so programi pisani v programskem jeziku Java
2-3 krat daljši, v nekaterih primerih tudi več, od ekvivalentov pisanih v programskem
jeziku Scala, priča o izrazni moči slednjega. Vzrok je v tem, da Scala daje velik pomen na
čim manjši rabi ponavljajoče oziroma odvečne kode [8]. Za lažjo predstavo si lahko
pogledamo enega izmed primerov napisanega v programskem jeziku Java:
class Oseba {
private String ime;
private String priimek;
private int starost;
public Oseba(String ime, String priimek, int starost) {
this.ime = ime;
this.priimek = priimek;
this.starost = starost;
}
public void setIme(String ime) { this. ime = ime; }
public void String getIme() { return this.ime; }
public void setPriimek(String lastName) { this.priimek = priimek; }
public void String getPriimek() { return this.priimek; }
public void setStarost(int starost) { this.starost = starost; }
public void int getStarost() { return this.starost; }
}
ter v programskem jeziku Scala:
class Oseba(var ime: String, var priimek: String, var starost: Int)
Zgornja primera sta enaka, razredi v programskem jeziku Scala so namreč parametrizirani
z argumenti konstruktorja, iz katerih se samodejno tvorijo metode za nastavljanje ter
dostop do spremenljivk. Omenjen primer je le eden izmed vrste tako imenovanih
sintaktičnih bombončkov (ang. syntactic sugar).
![Page 36: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/36.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 24
4.1 Nastanek
Avtor programskega jezika Scala Martin Odersky in Philip Wadler sta leta 1995 ustvarila
programski jezik imenovan Pizza, ki se je prevedel v binarno predstavitev programskega
jezika Java (eng. bytecode). Šlo je za nekakšen podaljšek programskega jezika Java, ki je
omogočal nekatere prvine funkcijskih jezikov, in sicer:
Generični tipi (ang. generics).
Funkcije višjega reda (ang. higher-order functions).
Ujemanje vzorcev (ang. pattern matching).
Zgoraj omenjen jezik je izšel leto po uradnem izidu prve različice programskega jezika
Java in je bil dober pokazatelj tega, da je na platformi JVM možno implementirati tudi
jezike s funkcijsko usmerjenimi prvinami.
Delo Martina Oderskya je bilo opaženo s strani podjetja Sun. Zanimivi so bili predvsem
generični tipi, kar je bil povod za nastanek novega projekta z imenom Generic Java ali
krajše GJ, katerega rezultat je bil po šestih letih od nastanka vključen v različico
programskega jezika Java od verzije 1.5 naprej. Prav tako pa je podjetje Sun zaradi večje
stabilnosti in lažjega vzdrževanja uporabilo prevajalnik projekta GJ že od verzije
programskega jezika Java 1.3 naprej.
Naslednji jezik, ki je nastal izpod rok Martina Oderskya in njegove ekipe, je bil Funnel.
Gre za mešanico funkcijskih ter objektno usmerjenih prvin, ki pa je bila distancirana od
vseh do tedaj znanih splošno namenskih programskih jezikov in je bila z vidika uporabe
samega jezika precej zahtevna zaradi minimalističnega nabora programskih konstruktov ter
akademske naravnanosti. Hitro je postalo jasno, da programski jezik lahko uspe le z
velikim naborom že obstoječih standardnih knjižnic.
Leta 2002 je sledil razvoj programskega jezika Scala, s pragmatičnim pristopom ter s
ciljem interoperabilnosti s programskim jezikom Java. Leta 2003 je sledil prvi javni izid
programskega jezika Scala, ki se prevaja v binarno predstavitev programskega jezika Java.
Leta 2006 je sledil izid verzije 2.0. Od omenjene verzije naprej popularnost programskega
jezika Scala samo še narašča [20].
![Page 37: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/37.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 25
4.2 Splošno o programskem jeziku Scala
Na krajšem primeru implementiranem v obeh programskih jezikih Java in Scala, si
poglejmo osnovne razlike ter podobnosti med omenjenima jezikoma. Na primeru
programskega jezika Scala je vidna interoperabilnost omenjenih jezikov. Program, ki služi
za primerjavo, izpiše vse argumente, ki jih podamo ob zagonu omenjenega primera.
Implementacija zapisana v programskem jeziku Java:
// Java
class PrintOptions {
public static void main(String[] args) {
System.out.println("Options selected:");
for (int i = 0; i < args.length; i++)
if (args[i].startsWith("-"))
System.out.println(" "+args[i].substring(1));
}
}
ter v programskem jeziku Scala:
// Scala
object PrintOptions {
def main(args: Array[String]): Unit = {
System.out.println("Options selected:")
for (val arg <- args)
if (arg.startsWith("-"))
System.out.println(" "+arg.substring(1))
}
}
Iz zgornjih primerov je razvidno naslednje:
Scala enako kot Java uporablja za definicijo razreda rezervirano besedo class, kar
sicer ni razvidno iz zgornjega primera. Zraven omenjene definicije pa se v
programskem jeziku Scala uporablja tudi rezervirana beseda object, ki definira
objekt razreda z natanko eno instanco (ang. singleton object).
V programskem jeziku Java se za definicijo spremenljivk ter parametrov uporablja
prefiksna notacija tip spremenljivka, medtem ko je v programskem jeziku Scala
vrstni red drugačen in sicer spremenljivka: tip.
![Page 38: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/38.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 26
Vse definicije se v programskem jeziku Scala začnejo z rezervirano besedo def.
Scala ne zahteva podpičij v programskih stavkih, vendar jih kljub temu dovoljuje.
Scala za polja ter dostop do elementov ne uporablja posebne notacije. Polje tipa T
zapišemo kot Array[T], pri čemer je Array klasičen razred, [T] pa parameter.
Polja v programskem jeziku Scala dedujejo od funkcij, zato do elementov
dostopamo s klicem funkcije array(i), namesto array[i], kot smo tega vajeni v
programskem jeziku Java.
Tip, ki ga vrača metoda v primeru programskega jezika Scala, je Unit, za razliko
od programskega jezika Java void. Razlog tiči v tem, da programski jezik Scala ne
razlikuje med izrazom ter ukazom. Vsaka funkcija oziroma metoda vrne vrednost
nazadnje ovrednotenega izraza. Tako je v programskem jeziku Scala tudi if-then-
else kontrolna struktura izraz in kot taka vrača vrednost.
Scala povzema veliko kontrolnih struktur programskega jezika Java. Poudariti pa
je treba razliko v for stavkih, ki so v programskem jeziku Scala veliko manj
restriktivni.
Čeprav je sintaksa programov pisanih v programskem jeziku Java precej različna
od programov pisanih v programskem jeziku Scala, je iz zgornjega primera
razvidno, da je Scala navkljub temu interoperabilna z programskim jezikom Java.
Tako se v zgornjem primeru kličeta metodi startsWith in subString razreda
programskega jezika Java String. Prav tako kličemo metodo println statičnega
polja out razreda System.
Referenca na statično polje out je mogoča kljub temu, da Scala ne podpira
koncepta statičnih elementov razredov. Vsak razred programskega jezika Java je
namreč s strani programskega jezika Scala viden kot skupek dveh entitet: razreda s
pripadajočimi dinamičnimi elementi ter eno instanco objekta, ki vsebuje vse
statične elemente. Tako je torej System.out dosegljiv v programskem jeziku Scala
kot element objekta System.
S primerjavo zgoraj zapisanih in opisanih programov smo na nek način prikazali le
osnovne razlike med programskima jezikoma Scala in Java. Sam programski jezik Scala v
resnici uporablja veliko naprednih objektnih ter funkcijsko usmerjenih principov, ki pa jih
![Page 39: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/39.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 27
ne bomo posebej opisovali, saj smo omejeni s številom strani, ki pa nikakor ne zadostuje
niti za splošen opis vseh funkcionalnosti.
V praktičnem delu kljub temu podrobneje predstavimo uporabljene principe, ki so
pripeljali do rešitve praktičnega dela diplomskega dela.
4.3 Namestitev
Programski jezik Scala lahko uporabljamo na vseh Unix ali Windows sistemih. Pogoj je
prednaložena Java od verzije 1.5 naprej.
Za delo potrebujemo distribucijo programskega jezika Scala, ki jo lahko dobimo na
naslovu:
http://www.scala-lang.org/downloads
Prav tako pa je potrebno nastaviti še dve spremenljivki okolja, kot je to prikazano na
primeru v tabeli 3.
Tabela 3: Spremenljivke okolja
Unix
$SCALA_HOME
$PATH%
/usr/local/share/scala
$PATH:$SCALA_HOME/bin
Windows
%SCALA_HOME%
%PATH%
c:\Progra~1\Scala
%PATH%;%SCALA_HOME%\bin
Ko smo omenjeni dve spremenljivki nastavili, lahko iz ukazne vrstice zaženemo
interaktivni tolmač programskega jezika Scala:
C:\>scala
Welcome to Scala version 2.9.1.final (Java HotSpot(TM) 64-Bit Server VM,
Java 1.6.0_23-ea).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val hello = "Hello world!"
hello: java.lang.String = Hello world!
![Page 40: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/40.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 28
Prevajalnik programskega jezika Scala se po vzoru programskega jezika Java imenuje
scalac.
Za resnejšo delo se je najbolje poslužiti enega izmed obstoječih razvojnih okolij, ki so
podprta z vtičniki za delo s programskim jezikom Scala. V tem primeru je potrebno samo
namestiti vtičnik za ustrezno razvojno okolje. Trenutno podprta razvojna okolja so:
Eclipse (Scala IDE).
IntelliJ IDEA (Scala Plugin).
NetBeans (Scala Plugin).
![Page 41: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/41.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 29
5 IMPLEMENTACIJA DOMENSKO SPECIFIČNEGA JEZIKA FDL
V PROGRAMSKEM JEZIKU SCALA
Izdelava domensko specifičnega jezika se, ne glede na programski jezik uporabljen za
implementacijo, prične z načrtovanjem. Čeprav v sklopu praktičnega dela implementiramo
jezik, katerega specifikacije so že podane in načrtovanje ni potrebno, kljub temu na hitro
predstavimo poglavitne korake pri načrtovanju domensko specifičnega jezika:
1. Namen domensko specifičnega jezika – V tej fazi je potrebno definirati namen
domensko specifičnega jezika. Dva izmed najpogosteje uporabljenih scenarijev sta
dokumentiranje znanja in generiranje programske kode. Pogosto pa so domensko
specifični jeziki uporabljeni tudi za generiranje testov, formalno verifikacijo,
konfiguriranje sistemov in simulacije. Prav tako moramo biti pozorni, da se
načrtovanje domensko specifičnega jezika prične z namenom, da je le ta čim bolj
konsistenten, kar pomeni, da je usmerjen v reševanje določene in le ene domene.
2. Realizacija domensko specifičnega jezika – Obstaja več načinov realizacije
domensko specifičnega jezika. Lahko ga realiziramo v celoti od začetka, lahko pa
uporabimo že obstoječ domensko specifičen jezik, ki ga po potrebi razširimo ali
omejimo. Prav tako se odločimo ali bomo uporabili tekstualno ali grafično notacijo.
3. Vsebina domensko specifičnega jezika – V tej fazi definiramo vsebino domensko
specifičnega jezika, ki naj zajema le nujne koncepte domene. Težimo k temu, da je
jezik čim bolj enostaven, s tem, da omejimo število konstruktov, ki jih jezik
uporablja. Izogibamo se konceptualni redundanci, kar pomeni, da določeno stvar
lahko zapišemo le na en način.
4. Sintaksa – Omenjena faza je namenjena specifikaciji sintakse, ki jo naj domensko
specifičen jezik uporablja. Težimo k temu, da je sintaksa čim bolj podobna že
obstoječi sintaksi, ki jo uporabljajo domenski strokovnjaki. Sintaksa mora biti čim
bolj samo opisna. Priporočljiva je tudi uvedba komentarjev v sam jezik. Stvar na
katero moramo paziti je razmerje med kompaktnostjo ter berljivostjo jezika. [4]
![Page 42: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/42.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 30
V naslednjih podpoglavjih bomo opisali potek ter samo implementacijo. Pred vsakim
večjim sklopom funkcionalnosti bomo opisali tudi uporabljene principe, in sicer tiste, ki jih
ne najdemo v programskem jeziku Java oziroma se v programskem jeziku Scala
razlikujejo.
5.1 Semantični model
Glede na to, da fazo načrtovanja v našem primeru lahko izpustimo, je prvi korak izdelava
semantičnega modela. Semantični model je eden izmed pomembnejših konstruktov
domensko specifičnih jezikov. Gre za interno predstavitev domensko specifičnega jezika.
Semantični model je lahko sestavljen le iz podatkovnega modela, lahko pa ga dopolnjuje
tudi logika. Domensko specifični jezik je le nekakšne vrste ovoj (ang. wrapper) okrog
semantičnega modela, kar je lepo razvidno pri dveh različnih implementacijah domensko
specifičnega jezika FDL, in sicer po principih prevajalnik/tolmač in vgrajeni pristop, ki sta
si sicer povsem različna, kljub temu pa se poslužujeta istega semantičnega modela kot to
prikazuje slika 5-1.
Slika 5-1: Uporaba semantičnega modela
5.1.1 Uporabljeni principi
Preden se lotimo opisa implementacije semantičnega modela, predstavimo principe, ki so
pri omenjeni implementaciji uporabljeni.
Spremljajoči objekti (ang. companion objects)
Kot smo že omenili, v programskem jeziku Scala za razliko od programskega jezika Java
ne poznamo statičnih spremenljivk in metod razredov, vendar lahko dosežemo enako
funkcionalnost s pomočjo spremljajočih objektov. Spremljajoč objekt v osnovi ni nič
![Page 43: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/43.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 31
drugega, kot objekt z natanko eno instanco katerega ime je enako razredu. Takšen objekt
lahko instanciramo z rezervirano besedico object. Za lažjo predstavo poglejmo primer:
class Oseba(ime: String, priimek: String)
object Oseba {
def apply(ime: String, priimek: String) = new Oseba(ime, priimek)
}
V zgornjem primeru smo definirali razred Oseba, ter pripadajoč spremljajoč objekt.
Primerke takšnih objektov pogosto uporabljamo za tvorjenje objektov. Poglejmo, na
kakšne načine vse lahko tvorimo objekt Oseba:
val oseba1 = new Oseba("Marjan", "Mernik")
val oseba2 = Oseba.apply("Tomaž", "Kosar")
val oseba3 = Oseba("Primož", "Kokol")
Tvorjenje objektov oseba1 in oseba2 v bistvu ni nič posebnega. Objekt oseba1
definiramo enako kot to storimo v programskem jeziku Java, objekt oseba2 tvorimo s
pomočjo metode apply spremljajočega objekta, ki nam vrne na novo tvorjeno instanco
razreda Oseba. Posebej zanimivo pa je instanciranje objekta oseba3. V bistvu ne gre za nič
drugega, kot klic metode apply. Poenostavljeno povedano, prevajalnik nadomesti klic
Oseba("Primož", "Kokol") s klicem Oseba.apply("Primož", "Kokol"). Razlog za
takšno delovanje je zapolniti vrzel med objektnim in funkcijskim. V programskem jeziku
Scala je namreč vsaka funkcija tudi objekt. Tako lahko funkcijo z enim argumentom
definiramo na dva načina:
val povecaj = (x: Int) => x + 1
val povecaj1 = new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
povecaj(0) == povecaj1(0) // Vrne true
Zagotovo pa je klic funkcije povecaj(0) prikladnejši kakor povecaj.apply(0).
![Page 44: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/44.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 32
Vzorčni razredi (ang. case classes)
Poglejmo si primer vzorčnega razreda:
case class Oseba(ime: String, priimek: String)
Kot je razvidno, razredu Oseba dodamo rezervirano besedico case. To je v splošnem vse,
kar je potrebno narediti, da običajen razred spremenimo v vzorčnega. Poglejmo si, kako
rezervirana besedica case vpliva na naš razred Oseba:
Razred lahko uporabljamo v navezavi z ujemanjem vzorcev. Omenjen princip
podrobneje predstavimo v sklopu opisa implementacije algebre semantičnega
modela.
Instance objekta lahko tvorimo brez rezervirane besedice new, kar je popolnoma
enako instanciranju objekta oseba3 v prejšnjem primeru. Prevajalnik namreč
vzorčnemu razredu samodejno doda spremljajoč objekt z implementirano metodo
apply, kakor to storimo v sklopu razlage spremljajočih objektov zgoraj.
Spremljajoč objekt prav tako deduje razred FunctionN, kar pomeni, da je le ta enak
funkciji.
Prevajalnik poskrbi za implementacijo metode unapply, ki jo podrobneje
predstavimo v sklopu poglavja ujemanja vzorcev, na katerega se neposredno
navezuje.
Argumenti konstruktorja so vidni zunaj razreda, saj se metode za dostop (ang.
getter) generirajo samodejno.
Prevajalnik samodejno prekrije metodo toString z implementacijo, ki izpiše ime
razreda in vse argumente.
Prevajalnik samodejno prekrije metodo equals z implementacijo, ki objekte istega
tipa primerja strukturno in ne po referenci.
Prevajalnik samodejno prekrije metodo hashCode z implementacijo tako, da le ta
uporabi vrednosti metode hashCode, ki jih vračajo argumenti konstruktorja [22].
![Page 45: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/45.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 33
Zapečateni razredi (ang. sealed classes)
Edina stvar, ki jo moramo razložiti preden si ogledamo implementacijo semantičnega
modela, so tako imenovani zapečateni razredi, ki jih označimo z rezervirano besedico
sealed. S tem prevajalniku povemo, da bo dedovanje določenega razreda mogoče le
znotraj zbirke, kjer je bil tako označen razred deklariran. Takšno informacijo potrebuje
prevajalnik, predvsem v navezavi z ujemanjem vzorcev, če želimo da nas opozarja, da
samo ujemanje vzorcev ne zajema vseh možnih izidov.
5.1.2 Implementacija
Oglejmo si še konceptualni razredni diagram, ki prikazuje strukturo dveh semantičnih
modelov, in sicer semantični model namenjen izrazom za opis lastnosti prikazan na sliki
5-2 ter semantični model za opis omejitev prikazan na sliki 5-3.
Slika 5-2: Semantični model namenjen opisu lastnosti
![Page 46: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/46.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 34
Slika 5-3: Semantični model namenjen opisu omejitev
Prikazana razredna diagrama služita implementaciji semantičnega modela, ki se nahaja v
zbirki FDLSemanticModel.scala.
package si.kokol.fdl
sealed abstract class FeatureExp
abstract class FeatureExpList() extends FeatureExp {
val features: List[FeatureExp]
}
case class AtomicFeature(name: String) extends FeatureExp {
override def toString(): String = {name}
def requires(feature: AtomicFeature): Constraint = Requires(this,
feature)
def excludes(feature: AtomicFeature): Constraint = Excludes(this,
feature)
}
case class Opt (feature: FeatureExp) extends FeatureExp {
override def toString(): String = "opt(" + feature.toString() + ")"
}
case class All(features: List[FeatureExp]) extends FeatureExpList{
override def toString(): String = "\nall("+features.mkString(",")+")"
}
case class OneOf(features: List[FeatureExp]) extends FeatureExpList {
override def toString(): String = "one-
of("+features.mkString(",")+")"
}
case class MoreOf(features: List[FeatureExp]) extends FeatureExpList {
![Page 47: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/47.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 35
override def toString(): String = "more-
of("+features.mkString(",")+")"
}
object All{def apply(features: FeatureExp*) = new All(features.toList)}
object OneOf{def apply(features: FeatureExp*) = new
OneOf(features.toList)}
object MoreOf{def apply(features: FeatureExp*) = new
MoreOf(features.toList)}
Pri nekaterih izmed zgornjih objektov je bila prekrita metoda toString, s čimer smo, kot
bomo videli v nadaljevanju, dosegli izpis skladen s sintakso domensko specifičnega jezika
FDL.
Prav tako je iz izvorne kode razvidno, da imajo razredi All, OneOf ter MoreOf pripadajoče
spremljajoče objekte, čeprav smo predhodno povedali, da vzorčnim razredom prevajalnik
samodejno doda spremljajoče objekte z definirano metodo apply, katere argumenti so
enaki konstruktorju razreda. S tem, ko smo spremljajoči objekt dodali ročno, smo v bistvu
razširili spremljajoči objekt, ki ga je ustvaril prevajalnik. Razlog, da smo spremljajočemu
objektu dodali še eno metodo apply je ta, da lahko razred instanciramo ne samo s pomočjo
seznama, ampak tudi s pomočjo variabilnega števila argumentov npr.:
val all = All(List(AtomicFeature("avtomatski"), AtomicFeature("ročni")))
ali
val all = All(AtomicFeature("avtomatski"), AtomicFeature("ročni"))
Implementacija semantičnega modela za opis omejitev je krajša, in sicer:
sealed abstract class Constraint
case class Include(atomicFeature: AtomicFeature) extends Constraint
case class Exclude(atomicFeature: AtomicFeature) extends Constraint
case class Requires(atomicFeature1: AtomicFeature, atomicFeature2:
AtomicFeature) extends Constraint
case class Excludes(atomicFeature1: AtomicFeature, atomicFeature2:
AtomicFeature) extends Constraint
Semantčni model ne vsebuje nobenih metod za samo dekompozicijo, oziroma, kot bomo
videli kasneje, normalizacijo izrazov. Razlog tiči v tem, da se za omenjeno nalogo
poslužimo funkcijskega pristopa ujemanja vzorcev.
![Page 48: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/48.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 36
5.2 Algebra diagramov lastnosti semantičnega modela
V poglavju 3.4.2 smo opisali domensko specifični jezik FDL. Prav tako smo omenili, da
algebro diagramov lastnosti sestavljajo pravila normalizacije, variabilnosti, razširitve ter
ustreznosti. V nadaljevanju predstavimo uporabljene principe ter samo implementacijo
algebre diagramov, ki sestoji iz omenjenih pravil.
5.2.1 Uporabljeni principi
V tem poglavju si oglejmo princip, ki dejansko omogoča, da samo logiko za normaliziranje
FDL izrazov ločimo od samega semantičnega modela. Gre za tako imenovano ujemanje
vzorcev. Približek ujemanja vzorcev površno podpira tudi Java, in sicer le nad nekaterimi
primitivnimi tipi (Java 7 tudi nad tipom String) s pomočjo programskega izraza switch.
V naslednjem poglavju bomo omenjen princip podrobneje pogledali, saj v navezavi z že
predstavljenimi vzorčnimi razredi predstavlja jedro naše rešitve implementacije domensko
specifičnega jezika FDL [9].
Ujemanje vzorcev
Poglejmo si najprej preprost primer ujemanja vzorcev:
abstract class Izraz
case class Stevilo(vrednost: Int) extends Izraz
case class Vsota(izraz1: Izraz, izraz2: Izraz) extends Izraz
case class Deljenje(izraz1: Izraz, izraz2: Izraz) extends Izraz
def ovrednoti(izraz: Izraz): Int = izraz match {
case Stevilo(vrednost) => vrednost
case Deljenje(izraz1, izraz2) => ovrednoti(izraz1) / ovrednoti(izraz2)
case Vsota(izraz1, izraz2) => ovrednoti(izraz1) + ovrednoti(izraz2)
}
Klic metode
ovrednoti(Vsota(Stevilo(3), Vsota(Stevilo(1), Stevilo(5)))))
vrne vrednost 9. Vrstni red case izrazov je pomemben, kar pomeni, da imajo prej zapisani
vzorci večjo prioriteto. Kakor hitro je določen vzorec najden, se vrne pripadajoč izraz,
nadaljnje iskanje ujemajočega vzorca pa se prekine. Prikazano delovanje omogoča
ujemanje vzorcev v navezavi z vzorčnimi razredi. Predhodno smo opisali vse lastnosti
vzorčnih razredov razen metode unapply. Ravno implementacija te metode pa je pogoj za
ujemanje vzorcev. Izraz iz zgornjega primera:
![Page 49: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/49.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 37
case Stevilo(vrednost)
se namreč pretvori v klic metode Stevilo.unapply, ki bi, če bi jo napisali sami, izgledala
takole:
object Stevilo {
def unapply(stevilo: Stevilo) = {
Some(stevilo.vrednost)
}
}
Že ime same metode pove, da gre za metodo, ki je ravno nasprotna metodi apply. Scala v
času izvajanja sama izlušči parametre, ki jih vrnemo znotraj objekta Some. Glede na to, da
je naš semantični model sestavljen iz velikega števila razredov, raba vzorčnih razredov
precej pripomore k kompaktnosti same programske kode, kot to lahko vidimo v
nadaljevanju [9].
V programskem jeziku Scala lahko tvorimo vzorce na več načinov. Poglejmo si možne
načine.
Prvi način je uporaba spremenljivke v izrazu za ujemanje vzorcev, kot na primer
case sprem. Takšen vzorec se ujame z vsemi objekti, sama vrednost pa se priredi
spremenljivki sprem. Če lahko samo vrednost zanemarimo oziroma le-ta ni
pomembna, namesto imena spremenljivke uporabimo kar znak _. Poglejmo si
primer:
izraz match {
case sprem => println("Spremenljivka: " + sprem)
}
Ujemanje vzorcev na podlagi tipov je naslednji zelo uporaben način. Sintaksa
zgleda takole:
izraz match {
case stevilo: Stevilo => println("Tole je stevilo: " + stevilo)
case _ => println("To je ostalo.")
}
V zgornjem primeru se prvi vzorec ujame z vsemi objekti tipa Stevilo, prav tako
pa se le ta priredi spremenljivki stevilo. Vse objekte, ki niso tipa Stevilo,
ujamemo s pomočjo znaka _.
Vzorce lahko tvorimo tudi s konstantami:
![Page 50: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/50.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 38
val stevilo = 3
stevilo match {
case 1 => println("To je stevilo 1")
case 2 => println("To je stevilo 2")
case 3 => println("To je stevilo 3")
case _ => println("To je stevilo vecje od 3")
}
Vzorci konstruktorjev (ang. constructor patterns) oblike C(p1, . . . ,pn), kjer je
C vzorčni razred, p1, . . . ,pn pa vzorci, se ujemajo z vsemi instancami razreda
C, ki so bile neposredno ali posredno preko konstruktorja izpeljanega razreda
instancirane z vrednostmi v1, . . . , vn, ki ustrezajo vzorcu p1, . . . ,pn.
Poglejmo si primer:
izraz match {
case Deljenje(stevec, 0) => println("Ups.. Deljenje števca " +
stevec + " z nič!")
}
Še zadnja izmed možnosti je povezovanje spremenljivke z vrednostjo, ki pa
izgleda takole:
izraz match {
case stevilo@Stevilo(_) => println(stevilo.vrednost)
}
Spremenljivki stevilo, smo tako priredili iskano vrednost tipa Stevilo [7].
Eden izmed pomembnejših konstruktov ujemanja vzorcev je tako imenovan vzorčni čuvaj
(ang. pattern guard). Gre za to, da lahko ujemanje samega vzorca še dodatno omejimo:
Stevilo match {
case stevilo:Int if stevilo % 2 => println("Sodo: " + stevilo)
case stevilo:Int => println("Liho: " + stevilo)
}
Ujemanje vzorcev igra pomembno vlogo pri sami algebri diagramov lastnosti. Vsa pravila
normalizacije so po večini implementirana s pomočjo le teh. Ostale principe obrazložimo
tekom opisa same implementacije.
![Page 51: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/51.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 39
5.2.2 Implemetacija
Vsa pravila za normalizacijo so, kot smo že dejali, ločena od samega semantičnega
modela. Metoda normalize razreda FDLManager.scala je namenjena normalizaciji FDL
izrazov:
def normalize(feature: FeatureExp): FeatureExp = feature match {
case a@AtomicFeature(_) => a
case Opt(opt@Opt(_)) => normalize(opt)
case Opt(feature) => normalizeOpt(feature)
case All(list) =>
normalizeAll(flatten[All](normalizeList(list map normalize)))
case OneOf(list) =>
normalizeOneOf(flatten[OneOf](normalizeList(list map normalize)))
case MoreOf(list) =>
normalizeMoreOf(flatten[MoreOf](normalizeList(list map
normalize)))
}
V zgornji metodi so uporabljeni principi opisani v predhodnem poglavju. Celotna
normalizacija izrazov je implementirana s pomočjo ujemanja vzorcev. Poglejmo si
ujemanje vzorcev zgornje metode po vrstnem redu zapisa:
Prvi vzorec case a@AtomicFeature(_) se ujema z atomarnimi lastnostmi. Samo
lastnost pusti nespremenjeno, saj gre za najmanjši konstrukt FDL izraza.
Drugi vzorec case Opt(opt@Opt(_)) se ujema z vgnezdenim opcijskim izrazom
znotraj opcijskega izraza. Rezultat ujemanja tega vzorca je v bistvu izraz znotraj le
enega opcijskega bloka. Gre za normalizacijsko pravilo 3. Rezultat ujemanja je
ponovni rekurzivni klic metode normalize, saj moramo zagotoviti normalizacijo
do najnižjega nivoja drevesa semantičnega modela. Rezultat se bo ob naslednjem
klicu zagotovo ujel z vzorcem, ki sledi.
Vzorec case Opt(feature) se ujame z vsemi opcijskimi lastnostmi. Rezultat je
klic metode za normalizacijo opcijskega izraza.
Rezultat ujemanja vzorca case all@All(_) je klic pripadajoče metode za
normalizacijo objektov tipa All.
Rezultat ujemanja vzorca case oneOf@OneOf(_) je klic pripadajoče metode za
normalizacijo objektov tipa OneOf.
![Page 52: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/52.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 40
Rezultat ujemanja vzorca case moreOf@MoreOf(_) je klic pripadajoče metode za
normalizacijo objektov tipa MoreOf.
Opisana metoda neposredno izvede le tretje normalizacijsko pravilo. Ostala pravila
normalizacije se posredno izvedejo preko klicev ostalih metod, ki v bistvu normalizirajo
objekte tipa Opt in sezname znotraj objektov tipa All, OneOf, MoreOf. Poglejmo si najprej
prvo klicano metodo, ki je rezultat ujemanja vzorca case Opt(feature):
private def normalizeOpt(feature: FeatureExp) = {
normalize(feature) match {
case opt@Opt(feature) => opt
case feature => Opt(feature)
}
}
Metoda se torej ujame z vsemi objekti tipa Opt. Prvi izraz metode je ponoven klic metode
normalize, saj je lahko rezultat normalizacije spet opcijska lastnost. V primeru, da je temu
res tako, vrnemo normaliziran izraz takšnega kot je, v nasprotnem primeru pa
normaliziran izraz vrnemo kot opcijsko lastnost. S tem zagotovimo, da metoda nikoli ne
vrne opcijskega izraza znotraj opcijskega izraza. Preostanek normalizacije je
implementiran na podoben način v smislu tega, da drevo semantičnega modela začnemo
normalizirati pri listih drevesa, saj lahko rezultat trenutnega nivoja normalizacije vpliva na
višje nivoje drevesa.
Rezultat ujemanja vzorcev, ki se ujamejo z objekti tipa All, OneOf, MoreOf so klici metod
za normalizacijo vsakega od naštetih tipov. Kot bomo videli v nadaljevanju, se v vsaki
izmed omenjenih metod kličeta metodi, ki implementira pravili normalizacije 1 in 2, zato
ju bomo opisali kar sedaj:
private def normalizeList(featureList: List[FeatureExp]):
List[FeatureExp] = {
normalizationRule2(featureList).distinct;
}
private def normalizationRule2(featureList: List[FeatureExp]):
List[FeatureExp] = featureList filter {
case Opt(feature) => !featureList.contains(feature)
case _ => true
}
V drugi metodi zgoraj smo uporabili funkcijo filter razreda List, katere deklaracija je
takšna:
![Page 53: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/53.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 41
filter(p: (A) ⇒ Boolean): List[A]
Funkcija kot parameter prejme predikat p, ki določa ustreznost elementov, vrne pa seznam
elementov, ki ustrezajo omenjenemu predikatu. Vrstni red se ohrani, kar sicer ni
pomembno. V našem konkretnem primeru je argument funkcije sintaksa za ujemanje
vzorcev brez rezervirane besedice match. Skrivnost je v tem, da se argument funkcije:
{
case Opt(feature) => !featureList.contains(feature)
case _ => true
}
v ozadju preoblikuje v anonimno funkcijo:
(x: FeatureExp) => (x: FeatureExp) match {
case Opt(feature) => !featureList.contains(feature)
case _ => true
}
Metoda normalizationRule2 torej ne naredi nič drugega kot to, da odstrani vse opcijske
lastnosti, katerih izraz se pojavlja neposredno v seznamu. Metoda normalizeList, po
klicu metode normalizationRule2, s pomočjo metode distinct odstrani vse podvojene
elemente.
Opišimo še eno metodo, ki se uporablja pri normalizaciji vseh treh instanc seznamov All,
OneOf, MoreOf, in sicer metodo z imenom flatten. Gre za metodo, ki je namenjena temu,
da seznam, ki vsebuje seznam enakega tipa dejansko sploščimo (ang. flattening). Gre za
pravila normalizacije 5, 7 in 10. Poglejmo si primer. Izraz:
all(a, b, c, all(d, e, f), more-of(g, i, h))
po klicu metode flatten izgleda takole:
all(a, b, c, d, e, f, more-of(g, i, h))
Poglejmo si metodo:
private def flatten[T <: FeatureExpList: Manifest](list:
List[FeatureExp]): List[FeatureExp] = list flatMap {
case all: All if manifest[T] <:< manifest[All] => all.features
case oneOf: OneOf if manifest[T] <:< manifest[OneOf] =>
oneOf.features
case moreOf: MoreOf if manifest[T] <:< manifest[MoreOf] =>
moreOf.features
case other => List(other)
}
![Page 54: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/54.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 42
V sklopu te metode ne naredimo nič drugega kot to, da se sprehodimo skozi seznam, pri
čemer sezname, ki so tipa T sploščimo, ostale elemente pa prepišemo. Preostala pravila so
zajeta v naslednjih štirih metodah, ki pa jih zaradi že opisanih principov zgoraj ne bomo
posebej opisovali:
private def constructAll(features: List[FeatureExp]): FeatureExp =
features match {
case List(oneFeature) => oneFeature // N. 4
case list => All(list)
}
private def constructOneOf(features: List[FeatureExp]): FeatureExp =
features match {
case List(oneFeature) => oneFeature // N. 6
case list => if (list.exists(_.isInstanceOf[Opt])) { // N. 8
Opt(OneOf(removeOptFeatures(list)))
} else {
OneOf(list)
}
}
private def constructMoreOf(features: List[FeatureExp]): FeatureExp =
features match {
case List(oneFeature) => oneFeature // N. 9
case list => if (list.exists(_.isInstanceOf[Opt])) { // N. 11
Opt(MoreOf(removeOptFeatures(list)))
} else {
MoreOf(list)
}
}
Metoda removeOptFeatures, klicana znotraj metod constructOneOf in
constructMoreOf , kot že samo ime pove, odstrani vse opcijske lastnosti iz seznama.
Njena implementacija je naslednja:
private def removeOptFeatures(list: List[FeatureExp]): List[FeatureExp] =
list flatMap {
case Opt(elem) => List(elem)
case elem => List(elem)
}
Vse zgoraj opisane metode temeljijo na indirektni rekurziji. Posledično se metoda
normalize kliče nad vsakim elementom semantičnega modela. Rezultat je normaliziran
izraz, ki je primeren za nadaljnjo obdelavo.
Naslednji korak je pretvorba normaliziranega diagrama lastnosti v disjunktivno normalno
obliko v skladu z naslednjimi pravili:
[E1] all(Fs, F?, Ft)
![Page 55: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/55.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 43
= one-of( all(Fs, F, Ft),
all(Fs, Ft))
[E2] all(Ft, F?, Fs)
= one-of( all(Ft, F, F),
all(Ft, Fs))
[E3] all(Fs, one-of(F, Ft), Fs’)
= one-of( all(Fs, F, Fs’),
all(Fs, one-of(Ft), Fs’))
[E4] all(Fs, more-of(F, Ft), Fs’)
= one-of( all(Fs, F, Fs’),
all(Fs, F, more-of(Ft),Fs’),
all(Fs, more-of(Ft), Fs’)
)
Rešitev, ki sovpada z naslednjimi pravili je naslednja:
def expandRule1Rule2(featureList: List[FeatureExp]): List[FeatureExp] =
featureList match {
case Nil => Nil
case Opt(head) :: tail => List(All(head::tail), All(tail))
case head :: tail => expandRule1Rule2(tail :+ head)
}
def expandRule3(featureList: List[FeatureExp]): List[FeatureExp] =
featureList match {
case Nil => Nil
case OneOf(head) :: tail => List(All((head.head)::tail),
All(OneOf(head.tail)::tail))
case head :: tail => expandRule3(tail :+ head)
}
def expandRule4(featureList: List[FeatureExp]): List[FeatureExp] =
featureList match {
case Nil => Nil
case MoreOf(head) :: tail => List( All((head.head)::tail),
All((head.head)::MoreOf(head.tail)::tail),
All(MoreOf(head.tail)::tail))
case head :: tail => expandRule4(tail :+ head)
}
Metode, ki posredno preko indirektne rekurzije kličejo zgoraj napisane metode, najdemo v
izvorni kodi, ki je podana kot priloga k diplomskemu delu. Omenimo le, da vse zgoraj
opisane metode vračajo seznam elementov All, saj element OneOf, ki je vsebnik
disjunktivne normalne oblike, zaradi hitrejšega izvajanja na koncu le dodamo.
V tem poglavju smo strnili najpomembnejše dele izvorne kode implementacije algebre
diagramov lastnosti, ki se izvedejo nad že opisanim semantičnim modelom. Kot bomo
videli v nadaljevanju, ostajata tako semantični model, kakor tudi algebra diagramov
![Page 56: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/56.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 44
lastnosti enaka za oba pristopa, in sicer vgrajeni pristop in pristop prevajalnik/tolmač. V
nadaljevanju bomo torej opisali dva različna pristopa za polnjenje semantičnega modela.
5.3 Vgrajeni pristop
Kot smo že dejali, pri vgrajenem pristopu gre za to, da za vnos domensko specifičnega
jezika uporabimo konstrukte gostujočega jezika, v našem primeru programskega jezika
Scala. Poglejmo si uporabljene principe, ki nas pripeljejo do rešitve, ki je kolikor se da
približana formalni definiciji domensko specifičnega jezika FDL.
5.3.1 Uporabljeni principi
V tem poglavju si ogledamo princip, ki je največ pripomogel k sami implementaciji
vgrajenega domensko specifičnega jezika, ki smo ga implementirali, in sicer implicitno
konverzijo (ang. implicit conversion). Prav tako se dotaknemo tudi ostalih konstruktov, ki
tako ali drugače pripomorejo k implementaciji rešitve diplomske naloge.
Implicitna konverzija
Samo implicitno konverzijo najlažje predstavimo na primeru. Poglejmo preprost razred, ki
predstavlja kompleksno število:
case class Komp(val re: Double,val img: Double) {
def +(c: Komp) = Komp(this.re + c.re, this.img + c.img)
def +(d: Double) = Komp(this.re + d, this.img)
}
Razred ima dve metodi. Prva omogoča seštevanje dveh števil, ki sta obe instanci razreda
Komp, druga pa omogoča seštevanje kompleksnega števila s številom Double. Poglejmo si
rabo omenjenih metod:
val c1 = Komp(0,0)
val c2 = Komp(1,1)
val result1 = c1 + 50.0
val result2 = c1 + c2
Kot smo dejali v uvodni predstavitvi programskega jezika Scala, je možno večino
operatorjev uporabiti kot ime metode. Zgornji klic metode c1 + 50.0 je v bistvu enak bolj
znani obliki zapisa: c1.+(50.0). Trenutna implementacija ima še zmeraj eno
pomanjkljivost. Poglejmo si naslednji izraz:
val result3 = 10.0 + c2
![Page 57: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/57.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 45
Zgornjo število 10.0 je v bistvu tipa Double. Težava je v tem, da razred Double ne podpira
metode +, ki bi kot argument prejela naš razred kompleksnega števila Komp. Ravno to pa je
težava, ki jo pomaga zaobiti implicitna konverzija. Poglejmo si naslednjo metodo:
implicit def doubleToKomp(double: Double) = Komp(double, 0)
Kot vidimo implicitna konverzija ni nič drugega kot deklaracija metode, pred katero
vstavimo rezervirano besedico implicit. V konkretnem primeru to pomeni, da se bo
metoda klicala avtomatsko takrat, ko bo prevajalnik naletel na objekt tipa Double, med tem
ko bo pričakoval objekt tipa Komp. Deklaracija takšne implicitne metode je veljavna v
obsegu, kjer je definicija le-te vidna.
5.3.2 Implementacija
Naslednji primer:
Avto: all( šasija,
one-of(avtomatski, ročni),
more-of(električni, bencinski),
one-of(majhnaMoč, srednjaMoč, velikaMoč),
opt(priklop))
najprej zapišemo v programskem jeziku Scala direktno s pomočjo vzorčnih razredov
oziroma s pomočjo trenutnega semantičnega modela:
val Avto = All(
AtomicFeature("sasija"),
OneOf( AtomicFeature("avtomatski"), AtomicFeature("rocni")),
MoreOf( AtomicFeature("elektricni"), AtomicFeature("bencinski)),
OneOf( AtomicFeature("majhnaMoc"),
AtomicFeature("srednjaMoc"),AtomicFeature("velikaMoc")),
Opt( AtomicFeature("priklop"))
)
Kot vidimo trenutna rešitev ne more biti zadovoljiva. Če bi dovolili, da uporabnik zapiše
diagram lastnosti tudi s pomočjo sestavljenih lastnosti, potem bi to pomenilo, da bi bilo vse
sestavljene lastnosti pod točno določenim imenom potrebno vstaviti v zgoščeno kazalo
(ang. hashmap), nad katerim bi kasneje opravili sicer preprosto substitucijo spremenljivk.
Takšnega dela zagotovo ne moremo naložiti končnemu uporabniku. V naslednjih korakih
predstavimo, kako smo postopoma prišli do rešitve, ki je primerna za končne uporabnike in
teži kolikor je to mogoče k formalni definiciji domensko specifičnega jezika FDL. Prva
pomanjkljivost v zgornjem primeru je ta, da moramo vse atomarne lastnosti ustvariti
![Page 58: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/58.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 46
neposredno s pomočjo spremljajočega objekta AtomicFeature, kar je precej moteče,
posledično pa bi bil takšen zapis domensko specifičnega jezika FDL v programskem jeziku
Scala precej drugačen od formalne EBNF definicije, ki smo jo predstavili v enem izmed
prejšnjih poglavij. Za omenjeno težavo obstaja preprosta rešitev, in sicer tako imenovana
implicitna konverzija. Poglejmo si torej želeni rezultat. Namesto izraza:
val sl = All(AtomicFeature("lastnost1"), AtomicFeature("lastnost2"))
bi želeli konstrukcijo neke sestavljene lastnosti sl na naslednji način:
val sl = All("lastnost1", "lastnost2")
Rešitev za dosego zgornje notacije, kot že rečeno, s pomočjo implicitne konverzije, zgleda
takole:
implicit def stringConversion(s: String): AtomicFeature =
AtomicFeature(s.name)
Izrazu:
val sl = All("lastnost1", "lastnost2")
prevajalnik vrine klic implicitno predznačene metode:
val sl = All(stringConversion("lastnost1"),stringConversion("lastnost2"))
V naši implementaciji za zapis atomarnih ter imen sestavljenih lastnosti raje uporabljamo
objekt tipa Symbol, ki ga zapišemo na naslednji način: 'lastnost1. Tako na primer
'lastnost1.name == "lastnost1" vrne true. Objekt Symbol je načeloma zelo podoben
objektu tipa String. Razlika je le, da objekta tipa Symbol, ki sta sestavljena iz enakega
niza znakov, zagotovo predstavljata enako instanco znotraj pomnilnika. Z uporabo tipa
Symbol lahko sedaj definiramo lastnosti namesto z dvema narekovajema, le z enim.
V nadaljevanju si oglejmo možnost implementacije FDL programa znotraj programskega
jezika Scala s pomočjo vgrajenega pristopa, ki smo ga razvili. Vse kar je potrebno storiti
je, da končni uporabnik ustvari instanco razreda FDLProgram, katerega deklaracija je
naslednja:
class FDLProgram protected
(featureDefinitions: Pair[Symbol, FeatureExp]*)
(constraintsArgs: Constraint*)
![Page 59: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/59.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 47
Kot vidimo prvi del konstruktorja prejme variabilno število objektov tipa Pair, ki ga
sestavljata tipa Symbol ter FeatureExp, drug dela konstruktorja pa prejme variabilno
število omejitev tipa Constraint. Da smo za vstavljanje definicij lastnosti izbrali prav
Pair, ni naključje. Instanco tipa Pair je zelo enostavno definirati:
'Printer -> OneOf('laser, 'inkjet)
Prav tako je takšna notacija zelo podobna formalni definiciji. Različen je sicer operator
prireditve, ki je v formalni definiciji = v našem primeru pa ->. Operatorja prireditve se v
programskem jeziku Scala namreč ne da predefinirati. Druga zelo dobra lastnost pa je ta,
da je variabilno število takšnih tipov parametrov zelo lahko pretvoriti v povezano zgoščeno
kazalo, ki kasneje omogoča izvedbo substitucije spremenljivk:
LinkedHashMap(featureDefinitions.toList:_*)
Drugi del konstruktorja, ki sprejme variabilno število omejitev, pretvorimo v seznam ter ga
kasneje uporabimo pri pravilih za določanje omejitev.
Poglejmo si sedaj primer celotnega FDL programa napisanega znotraj programskega jezika
Scala:
object IODevice extends FDLProgram(
'IODevice -> All ( Opt('Printer) , 'mouse, 'Display,
Opt('webcamera) , 'keyboard,
Opt('microphone), Opt('Receiver)),
'Printer -> OneOf('laser , 'inkjet),
'Display -> OneOf('crt , 'lcd),
'Receiver -> OneOf('speakers, 'headphones)
)(
'webcamera requires 'microphone,
include ('lcd)
)
ter za primerjavo znotraj programskega jezika Haskell:
ioDevice, printer, display, receiver :: Feature f => f
ioDevice = all [opt (printer) , atomic "mouse"
, display , opt (atomic "webcamera" )
, atomic "keyboard" , opt (atomic "microphone" )
, opt (receiver) ]
printer = one_of [ atomic "laser", atomic "inkjet" ]
display = one_of [ atomic "crt" , atomic "lcd" ]
receiver = one_of [ atomic "speakers", atomic "headphones" ]
![Page 60: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/60.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 48
constraint1, constraint2 :: Constraint
constraint1 = "webcamera" 'requires' "microphone"
constraint2 = include "lcd"
spec :: Feature f => Spec f
spec = Spec("IODevice", ioDevice)
[("Printer", printer),
("Display", display),
("Receiver", receiver)]
[constraint1, constraint2 ]
Že na prvi pogled je razvidno, da je rešitev zapisana v programskem jeziku Scala krajša.
Ker so imena spremenljivk ter programa v obeh primerih popolnoma enaka, je lahko eden
izmed pokazateljev kompleksnosti tudi število znakov potrebnih za zapis domensko
specifičnega jezika. Tako je bilo za zapis v programskem jeziku Scala potrebnih 280
znakov, medtem ko v programskem jeziku Haskell 551.
5.4 Pristop prevajalnik/tolmač
Naslednji pristop implementacije domensko specifičnega jezika FDL, ki se ga lotimo, je
pristop prevajalnik/tolmač. Gre za to, da lahko domensko specifičen jezik podamo v
notaciji, ki je skladna s formalno definicijo. Vhod v program je tako lahko preprosta
tekstovna datoteka ali povedano drugače, niz znakov.
5.4.1 Uporabljeni principi
Za omenjeno rešitev je potrebno razviti razpoznavalnik, ki napolni že obstoječ semantični
model. Omenjena naloga je realizirana s pomočjo pristopa kombiniranih razpoznavalnikov,
ki so del standardne distribucije programskega jezika Scala.
Kombinirani razpoznavalniki
Implementacija kombiniranih razpoznavalnikov v programskem jeziku Scala se poslužuje
rekurzivno sestopajočega načina razpoznavanja (ang. recursive descent parsing) [13].
Vsak neterminalni simbol predstavlja procedura, ki neterminalni simbol razpozna.
Omenjene metode se lahko med seboj kličejo in tako tvorijo medsebojno rekurzijo. Za
razpoznavanje neterminala X razpoznavalnik sestopi do najbolj desnega simbola, ki
sestavlja X, ter ga skuša razpoznati. Če je najbolj desni simbol spet neterminal, se postopek
ponovi, v nasprotne primeru pa se opravi direktna primerjava terminalnega simbola z
![Page 61: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/61.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 49
vhodom. Gramatika je tako že vodilo za implementacijo razpoznavalnika, kot bomo videli
v nadaljevanju.
Sistem kombiniranih razpoznavalnikov sestavljajo primitivni razpoznavalniki (ang.
primitive parsers) ter kombinatorji (ang. combinators):
Primitivni razpoznavalnik razpoznava nize znakov.
Kombinator je funkcija, ki je namenjena združevanju primitivnih razpoznavalnikov
tako, da le ti kot celota tvorijo nov razpoznavalnik, ki lahko spet tvori nov
sestavljen razpoznavalnik.
Poglejmo si preprost primer kombiniranega razpoznavalnika, ki ustreza EBNF definiciji
[8]:
expr ::= term {"+" term | "-" term}
term ::= factor {"*" factor | "/" factor)
factor ::= floatingPointNumber | "(" expr ")"
Zapis kombiniranega razpoznavalnika, ki ustreza zgornji definiciji je takšen:
import scala.util.parsing.combinator._
class Arith extends JavaTokenParsers {
def expr: Parser[Any] = term ~rep("+"~term | "-"~term)
def term: Parser[Any] = factor ~rep("*"~term | "/"~term)
def factor: Parser[Any] = floatingPointNumber | "("~expr~")"
}
Razred Arith deduje JavaTokenParser, v katerem so že definirani razpoznavalniki števil,
spremenljivk in podobno. Takšen v naprej definiran razpoznavalnik
floatingPointNumber uporabimo tudi mi. Kot vidimo je za prehod iz notacije EBNF v
implementacijo skorajšnja preslikava, kar pa ne drži v vseh primerih. Kombinirani
razpoznavalniki programskega jezika Scala namreč temeljijo na gramatiki razpoznavanja
izrazov PEG (ang. parsing expression grammars). Glavna razlika med EBNF in PEG je ta,
da operator |, ki v EBNF označuje izbiro, v PEG oziroma programskem jeziku Scala
predstavlja prioritetno izbiro [6]. Operator za prioritetno PEG je sicer /, vendar se je Scala
raje poslužila operatorja |, ki na nek način posnema EBNF način zapisa. Z njim torej
določimo vrstni red vzorcev, ki se uporabljajo pri razpoznavanju. Ko se najde prvi vzorec,
se preverjanje konča. Poglejmo si primer. V kontekstno prosti gramatiki sta EBNF pravili
A ::= ab | a in A ::= a | ab enaki, medtem ko sta enako zapisani pravili v PEG
![Page 62: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/62.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 50
gramatiki semantično popolnoma drugačni. Druga alternativa pravila A -> a | ab torej
ab ne bi bila izbrana nikoli, saj je vedno, ko je na vhodu prisoten znak 'a' izbrana prva
alternativa, nadaljnje preverjanje pa se zaključi. Tabela 4 prikazuje notacijo in opis še
ostalih PEG operatorjev znotraj programskega jezika Scala:
Tabela 4: Notacija PEG operatorjev programskega jezika Scala
OPIS PEG notacija SCALA notacija
Literal niza znakov " " " " REGEX [ ] “[ ]“.r
Poljuben znak . “.”.r
Grupiranje (e) (e)
Opcija e? (e?) ali opt(e)
Nič ali več e* e* ali rep(e)
En ali več e+ e+ ali rep1(e)
In-predikat &e guard(e)
Ne-predikat !e not(e)
Zaporedje e1e2 e1 ~ e2
Prioritetna izbira e1 / e2 e1 | e2
Poglejmo sedaj, kakšen je rezultat zgornjega razpoznavalnika napisanega v programskem
jeziku Scala, ki kot vhod sprejme naslednji niz znakov 2 * (3 + 7):
((2~List((*~((((~((3~List())~List((+~(7~List())))))~))~List()))))~List())
Rezultat je v bistvu drevo, katerega izpis je dokaj nerazločen. Rezultat, ki ga želimo po
navadi, je ovrednoten izraz ali pa napolnjen semantični model, ki predstavlja vmesno fazo
med samim razpoznavanjem vhoda ter ovrednotenjem. V nadaljevanju si oglejmo oba
primera, in sicer direktno ovrednotenje izraza, ter gradnjo semantičnega modela s pomočjo
pravila za prepisovanje (ang. rewriting rules). Poglejmo si najprej razpoznavalnik, ki izraz
ovrednoti direktno:
trait Arith extends JavaTokenParsers {
def expr: Parser[Double]= chainl1(term, "+" ^^^ Add | "-" ^^^ Sub)
def term = chainl1(factor, "*" ^^^ Mul | "/" ^^^ Div)
def factor = floatingPointNumber ^^ Number | "(" ~> expr <~ ")"
val Add = (a: Double, b: Double) => a + b
val Sub = (a: Double, b: Double) => a - b
val Mul = (a: Double, b: Double) => a * b
val Div = (a: Double, b: Double) => a / b
val Number = (a: String) => a.toDouble
}
![Page 63: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/63.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 51
Preden nadaljujemo, naj razložimo do sedaj še ne videno rezervirano besedo trait. Gre za
podoben konstrukt vmesniku programskega jezika Java s to razliko, da značilnost (ang.
trait) omogoča tudi konkretne implementacije.
Kombinator chainl1 je namenjen razpoznavanju elementov, ki so ločeni s kakšnim
drugim elementom. V zgornjem primeru seštevanja to pomeni, da imamo seznam simbolov
term ločenih s simbolom +, ki se s klicem "+" ^^^ Add nadomesti z metodo Add, podobno
to storimo za alternativo odštevanja. Enako storimo tudi za množenje, ter deljenje.
V primeru, da si izraza ne bi želeli ovrednotiti direktno, ampak bi želeli samo napolniti
semantični model, bi celoten razpoznavalnik ostal enak. Izjema je le metoda expr, ki mora
vračati Parser[Expr], same metode za ovrednotenje pa bi nadomestili z vzorčnimi
razredi:
trait Arith extends JavaTokenParsers {
def expr: Parser[Expr] = chainl1(term, "+" ^^^ Add | "-" ^^^ Sub)
def term = chainl1(factor, "*" ^^^ Mul | "/" ^^^ Div)
def factor = floatingPointNumber ^^ Number | "(" ~> expr <~ ")"
sealed abstract class Expr
case class Add(e1: Expr, e2: Expr) extends Expr
case class Sub(e1: Expr, e2: Expr) extends Expr
case class Mul(e1: Expr, e2: Expr) extends Expr
case class Div(e1: Expr, e2: Expr) extends Expr
case class Number(e: String) extends Expr
}
Slednji princip uporabimo tudi mi, saj želimo, da je rezultat razpoznavanja le napolnjen
semantični model, nad katerim kasneje pretežno s pomočjo ujemanja vzorcev vršimo
normalizacijo.
5.4.2 Implementacija
Razpoznavalnik, ki smo ga implementirali, deduje od značilnosti RegexParser, ki
omogoča uporabo regularnih izrazov in uporabo znakovnih literalov. Definira namreč
implicitno konverzijo iz omenjenih dveh tipov v pripadajoča razpoznavalnika. Samo
implementacijo bomo predstavili po korakih, celotna izvorna koda pa se nahaja v prilogah
in sicer v datoteki FDLParser.scala. Poglejmo si torej prvo metodo, ki definira samo
lastnost:
private def feature = "[a-zA-Z][a-zA-Z0-9-]*".r ^^ (AtomicFeature(_))
![Page 64: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/64.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 52
Lastnost podamo s pomočjo regularnega izraza, kar označuje klic metode r nad znakovnim
literalom "[a-zA-Z][a-zA-Z0-9-]*". Sestavljena je torej iz malih ter velikih tiskanih črk
in številk minimalne dolžine enega znaka, pri čemer se mora le ta obvezno začeti z malo
ali veliko (sestavljena lastnost) tiskano črko. Ravnokar smo definirali prvi razpoznavalnik.
Z dedovanjem RegexParser smo namreč podedovali implicitno metodo implicit def
regex(r: Regex): Parser[String], ki samodejno pretvori regularni izraz v pripadajoč
razpoznavalnik. Sledi kombinator ^^, ki v primeru, da je bil razpoznavalnik na levi strani
uspešen, rezultatu le tega priredi funkcijo na desni strani. V našem primeru je funkcija kar
objekt AtomicFeature. Tako je rezultat našega razpoznavalnika razpoznavalnik, katerega
rezultat, če bo le ta uspešen, bo atomarna lastnost AtomicFeature z razpoznanim imenom.
Ostale definicije, ki jih bomo videli v nadaljevanju, so v bistvu zelo podobne. Med drugim
pa tudi sama sintaksa, ki je zelo podobna zgoraj opisani, pripomore k samemu
razumevanju pravil:
private def optFeature: Parser[FeatureExp] =
"opt"~"(" ~> featureExp <~ ")" ^^ (Opt(_))
private def list: Parser[List[FeatureExp]] = rep1sep(featureExp,",")
private def all = "all"~"(" ~> list <~ ")" ^^ (All(_:_*))
private def oneOf = "one-of"~"(" ~> list <~ ")" ^^ (OneOf(_:_*))
private def moreOf = "more-of"~"(" ~> list <~ ")" ^^ (MoreOf(_:_*))
private def featureExp = all | oneOf | moreOf | optFeature | feature
private def featureDef = feature ~ ":" ~ featureExp ^^ {
case feature ~ ":" ~ featureExp =>
(Symbol(feature.toString()), featureExp)
}
private def constraint = userConstraint | diagramConstraint
private def userConstraint = (
"include" ~> feature ^^ (Include(_))
| "exclude" ~> feature ^^ (Exclude(_))
)
private def diagramConstraint = (
feature ~ "requires" ~ feature ^^ {
case feature1 ~ "requires" ~ feature2
=> Requires(feature1, feature2) } |
feature ~ "excludes" ~ feature ^^ {
case feature1 ~ "excludes" ~ feature2
=> Excludes(feature1, feature2) }
)
def featureDiagram = (rep1(featureDef)) ~ rep(constraint)
Kot vidimo za celotno implementacijo razpoznavalnika ni bilo potrebno pretirano veliko
izvorne kode. Na primeru je viden deklarativni pristop, kar pomeni, da ne opisujemo
![Page 65: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/65.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 53
postopka kako razpoznati vhod v program, ampak podamo samo deklaracije oziroma
navodila skladna z domensko specifičnim jezikom.
![Page 66: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/66.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 54
6 MERITVE IN PRIMERJAVE
V naslednjih dve podpoglavjih prikažemo postopek štetja števila vrstic potrebnih za
implementacijo, prav tako pa predstavimo časovno zmogljivost oziroma čas potreben za
normalizacijo diagrama lastnosti. Dobljene rezultate nato primerjamo z rezultati iz članka
[10].
6.1 Primerjava števila vrstic implementacij
Za štetje števila vrstic je bilo uporabljeno orodje imenovano CLOC, ki ga lahko dobimo na
spletnem naslovu: http://cloc.sourceforge.net/. Orodje zaganjamo iz ukazne
vrstice. Ker je celotna implementacija domensko specifičnega jezika v našem primeru v
istem paketu oziroma mapi, lahko orodje iz le-te zaženemo takole cloc -by-file *.
Rezultat klica je:
4 text files.
4 unique files.
0 file ignored.
http://cloc.sourceforge.net v 1.55 T=0.5 s (8.0 files/s, 668.0 lines/s)
-------------------------------------------------------------------------
File blank comment code
-------------------------------------------------------------------------
FDLManager.scala 27 11 132
FDLProgram.scala 13 2 72
FDLSemanticModel.scala 11 0 30
FDLParser.scala 6 2 28
-------------------------------------------------------------------------
SUM: 57 15 262
-------------------------------------------------------------------------
Kot vidimo iz zgornjega izpisa rešitev vsebuje 57 praznih vrstic, 15 komentarjev in 262
vrstic kode. Zgornja tabela prikazuje metriko LOC (ang. Lines Of Code). Za nas pa je,
zaradi primerjave z rezultati članka [10], bolj zanimiva metrika eLOC (ang. Effective Lines
Of Code). Efektivno število vrstic je v bistvu enako številu vrstic LOC, pri čemer
odštejemo še vse vrstice, ki vsebujejo le en oklepaj. Samo orodje CLOC takšnega štetja
![Page 67: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/67.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 55
privzeto žal ne upošteva, zato je potrebno spremeniti privzeto definicijo programskega
jezika, ki smo jo shranili v ScalaELOC.txt:
Scala
filter remove_matches ^\s*//|^\s*}\s*$|^\s*{\s*$
filter remove_inline //.*$
filter call_regexp_common C
extension scala
3rd_gen_scale 4.10
Po zagonu
cloc -by-file --read-lang-def=ScalaELOC.txt *
torej z uporabo nove definicije, dobimo željeno eLOC statistiko:
5 text files.
5 unique files.
1 file ignored.
http://cloc.sourceforge.net v 1.55 T=0.5 s (8.0 files/s, 668.0 lines/s)
-------------------------------------------------------------------------
File blank comment code
-------------------------------------------------------------------------
FDLManager.scala 27 40 103
FDLProgram.scala 13 17 57
FDLParser.scala 6 5 25
FDLSemanticModel.scala 11 6 24
-------------------------------------------------------------------------
SUM: 57 68 209
-------------------------------------------------------------------------
Kot vidimo celotno rešitev sestavlja le 209 efektivnih vrstic programske kode. Zgornji
rezultat ne vsebuje implicitnih konverzij, ki jih znotraj datoteke packet.scala vključimo
v paket, kjer želimo, da te veljajo, kar nanese dodatnih 5 vrstic. Omenjene vrstice sicer
prav tako upoštevamo pri sami primerjavi. Skupno je torej potrebno 214 vrstic.
Poudariti je potrebno, da rešitev vsebuje dva pristopa, in sicer vgrajen pristop ter pristop
prevajalnik/tolmač. Število efektivnih vrstic za vgrajen pristop je tako 173, medtem ko je
za pristop prevajalnik/tolmač potrebnih 190 efektivnih vrstic.
Na grafu 6-1 so prikazani rezultati števila efektivnih vrstic različnih implementacij
domensko specifičnega jezika FDL iz članka [10], dodana sta le stolpca, ki prikazujeta
število vrstic naše implementacije v programskem jeziku Scala. Za nas so torej zanimivi
poudarjeni stolpci na grafu, ki predstavljajo število potrebnih vrstic za implementacijo
vgrajenega pristopa in pristopa prevajalnik/tolmač.
![Page 68: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/68.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 56
Graf 6-1: eLOC iz primerjanega članka
Razvidno je, da sta obe implementaciji v programskem jeziku Scala najkrajši. Iz grafa 6-3
lahko razberemo, da je pri vgrajenem pristopu rešitev v programskem jeziku Scala 121
vrstic krajša kakor najkrajša implementacija programskega jezika FDL s pomočjo
programskega jezika Haskell v sklopu članka [10]. Večja razlika se pokaže pri pristopu
prevajalnik/tolmač, kjer je rešitev v programskem jeziku Scala kar 1325 vrstic krajša od
rešitve v programskem jeziku Java, kar je prikazano na grafu 6-3.
0
500
1000
1500
2000
2500
Java Lisa Haskell C++ Haskell Scala Java Scala Lisa SmaCC C# XML
Semantika 1283 1316 578 1482 294 173 1515 165 959 823 618 1383
Sintaksa 512 44 98 0 0 0 512 25 48 29 103 0
eLO
C
Izvorna-v-izvorno kodo
Makro procesiranje
Vgrajeni pristop
Prevajalnik /
tolmač Razširjen prevajalnik
COTS Generator prevajalnika
![Page 69: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/69.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 57
Graf 6-2: Število vrstic implementacije – vgrajeni pristop
Graf 6-3: Število vrstic implementacije – pristop prevajalnik/tolmač
173
294
Skupno št. Vrstic
Scala Haskell
190
1515
Skupno št. Vrstic
Scala Java
![Page 70: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/70.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 58
6.2 Primerjava časovne zmogljivosti
Poglejmo si najprej rezultate meritev časovnih zmogljivosti pridobljenih v [10]. Rezultate
prikazuje tabela 5.
Tabela 5: Rezultati časovnih zmogljivosti iz primerjalnega članka
Pristop Jezik Čas
Izvorna-v-izvorno kodo Java 7min 41s 021ms
Lisa 5min 23s 075ms
Haskell 8s 980ms
Makro procesiranje C++ 57s 887ms
Vgrajeni pristop Haskell 5s 756ms
Prevajalnik/tolmač Java 7min 30s 951ms
Generator prevajalnika LISA 5min 49s 902ms
SmaCC 7min 59s 257ms
Razširjeni prevajalnik C# 119ms
COTS XML N/A
Naj povemo, da je bil FDL program namenjen testiranju sestavljen iz 20 vrstic, tvoril pa je
11.880 konfiguracij. Vse meritve so bile opravljene na istem računalniku, prevedene z
privzetim prevajalnikom brez dodatnih parametrov. Vsaka rešitev je bila zagnana iz ukazne
vrstice v povprečju trikrat. Kot vidimo v hitrosti izvajanja krepko prednjači razširjeni
prevajalnik programskega jezika C#, za njim Haskell ter nato C++. Ostale rešitve so, lahko
bi rekli, nekonkurenčne v smislu časovne zmogljivosti. Najboljše bi bilo preizkusiti rešitev
napisano v programskem jeziku Scala pod enakimi pogoji oziroma na enak način. Težava
je ta, da nismo imeli na voljo enake strojne opreme oziroma računalnika, na katerem so bili
zagnani testi v sklopu članka. Meritve smo zato opravili ponovno za:
Razširjeni prevajalnik C# - (časovno najzmogljivejša rešitev)
Vgrajeni pristop implementiran v programskem jeziku Haskell
Vgrajeni pristop implementiran v programskem jeziku Scala
Pristop prevajalnik/tolmač v programskem jeziku Java
Pristop prevajalnik tolmač v programskem jeziku Scala
V primerjavo sta torej vključena enaka pristopa naši implementaciji v programskem jeziku
Scala ter najhitrejša rešitev implementacije domensko specifičnega jezika FDL v članku
[10]. FDL, ki je služil kot testni primer, je zajemal 15552 konfiguracij. Program smo
zagnali na enak način kot v članku [10], v povprečju trikrat za vsako izmed implementacij.
Izmerjene vrednosti prikazuje tabela 6.
![Page 71: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/71.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 59
Tabela 6: Časovna zmogljivost implementacij
Pristop Jezik Čas
Vgrajeni pristop Scala 1600 ms
Haskell 2860 ms
Prevajalnik/tolmač Scala 1790 ms
Java 22:20 min
Razširjeni prevajalnik C# 90ms
Čase smo zaokrožili na 10 ms. Kot vidimo Scala prednjači v obeh pristopih. Rezultati
vgrajenega pristopa programskih jezikov Scala in Haskell so precej podobni. Bolj izstopa
rezultat pristopa prevajalnik/tolmač, kjer je razlika precejšnja.
![Page 72: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/72.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 60
7 ZAKLJUČEK
V uvodu smo na kratko predstavili splošno namenski programski jezik Scala ter domensko
specifični jezik FDL.
V naslednjih dveh poglavjih smo opisali splošno namenske jezike, katerih predstavnik je
programski jezik Scala in domensko specifične jezike, med katere spada tudi jezik FDL, ki
je bil implementiran s pomočjo dveh pristopov v programskem jeziku Scala. Prav tako smo
opisali prednosti in slabosti rabe domensko specifičnih jezikov ter možne načine
implementacije.
Sledilo je poglavje, ki je bilo v celoti namenjeno programskemu jeziku Scala. Opisali smo
njen nastanek, splošne značilnosti ter postopek za samo namestitev vsega potrebnega za
razvoj.
Implementacijo domensko specifičnega jezika FDL v Scali smo opisali v naslednjem
poglavju, katerega podnaslovi ustrezajo časovnemu zaporedju implementacije. Vsakega
izmed pomembnejših gradnikov implementacije smo podrobneje opisali, prav tako pa smo
opisali samo implementacijo le-tega. Tako smo najprej predstavili sam semantični model,
v nadaljevanju smo opisali algebro diagramov lastnosti semantičnega modela, sledili pa sta
podpoglavji, namenjeni vgrajenemu pristopu in pristopu prevajalnik/tolmač.
V predzadnjem poglavju smo primerjali opravljene meritve, ki so zajemale primerjavo
števila vrstic ter primerjavo časovne zmogljivosti.
Predpostavko, da je programski jezik Scala nadvse primeren za implementacijo domensko
specifičnih jezikov, smo potrdili. Scala ponuja veliko možnosti v smislu principov,
knjižnic ter same izrazne moči, ki še kako olajšajo implementacijo tako vgrajenih kot tudi
drugih pristopov. V sklopu diplomskega dela smo tako s pomočjo programskega jezika
Scala uspeli izdelati krajši ter hitrejši rešitvi v primerjavi z implementacijama enakih
pristopov v članku [10]. Gre za vgrajen pristop in pristop prevajalnik/tolmač. Glede na to,
da se Scala neprenehoma razvija, lahko v prihodnosti zagotovo pričakujemo še več orodij,
![Page 73: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/73.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 61
v smislu programskih konstruktov, ki bodo samo implementacijo domensko specifičnih
jezikov še olajšala.
![Page 74: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/74.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 62
LITERATURA
1. K. W. Beyer, Grace Hopper and the Invention of the Information Age, MIT
Press, 2009, str. 9
2. A. Deursen, P. Klint, J. Visser, Domain-Specific Languages: An Annotated
Bibliography, 2000
3. A. Deursen, P. Klint, Domain specific language design requires feature
descriptions, J. Comput. Inform. Tech 10, CWI, 2002
4. G. Karsai, H. Krahn, C. Pinkernell, B. Rumpe, M. Schindler, S. Völkel, Design
Guidelines for Domain Specific Languages, Vanderbilt University, USA,
Department of Computer Science, Germany, 2006
5. M. Fowler, R. Pearsons, Domain-Specific Languages, Addison-Wesley, 2010
6. B. Ford, Parsing Expression Grammars: A Recognition-Based Syntactic
Foundation, Massachusetts Institute of Technology Cambridge, 2004
7. B. Emir, M. Odersky, J. Williams, Matching Objects With Patterns, EPFL,
Switzerland, Language Computer Corporation, Richardson, 2007
8. M. Odersky, Lex Spoon, Bill Venners, Programming in Scala, Second Edition,
Aritma, 2011
9. M. Rüegg, Pattern Matching in Scala, University of Applied Sciences
Rapperswil, 2009
10. T. Kosar, P. E. Martínez López, P. A. Barrientos, M. Mernik, A preliminary study
on various implementation approaches of domain-specific language, Information
and Software Technology 50, (2008), str. 390-405
11. P. Haller, M. Odersky, Scala actors: Unifying thread-based and event-based
programming, Theoretical Computer Science, 2008, str. 3
12. P. Hudak, Domain-specific languages, Department of Computer ScienceYale
University, 1997
13. E. Labun, Masterarbeit, Combinator Parsing in Scala, ISA, 2012
14. K. C. Louden, K. A. Lambert, Programming Languages Principles and
Practises, Third Edition, Course Technology, 2011, str. 3-4
15. M. Mernik, J. Heering, and A. M. Sloane, When and how to develop domain-
specific languages, University of Maribor, Macquarie University, 2003
![Page 75: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/75.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala Stran 63
16. J. E. Sammet, Programming Languages: History and Future, IBM Corporation,
1972
17. J. Sprinkle, G. Karsai, A Domain-Specific Visual Language For Domain Model
Evolution, Vanderbilt University, 2004
18. M. V. Wilkes, Computers Then and Now, Cambridge University, 1967
19. V. Žumer, J. Brest, Objektno programiranje v C++, Fakulteta za elektrotehniko,
računalništvo in informatiko, Inštitut za računalništvo, Maribor, 2001, str. 245-
246
20. Spletni naslov: http://www.artima.com/scalazine/articles/origins_of_scala.html,
The Origins of Scala, A Conversation with Martin Odersky, Part I, by Bill
Venners and Frank Sommers, 2009
21. Spletni naslov: http://www.scala-lang.org/node/1658, Scala in Enterpries, 2012
22. Spletni naslov: http://www.scala-lang.org/node/258, Scala in Enterpries, 2012
![Page 76: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/76.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
Naslov študenta
Primož Kokol
Spuhlja 73
2250 Ptuj
Tel.: 041 394 122
E-pošta: [email protected]
Kratek življenjepis
Rojen: 5.4.1984
Šolanje: 1990-1998 Osnovna šola Mladika
1999-2003 Elektro in računalniška šola Ptuj
2003-2010 Fakulteta za elektrotehniko, računalništvo in informatiko v
Mariboru, smer programska oprema, univerzitetni študijski
program
![Page 77: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/77.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
![Page 78: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/78.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
![Page 79: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/79.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
![Page 80: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/80.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
PRILOGA A
Implementacija je že opisana v samem diplomskem delu, zato izpis izvorne kode ne vsebuje
komentarjev.
FDLSemanticModel.scala
package si.kokol.fdl
sealed abstract class FeatureExp
abstract class FeatureExpList() extends FeatureExp { val features: List[FeatureExp]
}
case class AtomicFeature(name: String) extends FeatureExp {
override def toString(): String = {name}
def requires(feature: AtomicFeature): Constraint = Requires(this, feature)
def excludes(feature: AtomicFeature): Constraint = Excludes(this, feature)
}
case class Opt(feature: FeatureExp) extends FeatureExp {
override def toString(): String = "opt(" + feature.toString() + ")"
}
case class All(features: List[FeatureExp]) extends FeatureExpList{
override def toString(): String = "\nall("+features.mkString(",")+")"
}
case class OneOf(features: List[FeatureExp]) extends FeatureExpList {
override def toString(): String = "one-of("+features.mkString(",")+")"
}
case class MoreOf(features: List[FeatureExp]) extends FeatureExpList {
override def toString(): String = "more-of("+features.mkString(",")+")"
}
object All{def apply(features: FeatureExp*) = new All(features.toList)}
object OneOf{def apply(features: FeatureExp*) = new OneOf(features.toList)}
object MoreOf{def apply(features: FeatureExp*) = new MoreOf(features.toList)}
sealed abstract class Constraint
case class Include(atomicFeature: AtomicFeature) extends Constraint
case class Exclude(atomicFeature: AtomicFeature) extends Constraint
case class Requires(atomicFeature1: AtomicFeature, atomicFeature2: AtomicFeature) extends Constraint
case class Excludes(atomicFeature1: AtomicFeature, atomicFeature2: AtomicFeature) extends Constraint
FDLManager.scala
package si.kokol.fdl
import scala.collection.mutable.ListBuffer
object FDLManager {
def normalize(feature: FeatureExp): FeatureExp = feature match {
case a@AtomicFeature(_) => a
case Opt(opt@Opt(_)) => normalize(opt)
case Opt(feature) => normalizeOpt(feature)
case All(list) => normalizeAll(flatten[All](normalizeList(list map normalize)))
case OneOf(list) => normalizeOneOf(flatten[OneOf](normalizeList(list map normalize)))
case MoreOf(list) => normalizeMoreOf(flatten[MoreOf](normalizeList(list map normalize)))
}
private def normalizeOpt(feature: FeatureExp) = {
normalize(feature) match {
case opt@Opt(feature) => opt
case feature => Opt(feature)
}
}
private def normalizeList(featureList: List[FeatureExp]): List[FeatureExp] = {
normalizationRule2(featureList).distinct;
}
private def normalizationRule2(featureList: List[FeatureExp]): List[FeatureExp] = featureList filter {
case Opt(feature) => !featureList.contains(feature)
case _ => true
}
private def flatten[T <: FeatureExpList: Manifest](list: List[FeatureExp]): List[FeatureExp] = list flatMap {
case all: All if manifest[T] <:< manifest[All] => all.features
case oneOf: OneOf if manifest[T] <:< manifest[OneOf] => oneOf.features
case moreOf: MoreOf if manifest[T] <:< manifest[MoreOf] => moreOf.features
case other => List(other)
}
private def normalizeAll(features: List[FeatureExp]): FeatureExp = features match {
case List(oneFeature) => oneFeature
case list => All(list)
}
![Page 81: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/81.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
private def normalizeOneOf(features: List[FeatureExp]): FeatureExp = features match {
case List(oneFeature) => oneFeature
case list => if (list.exists(_.isInstanceOf[Opt])) {
Opt(OneOf(removeOptFeatures(list)))
} else {
OneOf(list)
}
}
private def normalizeMoreOf(features: List[FeatureExp]): FeatureExp = features match {
case List(oneFeature) => oneFeature
case list => if (list.exists(_.isInstanceOf[Opt])) {
Opt(MoreOf(removeOptFeatures(list)))
} else {
MoreOf(list)
}
}
private def removeOptFeatures(list: List[FeatureExp]): List[FeatureExp] = list flatMap {
case Opt(elem) => List(elem)
case elem => List(elem)
}
def expand(feature: FeatureExp): FeatureExp = {
val oneOfList = ListBuffer[FeatureExp]()
expand(feature)
def expand(feature: FeatureExp): Unit ={
feature match {
case a@AtomicFeature(_) => a
case Opt(feature) => expand(feature)
case All(list) => expandRules(list)
case OneOf(list) => list map expand
case MoreOf(list) => list map expand
}
}
def expandRules(features: List[FeatureExp]): Unit =
{
val featuresNorm = features flatMap {
case OneOf(oList@List(element)) => (oList)
case MoreOf(mList@List(element)) => (mList)
case All(list) => list
case other => List[FeatureExp](other)
}
featuresNorm match {
case list if list.exists(_.isInstanceOf[Opt]) => (expandRule1Rule2(list) map expand)
case list if list.exists(_.isInstanceOf[OneOf]) => (expandRule3(list) map expand)
case list if list.exists(_.isInstanceOf[MoreOf]) => (expandRule4(list) map expand)
case list if list.exists(_.isInstanceOf[All]) => expandRules(list)
case list => oneOfList.append(All(list))
}
}
def expandRule1Rule2(featureList: List[FeatureExp]): List[FeatureExp] = featureList match {
case Nil => Nil
case Opt(head) :: tail => List(All(head::tail), All(tail))
case head :: tail => expandRule1Rule2(tail :+ head)
}
def expandRule3(featureList: List[FeatureExp]): List[FeatureExp] = featureList match {
case Nil => Nil
case OneOf(head) :: tail => List(All((head.head)::tail), All(OneOf(head.tail)::tail))
case head :: tail => expandRule3(tail :+ head)
}
def expandRule4(featureList: List[FeatureExp]): List[FeatureExp] = featureList match {
case Nil => Nil
case MoreOf(head) :: tail => List( All((head.head)::tail),
All((head.head)::MoreOf(head.tail)::tail),
All(MoreOf(head.tail)::tail))
case head :: tail => expandRule4(tail :+ head)
}
OneOf(oneOfList.toList)
}
def sat(featureExp: FeatureExp, constraints: List[Constraint]): FeatureExp =
{
val sat2Call = sat(_: FeatureExp,constraints)
featureExp match {
case OneOf(list) => OneOf((list map sat2Call).filterNot(_==All(Nil)) )
case All(list) => if (satisfy(list, constraints)) All(list) else All(Nil)
case _ => throw new RuntimeException("Not normalized form!!")
}
}
private def satisfy(featuresList: List[FeatureExp], constraints: List[Constraint]): Boolean = {
var satisfy=true;
constraints.foreach(_ match {
case Include(a) => if (!featuresList.contains(a)) satisfy=false
case Exclude(a) => if (featuresList.contains(a)) satisfy=false
case Requires(a,b) => if (featuresList.contains(a) && !featuresList.contains(b)) satisfy=false
case Excludes(a,b) => if (featuresList.contains(a) && featuresList.contains(b)) satisfy=false
}
)
satisfy
}
def variability(feature: FeatureExp): Long = feature match {
![Page 82: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/82.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
case AtomicFeature(_) => 1
case Opt(exp) => variability(exp) + 1
case All(List(exp)) => variability(exp)
case All(head :: tail) => variability(head) * variability(All(tail))
case OneOf(List(exp)) => variability(exp)
case OneOf(head :: tail) => variability(head) + variability(OneOf(tail))
case MoreOf(List(exp)) => variability(exp)
case MoreOf(head :: tail) => variability(head) + ((variability(head)+1) * variability(MoreOf(tail)))
case _ => 0
}
}
FDLParser.scala
package si.kokol.fdl
import scala.util.parsing.combinator.RegexParsers
object FDLParser extends RegexParsers {
private def feature = "[a-zA-Z][a-zA-Z0-9-]*".r ^^ (AtomicFeature(_))
private def optFeature: Parser[FeatureExp] = "opt"~"(" ~> featureExp <~ ")" ^^ (Opt(_))
private def list: Parser[List[FeatureExp]] = rep1sep(featureExp,",")
private def all = "all"~"(" ~> list <~ ")" ^^ (All(_:_*))
private def oneOf = "one-of"~"(" ~> list <~ ")" ^^ (OneOf(_:_*))
private def moreOf = "more-of"~"(" ~> list <~ ")" ^^ (MoreOf(_:_*))
private def featureExp = all | oneOf | moreOf | optFeature | feature
private def featureDef = feature ~ ":" ~ featureExp ^^ {
case feature ~ ":" ~ featureExp => (Symbol(feature.toString()), featureExp)
}
private def constraint = userConstraint | diagramConstraint
private def userConstraint = (
"include" ~> feature ^^ (Include(_))
| "exclude" ~> feature ^^ (Exclude(_))
)
private def diagramConstraint = (
feature ~ "requires" ~ feature ^^ {
case feature1 ~ "requires" ~ feature2 => Requires(feature1, feature2)
} |
feature ~ "excludes" ~ feature ^^ {
case feature1 ~ "excludes" ~ feature2 => Excludes(feature1, feature2)
}
)
def featureDiagram = (rep1(featureDef)) ~ rep(constraint)
}
FDLProgram.scala
package si.kokol.fdl
import java.io.FileReader
import java.io.StringReader
import scala.collection.mutable.ListBuffer
import scala.collection.mutable.LinkedHashMap
class FDLProgram protected (featureDefinitions: Pair[Symbol, FeatureExp]*)(constraintsArgs: Constraint*) {
private val featuresDefinitionMap = LinkedHashMap(featureDefinitions.toList:_*)
val constraints = constraintsArgs.toList
lazy val featureExp = substituteVariables()
lazy val featureExpNormalized = FDLManager.normalize(featureExp)
lazy val featureExpExpanded = FDLManager.expand(featureExpNormalized)
lazy val featureExpConstrained = if (constraints.length > 0) {
FDLManager.sat(featureExpExpanded, constraints)
} else
featureExpExpanded
lazy val featureVar = FDLManager.variability(featureExpNormalized)
lazy val featureVarConstarined = FDLManager.variability(featureExpConstrained)
private def getFirsExp(): FeatureExp = {
featuresDefinitionMap.headOption match {
case Some(feature) => featuresDefinitionMap.remove(feature._1); feature._2
case None => throw new IllegalArgumentException("Empty feature definition!")
}
}
private def substituteVariables(): FeatureExp = {
val mainFeature = getFirsExp()
substitute(mainFeature)
}
private def substitute(feature: FeatureExp): FeatureExp = feature match {
case AtomicFeature(featureName) if isCompositeFeature(featureName) =>
featuresDefinitionMap.get(Symbol(featureName)) match {
case Some(feature) => substitute(feature)
case None => throw new IllegalArgumentException("Feature definition " + featureName + " not found!")
}
case af@AtomicFeature(_) => af
case Opt(featureExp) => Opt(substitute(featureExp))
case All(list) => All(list map substitute)
case OneOf(list) => OneOf(list map substitute)
case MoreOf(list) => MoreOf(list map substitute)
}
![Page 83: Gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela delitev na domensko specifične in splošno namenske jezike vseh programskih jezikov v](https://reader031.vdocuments.net/reader031/viewer/2022011814/5e48b900a33882622f2f9b8a/html5/thumbnails/83.jpg)
Gradnja domensko specifičnih jezikov s programskim jezikom Scala
private def isCompositeFeature(feature: String): Boolean = {
Character.isUpperCase(feature(0))
}
}
object FDLProgram {
def apply(featureDefinitions: Pair[String, FeatureExp]*)(constraintsArgs: Constraint*): FDLProgram = {
val symbolsList = new ListBuffer[Pair[Symbol, FeatureExp]]()
for(feature <- featureDefinitions) {
symbolsList.append((Symbol(feature._1), feature._2))
}
new FDLProgram(symbolsList.toList:_*)(constraintsArgs:_*)
}
def apply(feature: FeatureExp): FDLProgram = {
val symbolsList = new ListBuffer[Pair[Symbol, FeatureExp]]()
symbolsList.append((Symbol("MOCK"), feature))
new FDLProgram(symbolsList.toList:_*)(Nil:_*)
}
def apply(reader: FileReader): FDLProgram = {
val test = FDLParser.parseAll(FDLParser.featureDiagram, reader)
test match {
case FDLParser.Success(result, _) =>new FDLProgram(result._1:_*)(result._2:_*)
case [email protected](_, _) => println(b); throw new IllegalArgumentException(b.toString())
}
}
def apply(fdlToParse: String): FDLProgram = {
val reader = new StringReader(fdlToParse)
val test = FDLParser.parseAll(FDLParser.featureDiagram, reader)
test match {
case FDLParser.Success(result, _) =>new FDLProgram(result._1:_*)(result._2:_*)
case [email protected](_, _) => println(b); throw new IllegalArgumentException(b.toString())
}
}
}