TDP007 Konstruktion av datorspråk
Seminarie 3: Parsning och Domänspecifika språk
Detta seminarium behandlar parsning av språk och domänspecifika språk. Parsning behandlar hur man bryter ner ett formellt språk i dess beståndsdelar för att sedan kunna tolka uttryck skrivna i språket. Domänspecifika språk är språk som är specifika för ett visst ändamål och som ofta är implementerade genom att man använder sig av ett grundspråk som utökas för ändamålet. Sättet man utökar språket på beror på språket. I fallet med Ruby så kan vi omdefiniera vad som händer när okända konstanter eller metoder hittas och därmed utöka språket. I Lisp och liknande språk så kan man manipulera uttryck som är syntaktiskt korrekta på en basal nivå så att de skrivs om till uttryck som känns igen av språkets programtolk (interpretatorn, motsvarande
irb
).
Att tänka på
Nu blir det allt viktigare att ni dokumentera ordentligt vad ni gjort och, när ni opponerar, att ni läser igenom vad den andra gruppen gjort. Arbetet med uppgifterna kommer att innebära att ni behöver analysera befintlig kod, felsöka, skriva tester och dokumentera era frågor ordentligt. Detta kommer utgöra grunden för vår bedömning av era inlämningar.Uppgift 1: Domänspecifika språk
Introduktion
I denna uppgift ska ni skapa ett domänspecifikt språk för att uttrycka hur man beräknar försäkringspremien för en bilförsäkring. Ni ska först bestämma hur språket ska se ut. Det ska vara ett internt DSL i Ruby, d.v.s. den grundläggande syntaxen ska se ut som Ruby-kod. Därefter ska ni uttrycka informationen som finns i exemplet nedan i det här språket. Till sist ska ni skapa metoder för att läsa in och tolka den här informationen och på så sätt beräkna försäkringspremien för en person. Tanken är att det ska funka ungefär så här:
>> kalle=Person.new("Volvo","58435",2,"M",32) => #<Person:0x29bc890 ...> >> kalle.evaluate_policy("policy.rb") => 15.66
Exempel att utgå från
I vårt försäkringsbolag baseras premien för bilförsäkringen på ett antal olika faktorer. Dessa vägs samman och ger ett säkerhetsbetyg. Ju högre säkerhetsbetyg man får, desto lägre blir premien. I det här exemplet ska vi enbart beräkna säkerhetspoängen. Detta görs i två steg.
Först räknar vi ut en grundpoäng som baserar sig på ett antal olika tabeller. Det beror på bilmärke, var man bor (postnummer), hur många år man haft körkort, kön och ålder. Tabellerna ser ut så här:
Bilmärke | Poäng |
---|---|
BMW | 5 |
Citroen | 4 |
Fiat | 3 |
Ford | 4 |
Mercedes | 5 |
Nissan | 4 |
Opel | 4 |
Volvo | 5 |
Postnummer | Poäng |
---|---|
58937 | 9 |
58726 | 5 |
58647 | 3 |
Körkort antal år | Poäng |
---|---|
0-1 år | 3 |
2-3 år | 4 |
4-15 år | 4,5 |
16-99 år | 5 |
Kön | Poäng |
---|---|
Kvinna | 1 |
Man | 1 |
Ålder | Poäng |
---|---|
18-20 år | 2,5 |
21-23 år | 3 |
24-26 år | 3,5 |
27-29 år | 4 |
30-39 år | 4,5 |
40-64 år | 5 |
65-70 år | 4 |
71-99 år | 3 |
(I just det här försäkringsbolaget hade man förut olika premier för män och kvinnor, men det har man inte längre, åtminstone inte i tabellerna.)
I nästa steg höjer eller sänker man grundpoängen baserat på speciella regler. I det här försäkringsbolaget har man två sådana regler
- För män som haft körkort mindre än tre år multipliceras säkerhetspoängen med 0.9.
- För personer som äger Volvo och som bor i ett postnummerområde som börjar med 58 (större delen av Östergötland) multipliceras säkerhetspoängen med 1.2.
Er uppgift är alltså att hitta på ett sätt att uttrycka tabellerna och reglerna. Ni ska forma ett eget domänspecifikt språk som dels följer grundsyntaxen i Ruby, dels är så enkelt att personalen som jobbar på försäkringsbolaget ska kunna ändra i specifikationen om det behövs.
Vad är det som ska hända?
Tanken är att när ni är klara ska ni kunna skriva ungefär så här:
>> kalle=Person.new("Volvo","58435",2,"M",32) => #<Person:0x29bc890 ...> >> kalle.evaluate_policy("policy.rb") => 15.66
I det första anropet skapar ni ett nytt objekt som innehåller information om en person. Han har en Volvo, bor i postnummerområde 58435, har haft körkort i två år, är man och 32 år. Den DSL-kod som uttrycker företagets regler finns i filen policy.rb. Metoden evaluate_policy läser in och tolkar denna kod som ett DSL, samtidigt som säkerhetspoängen räknas ut. I det här exemplet blir det 15.66.
Om ni vill får ni gärna hitta på alternativa tabeller och regler som är ungefär lika komplicerade. Tanken är att ni ska göra en så generell lösning ni hinner med.
Uppgift 2: Parsning
Ni ska använda er av en befintlig parser som ni hittar i filen rdparse.rb. Längst ner i filen hittar ni ett exempel på hur parsern kan instansieras med grammatiska regler och kod för att evaluera uttryck så att vi kan hantera ett språk för att rulla tärningar.Er uppgift blir att implementera ett språk för enkla logiska uttryck med hjälp av den befintliga parsern. Språket har följande grammatiska specifikation:
VALID ::= ASSIGN VALID ::= EXPR ASSIGN ::= '(' 'set' VAR EXPR')' <-- Tilldelning av variabel EXPR ::= '(' 'or' EXPR EXPR ')' EXPR ::= '(' 'and' EXPR EXPR ')' EXPR ::= '(' 'not' EXPR ')' EXPR ::= TERM TERM ::= VAR <-- Variabel TERM ::= 'true' TERM ::= 'false'
VAR
ska beteckna en variabel som ni själva får bestämma
de syntaktiska reglerna för och VALID
-reglerna beskriver
vilka uttryck som ska vara tillåtna i språket. Resultatet av att läsa
in ett uttryck ska motsvara det förväntade resultat ni fått om ni
skrivit motsvarande uttryck direkt i Ruby. Ta gärna inspiration av hur
DiceRoller
-klassen fungerar för att se hur man kan
implementera språket.
Sidansvarig: Pontus Haglund
Senast uppdaterad: 2025-01-20