gradnja domensko specifičnih jezikov s · 2017-11-27 · razumevanje vzrokov, da črno-bela...

83
Primož Kokol Gradnja domensko specifičnih jezikov s programskim jezikom Scala Diplomsko delo Maribor, julij 2012

Upload: others

Post on 02-Feb-2020

2 views

Category:

Documents


0 download

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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())

}

}

}