Handleiding bij Practicum Theo Ruys

advertisement
V ERTALERBOUW
Handleiding bij
Practicum
Theo Ruys
vakcode: 192110352
editie: 2010/2011 kw4
versie: ma 25 april 2011
Naam:
Studierichting:
Docent:
Practicumassistent:
Email assistent:
Groep:
Inhoudsopgave
Inleiding
v
1 Java en Triangle
1.1 Wordcount in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Wordcount in Triangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Symbol Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1
2
3
2 Recursive-Descent Parsing
2.1 Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
9
13
3 ANTLR
3.1 Beginnen met Antlr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
15
4 Codegeneratie
4.1 Nogmaals decluse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 TAM-Assembler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.3 Codegenerator voor Calc . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
21
22
23
5 Triangle Extensies
5.1 Uitbreiden van Triangle . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
25
A Eindopdracht
A.1 Inleiding . . . . . . . . . . . . . . . .
A.1.1 Randvoorwaarden . . . . . . .
A.1.2 Globale indeling weken 7 t/m 12
A.2 Beoordeling . . . . . . . . . . . . . . .
A.3 Verslageisen . . . . . . . . . . . . . .
A.3.1 Programmatuuur . . . . . . . .
A.3.2 Verslag . . . . . . . . . . . . .
A.4 Eindopdrachten . . . . . . . . . . . . .
A.4.1 Basic expression language . . .
A.4.2 Conditional statement . . . . .
A.4.3 While statement . . . . . . . .
A.4.4 Procedures and functions . . . .
A.4.5 Arrays . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
A-1
A-1
A-1
A-2
A-3
A-4
A-5
A-6
A-7
A-8
A-10
A-11
A-11
A-12
iv
Inhoudsopgave
A.4.6
A.4.7
A.5 Testen
A.5.1
A.5.2
A.5.3
A.5.4
A.5.5
A.5.6
A.5.7
Records . . . . . . . . . .
Pointers . . . . . . . . . .
. . . . . . . . . . . . . . .
Basic expression language
Conditional statement . .
While statement . . . . .
Procedures and functions .
Arrays . . . . . . . . . . .
Records . . . . . . . . . .
Pointers . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
A-13
A-14
A-14
A-15
A-19
A-19
A-20
A-21
A-21
A-21
Inleiding
Deze practicumhandleiding beschrijft de invulling van de practica van het vak Vertalerbouw (VB,
vakcode 211035). Vooraf enige informatie over de organisatie van het vak.
B ESCHRIJVING
Doel van het vak Vertalerbouw: het kunnen vertalen en analyseren van structuren die veel voorkomen binnen de informatica, met name in hogere programmeertalen.
Het hoorcollege behandelt het complete traject van de (automatische) vertaling van moderne
(object-georienteerde) programmeertalen naar machinetaal voor een microprocessor. Er wordt
vooral ingegaan op de practische aspecten van het schrijven van een vertaler. Aan de orde komen
onder meer: syntactische analyse, contextuele analyse, run-time organisatie, vertaler generatoren,
code generatie, interpretatie en het afhandelen van fouten. Er wordt gebruik gemaakt van de
programmeertaal Java.
Er is een aparte website voor Vertalerbouw: http://fmt.cs.utwente.nl/courses/vertalerbouw.
Via deze site zal algemene informatie over de organisatie van het vak beschikbaar komen, o.a.
practicumfiles, hoorcollegesheets, opgavenseries, eindopdracht, etc. Er is uiteraard ook een
TeleTOP-pagina voor Vertalerbouw, te bereiken via: http://teletop.utwente.nl/08211035.nsf. Deze
TeleTOP-pagina zal vooral gebruikt worden voor belangrijk nieuws en voor discussies.
O NDERWIJSMATERIAAL
Het onderwijsmateriaal voor Vertalerbouw bestaat uit:
David A. Watt and Deryck F. Brown. Programming Language Processors in Java (Compilers
and Interpreters). John Wiley & Sons, New York, 2002, ISBN 0-471-35489-9. Prentice
Hall/Pearson Education, Harlow, England, 2000. ISBN 0-130-25786-9.
Deze practicumhandleiding (die in delen beschikbaar zal komen via de website van Vertalerbouw).
Materiaal dat tijdens het hoorcollege door de docent beschikbaar wordt gesteld: collegeaantekeningen, programmatuur voor de practica, etc.
Informatie op de website- en TeleTOP-pagina van Vertalerbouw: o.a. links en files.
Het boek en de practicumhandleiding heeft u bij de practica steeds nodig.
v
vi
Inleiding
B EGELEIDING
Verantwoordelijk docent voor het vak Vertalerbouw is
Michael Weber
kamer: Zilverling 5037
telefoon: 3716
email: [email protected]
Hij verzorgt de hoorcolleges en is beschikbaar voor algemene vragen t.a.v. het vak.
Daarnaast wordt elke practicumgroep door een studentassistent begeleid. De studentassistenten zullen tevens de twee opgavenseries van hun eigen practicumgroep corrigeren en ook voorbereidend werk verrichten bij het nakijken van de eindopdracht.
B EOORDELING
Het vak Vertalerbouw wordt beoordeeld d.m.v.
twee opgavenseries die individueel gemaakt moeten worden, en
een practicum waarbij met iemand kan worden samengewerkt.
Als beide beoordelingen met tenminste een 5,0 beoordeeld zijn, wordt het eindcijfer van Vertalerbouw bepaald door het gemiddelde van beide beoordelingen. Echter, als de opgavenseries of
de practicumopdracht met minder dan een 5,0 beoordeeld is, kan het eindcijfer voor Vertalerbouw
nooit hoger dan een 4 zijn.
Waarschuwing! Helaas blijkt (bijna) ieder jaar weer dat sommige studenten de verleiding niet kunnen
weerstaan om werk van anderen te kopiëren. Overeenkomstig artikel 6.10 (Fraude) van de Onderwijs- en
Examenregeling zal voor wie fraude pleegt, het vak Vertalerbouw met het cijfer één worden beoordeeld.
Dit geldt zowel voor degene die kopieert als voor degene die werk beschikbaar stelt om te laten kopiëren.
Tevens zal de examencommissie geı̈nformeerd worden.
Opgavenseries
De leerstof die in het hoorcollege wordt behandeld, wordt getoetst door middel van twee opgavenseries. De opgavenseries vormen een goede manier om met de stof van het vak Vertalerbouw
te oefenen. De opgavenseries dienen zoals gezegd individueel te worden gemaakt. Raadpleeg de
website van Vertalerbouw om te zien wanneer de deadlines voor de opgavenseries zijn; dan kunt
u hier bij de planning van het kwartiel rekening mee houden.
Practicum
Het practicum is verroosterd; aanwezigheid bij het practicum is verplicht. Het practicum bestaat
uit twee delen.
Tijdens het eerste deel van het practicum (eerste vijf weken) wordt geoefend met de stof
uit het hoorcollege, de Triangle compiler en er wordt kennisgemaakt met ANTLR (zie
http://www.antlr.org), een LL(*) vertaler generator. Daarnaast worden er in het eerste deel
van het practicum enkele Java modules ontwikkeld die gebruikt zullen worden in het vervolg
van het practicum.
Inleiding
vii
In het tweede gedeelte (drie weken, met uitloop in de twee tentamenweken) doen de studenten ervaring op met de ontwikkeling van een complete vertaler voor een Pascal-achtige programmeertaal, gebruikmakend van ANTLR. De opdrachtomschrijving van de eindopdracht
en de eisen aan het verslag worden voor aanvang van het tweede deel bekend gemaakt via de
website van Vertalerbouw.
Het practicum wordt gedaan in tweetallen (koppels). Het is niet toegestaan met z’n drieen te
werken; eventueel kan het dus zo uitkomen dat iemand alleen moet werken.
Elke practicumgroep staat onder leiding van een studentassistent. Deze begeleidt de studenten
tijdens het practicum en tekent ook de practicumopgaven af.
Java. Bij het vak Vertalerbouw gebruiken we Java als implementatietaal. Het leerboek voor
Vertalerbouw gebruikt helaas nog een oudere versie van Java, nl. versie 1.4. Sinds Java 5 (aka
1.5) bevat Java echter verschillende features die het programmeren (van een vertaler) aanzienlijk
eenvoudiger maken. Voor de inleidende practica wordt sterk aangeraden om tenminste Java versie
5 te gebruiken. Voor de eindopdracht is het zelfs verplicht om (tenminste) Java 5 te gebruiken.
Herhalers
Deelcijfers voor het vak Vertalerbouw blijven altijd staan. Dit betekent dat als men ooit voor de
(beide!) opgavenseries of de eindopdracht tenminste een 5.0 heeft behaald, men dit onderdeel niet
hoeft te herkansen. Ook als ooit de eerste vijf weken van het practicum met voldoende resultaat
zijn afgesloten, hoeft dit gedeelte van het vak niet te worden overgedaan. Deze regel blijft in ieder
geval van kracht zolang het vak Vertalerbouw door de leerstoel FMT verzorgd wordt.
T IJDSBESTEDING
Vertalerbouw heeft in het studiejaar 2010/2011 een studielast van 5 EC (= 3.5 SP), ofwel 140 uur.
In onderstaande tabel staat een verdeling van deze 140 uur over de studie-onderdelen van het vak.
hoorcolleges (contacturen)
zelfstudie naast de hoorcolleges
practicum blok 1 (contacturen)
voorberereidingen op practicum blok 1
opgavenseries
practicum blok 2 (contacturen)
eindopdracht (buiten practicum)
verslag van de eindopdracht
totaal
9 × 2 uur
7 × 3 uur
5 × 4 uur
5 × 2 uur
2 × 8 uur
3 × 6 uur
5 × 5 uur
=
=
=
=
=
=
=
18 uur
21 uur
20 uur
10 uur
16 uur
18 uur
25 uur
12 uur
140 uur
Zij trouwens opgemerkt dat bovenstaande verdeling slechts een richtlijn is; sommige studenten
zullen met sommige onderdelen meer dan wel minder minder moeite hebben.
viii
Inleiding
Week
1
Java en Triangle
P RACTICUM
Tijdens dit eerste practicum van Vertalerbouw wordt allereerst de Java-kennis een beetje opgefrist.
Daarnaast wordt er geoefend met de programmeertaal Triangle, de taal die centraal staat in het
boek van Vertalerbouw, Watt & Brown (2000). Tenslotte wordt een eerste aanzet gemaakt tot het
bouwen van een symbol table.
1.1
Wordcount in Java
Bij het practicum van Vertalerbouw gaan we ervan uit dat uw kennis van de programmeertaal
Java tenminste op het niveau van Programmeren 1 en 2 ligt. U moet in staat zijn om zelfstandig
Java programma’s te schrijven en u dient te weten hoe de command-line tools (met name: java,
javac en javadoc) van de Java SDK gebruikt moeten worden. U wordt verder geacht de Java
API documentatie te kunnen raadplegen.
Bij deze eerste opgave werken we aan een applicatie die het aantal regels, woorden en karaketers telt van de standaard input of van een file. Onder Unix/Linux staat dit programma bekend
onder de naam wc, een afkorting van wordcount. Via de website van Vertalerbouw kunt u de file
WordCount.java vinden. Dit programma is een Java implementatie van wordcount.
+ 1.1.1
Bestudeer de file WordCount.java en compileer dit programma. Experimenteer met het
programma om te kijken dat het programma naar behoren werkt.
In het programma WordCount.java worden de woorden geteld door in elke regel expliciet naar
zogenaamde whitespace karakters (nl. spaties en tabs) te zoeken. Inplaats daarvan is het ook
mogelijk om een java.util.Scanner object te gebruiken.
Een Scanner splitst de invoer op in tokens, de kleinste bouwstenen die een betekenis hebben
in de invoer. Een Scanner gebruikt een afbreekpatroon (delimiter pattern) om tokens van elkaar te
1
2
Week 1 – Java en Triangle
scheiden (default: whitespace karakters). In het geval van wordcount, zijn de kleinste bouwstenen
de woorden van de file: de woorden worden gescheiden door whitespace.1
+ 1.1.2
Schrijf een klasse WordCountScanner die een subklasse is van de klasse WordCount. De
methode count van WordCountScanner moet een java.util.Scanner object gebruiken
om de regels in woorden op te splitsen.
Voeg ook een methode main aan de klasse WordCountScanner toe opdat ook de applicatie
WordCountScanner gebruikt kan worden om woorden in een tekstbestand te tellen. Test
de klasse WordCountScanner en vergewis u ervan dat de programma’s WordCount en
WordCountScanner hetzelfde gedrag vertonen.
1.2
Wordcount in Triangle
Gelijk vrijwel alle vertaler leerboeken, behandelen Watt & Brown (2000) de theorie en praktijk
het bouwen van vertalers aan de hand van een klein programmeertaaltje. Het taaltje van Watt &
Brown (2000) heet Triangle. De auteurs hebben ook een vertaler ontwikkeld voor Triangle die
code voor de Triangle Abstract Machine (TAM) genereert. Met behulp van de TAM-Interpreter
kunnen Triangle programma’s daadwerkelijk uitgevoerd worden.2 Bijlage B van Watt & Brown
(2000) bevat een informele specificatie van Triangle.
Zowel het boek (met name het voorbeeldprogramma op bladzijde 400) als de source code
van Triangle bevatten enkele slordige foutjes. Via de website van Vertalerbouw kunt u een lijst
met errata/bugs vinden. Ook kunt u daar een verbeterde versie van de Triangle.jar distributie
vinden.
Uitvoeren van een Triangle programma. We gaan ervan uit dat u de beschikking heeft over
Triangle.jar, de archive die zowel de Triangle compiler als de TAM Interpreter en Disassembler bevat.
Compileren van een Triangle programma foo.tri gaat als volgt:3
java -cp Triangle.jar Triangle.Compiler foo.tri
Als het programma foo.tri geen fouten bevat, zal de compiler de file obj.tam genereren waarin
zich de (binary) TAM-code bevindt voor de Triangle Abstract Machine. Met behulp van de TAMinterpreter kan het programma vervolgens uitgevoerd worden:
java -cp Triangle.jar TAM.Interpreter
Als u wilt zien welke TAM code door de Triangle compiler gegenereerd is kunt u de disassembler
gebruiken, die een tekstuele representatie van obj.tam op de standaard output laat zien:
1
Java biedt ook een klasse java.util.StringTokenizer voor het opsplitsen van de invoer in tokens.
Scanner is echter krachtiger en flexibeler en verdient de voorkeur boven StringTokenizer.
2
De Java source code van zowel de Triangle-vertaler als de TAM-interpreter is ook beschikbaar (zie de website
van Vertalerbouw). Veel van de code fragmenten uit Watt & Brown (2000) komen rechtstreeks in de Java code voor.
Helaas is de Java code zwak becommentarieerd: de code bevat nauwelijks zinvol javadoc commentaar en het overige
commentaar klopt vaak niet. De programmatuur haalt derhalve helaas niet de standaard van P1 en P2.
3
De -cp Triangle.jar option is nodig om ervoor te zorgen dat de inhoud van Triangle.jar aan het
Java CLASSPATH toegevoegd wordt. U zou er ook voor kunnen kiezen om de file Triangle.jar standaard in
uw Java CLASSPATH op te nemen.
1.3 Symbol Table
3
java -cp Triangle.jar TAM.Disassembler
+ 1.2.1
Als kennismaking met Triangle, dient u een Triangle programma te schrijven dat het eindcijfer bepaalt van het vak Vertalerbouw. Omdat Triangle geen reële getallen ondersteunt,
dient u te rekenen met cijfers tussen 10 en 100, d.w.z. de oorspronkelijke cijfers met 10
vermenigvuldigd.
Het programma dient eerst te vragen naar de cijfers voor de twee opgavenseries. Vervolgens dient het programma te vragen naar het cijfer van het practicum. Met deze gegevens
moet het programma het eindcijfer voor het vak bepalen. In de Inleiding van deze practicumhandleiding, onder het kopje “Beoordeling”, staan de regels voor het bepalen van het
eindcijfer.
Nota Bene. Als uw Triangle programma fouten bevat zult u merken dat de Triangle compiler
niet erg sterk is in het geven van foutmeldingen. We hopen dat de compiler die u in de
eindopdracht zult ontwikkelen beter met fouten in de invoer zal omspringen.
Na deze vingeroefening, ontwikkelen we vervolgens een wordcount programma in Triangle. Het
Triangle programma op pagina 400 van Watt & Brown (2000) kan hierbij als voorbeeld dienen
van een programma dat karakters inleest en weer wegschrijft.
Let op: het programma op bladzijde 400 bevat (tenminste) twee storende fouten. De condities
van while eol() en while eof() dienen uiteraard voorafgegaan worden door de operator voor
logische negatie: \. Daarnaast is de procedure getline foutief. Voor een correcte getline
wordt verwezen naar de website van Vertalerbouw: de “Triangle Bugs” pagina. Helaas bevat de
oorspronkelijke Triangle compiler ook enkele fouten, waardoor (zelfs) het verbeterde programma
niet zal werken. Zorg ervoor dat u de laatste versie van de Triangle compiler gebruikt.
+ 1.2.2
1.3
Schrijf analoog aan het WordCount programma in Java van Opgave 1.1 een Triangle
programma dat karakters van de standaard input inleest en vervolgens het aantal regels,
het aantal woorden en het aantal karakters telt op de input en deze aantallen afdrukt op de
standaard output. Zorg ervoor dat uw Triangle programma dezelfde uitvoer geeft als de
Java programma’s van Opgave 1.1.
Symbol Table
In deze opgave maken we een begin met een symbol table, een structuur waarin gegevens over de
identifiers van een programma efficient kunnen worden opgeslagen. De programmatuur van deze
opgave zult u ook bij de eindopdracht van Vertalerbouw kunnen gebruiken.
4
Week 1 – Java en Triangle
Beschouw de volgende grammatica in BNF-formaat met startsymbol decluse.
decluse
serie
::=
::=
unit
::=
|
|
|
decl
use
id
::=
::=
::=
letter
::=
|
“(” serie “)”
unit serie
ǫ
decl
use
“(” serie “)”
“D:” id
“U:” id
letter id
letter
“a” | “b” | “c” | “d” | “e” | “f” | “g” |
“h” | “i” | “j” | “k” | “l” | “m” | “n” |
“o” | “p” | “q” | “r” | “s” | “t” | “u” |
“v” | “w” | “x” | “y” | “z”
De strings tussen dubbele quotes zijn terminals; de dubbele quotes zelf horen niet bij de terminals.
Bij de serie is het tweede alternatief ǫ wat correspondeert met de ‘lege string’. Een serie is dus òf
een unit gevolgd door een serie òf de lege string.
Deze BNF beschrijft een simpele hierarchische structuur van identifiers, waarbij identifiers
(nl. id) gedeclareerd (decl) en gebruikt (use) kunnen worden. De haakjes corresponderen met
zogenaamde scope-levels van programmeertalen. Een voorbeeld van een zin van deze grammatica
is:
(D:aap (U:aap D:noot D:aap (U:noot) (D:noot U:noot)) U:aap)
Merk op dat hoewel dit voorbeeld een zin is in de taal gegenereerd door de grammatica decluse,
de impliciet gesuggereerde relatie tussen de gedeclareerde en gebruikte identifiers niet klopt.
Merk tevens op dat ten behoeve van de leesbaarheid de voorbeeldzin whitespace karakters (nl.
spaties) bevat die niet door de BNF gedefinieerd worden.
+ 1.3.1
Schrijf een Java programma dat bovenstaande grammatica kan parsen. Het programma
moet controleren of de gevonden identifiers goed geformatteerd zijn (d.w.z. een D: of een
U: prefix hebben). Daarnaast moet het programma controleren of de haakjes goed genest
zijn.
Uw programma moet zogenaamde whitespace-karakters (spaties, tabs en regelovergangen)
wel inlezen maar verder negeren.
Hint: U hoeft geen recursive descent parser te schrijven. De grammatica is dermate simpel
dat u in de inleeslus met enkele variabelen kunt bijhouden of de invoer nog aan de grammatica voldoet.
We hebben tot dusver nog geen nadere beperkingen aan geldige zinnen van de grammatica decluse
opgelegd. Hieronder staan de context beperkingen (Engels: context constraints) met betrekking
tot de grammatica decluse:
Een id mag alleen gebruikt worden (use) als het daarvoor gedeclareerd is (decl) op hetzelfde
level of binnen een omsluitende scope.
Een id mag niet nogmaals gedeclareerd worden in dezelfde scope.
1.3 Symbol Table
5
Een id mag wel nogmaals gedeclareerd worden in een diepere scope. Deze nieuwe decleratie
overschrijft (tijdelijk) de declaratie van een buitenste scope.
Om context beperkingen van de grammatica te controleren moeten de gedeclareerde identifiers
in een tabel worden opgeslagen. Een dergelijke tabel wordt symbol table of identification table
genoemd. Voor elke gedeclareerde identifier wordt belangrijke informatie opgeslagen; te denken
valt aan het level van de declaratie, de positie in de file (regelnummer en kolomnummer) en het
type van de identifier (bij een programma), etc.
Voor deze opgave is het voldoende om van elke identifier alleen het level bij te houden. We
definiëren daartoe de klasse IdEntry.
public class IdEntry {
private int level = -1;
public int
public void
getLevel()
setLevel(int level)
{ return level;
{ this.level = level;
}
}
}
Het bestand IdEntry.java is ook op de website van Vertalerbouw te vinden.
+ 1.3.2
Schrijf een klasse SymbolTable waarin informatie over identifiers kan worden opgeslagen. Deze informatie dient binnen de SymbolTable als IdEntry-objecten te worden
opgeslagen. Het raamwerk van de klasse SymbolTable en haar methoden ziet er als volgt
uit.
import java.util.*;
public class SymbolTable<Entry extends IdEntry> {
/**
* Constructor.
* @ensure this.currentLevel() == -1
*/
public SymbolTable() {
// body nog toe te voegen
}
/**
* Opens a new scope.
* @ensure this.currentLevel() == old.currentLevel()+1
*/
public void openScope() {
// body nog toe te voegen
}
/**
* Closes the current scope. All identifiers in
* the current scope will be removed from the SymbolTable.
* @require old.currentLevel() > -1
* @ensure this.currentLevel() == old.currentLevel()-1
*/
public void closeScope() {
// body nog toe te voegen
}
6
Week 1 – Java en Triangle
/** Returns the current scope level. */
public int currentLevel() {
return 0; // body nog toe te voegen
}
/**
* Enters an id together with an entry into this SymbolTable using the
* current scope level. The entry’s level is set to currentLevel().
* @require String != null && String != "" && entry != null
* @ensure this.retrieve(id).getLevel() == currentLevel()
* @throws SymbolTableException when there is no valid current scope level,
or when the id is already declared on the current level.
*
*/
public void enter(String id, Entry entry) throws SymbolTableException {
// body nog toe te voegen
}
/**
* Get the Entry corresponding with id whose level is the highest.
* In other words, the method returns the Entry that is defined last.
* @return Entry of this id on the highest level
null if this SymbolTable does not contain id
*
/
*
public Entry retrieve(String id) {
return null; // body nog toe te voegen
}
}
/** Exception class to signal problems with the SymbolTable */
class SymbolTableException extends Exception {
public static final long serialVersionUID = 24362462L; // for Serializable
public SymbolTableException(String msg) { super(msg); }
}
De klasse SymbolTable is geparameteriseerd met de klasse IdEntry: een SymbolTable
kan alleen maar objecten opslaan die een (subklasse van) IdEntry zijn. De IdEntryobjecten worden geı̈dentificeerd door een String-representatie van de identifier (zie de
methoden enter en retrieve). De methode enter gooit een SymbolTableException
op het moment dat er iets mis is met de identifier die aan de SymbolTable moet worden
toegevoegd. Het raamwerk van de klasse SymbolTable is beschikbaar als SymbolTable.java op de Vertalerbouw-website.
Hint: Bij de implementatie van de klasse SymbolTable kunnen de klassen uit Java’s collectie hiërarchie handig zijn. Met name de interfaces java.util.Map en java.util.List
en de klasse java.util.Stack zijn wellicht nuttig.
+ 1.3.3
Breidt tenslotte het programma van Vraag 1.3.1 zodanig uit dat de gedeclareerde identifiers in een SymbolTable-object worden opgeslagen. Voor elk id dat gebruikt wordt moet
gecontroleerd worden of het gedeclareerd is. Houdt daarbij rekening met het volgende.
Als een id twee keer (of meer) gedeclareerd wordt in dezelfde scope, dan moet het
1.3 Symbol Table
7
programma dit melden, maar wel doorgaan met het verwerken van de invoer.
Het programma moet tenminste alle woorden afdrukken die gebruikt worden. Voor
elk gebruikt id moet het afdrukken op welk scope-level het id gebruikt wordt en op
welk scope-level het id gedeclareerd is. Het scope-level van de eerste serie (die van
decluse) is 0.
Als een woord gebruikt wordt dat niet gedeclareerd is moet het programma dit melden.
De rest van het invoer moet echter wel verder verwerkt worden.
Voorbeeld. Beschouw de volgende structuur. Net als het eerdere voorbeeld, voldoet dit voorbeeld aan de grammatica decluse.
(D:x D:y D:z
U:y U:z
(D:a D:z
U:x U:y U:z (D:p D:q U:p U:y) (U:z D:z U:z) U:a
)
(D:x D:p D:x
U:x U:q U:y U:z
)
)
Dit voorbeeld is als sample-2.txt op de Vertalerbouw-website te vinden.
Het voorbeeld houdt zich echter niet aan de context beperkingen van de grammatica decluse. Met
dit voorbeeld als invoer zou een programma van Vraag 1.3.3 de volgende output kunnen genereren:
D:x on
D:y on
D:z on
U:y on
U:z on
D:a on
D:z on
U:x on
U:y on
U:z on
D:p on
D:q on
U:p on
U:y on
U:z on
D:z on
U:z on
U:a on
D:x on
D:p on
error:
U:x on
U:q on
U:y on
U:z on
level 0
level 0
level 0
level 0, declared on level 0
level 0, declared on level 0
level 1
level 1
level 1, declared on level 0
level 1, declared on level 0
level 1, declared on level 1
level 2
level 2
level 2, declared on level 2
level 2, declared on level 0
level 2, declared on level 1
level 2
level 2, declared on level 2
level 1, declared on level 1
level 1
level 1
’x’ already declared on the current level
level 1, declared on level 1
level 1, *undeclared*
level 1, declared on level 0
level 1, declared on level 0
8
Week 1 – Java en Triangle
Week
2
Recursive-Descent Parsing
P RACTICUM
Tijdens dit practicum wordt een eenvoudige one-pass recursive-descent vertaler ontwikkeld in
Java. We volgen daarbij hoofdstuk 4 van Watt & Brown. De te ontwikkelen compiler dient een
tabel-representatie in LATEX te vertalen naar een tabel-representie in HTML.
Opmerking: Het practicum van deze week is niet moeilijk maar wel veel van hetzelfde. De opdracht
illustreert dat het redelijk eenvoudig is om een recursive-descent parser te schrijven, maar dat het voor
grotere talen sterk aan te bevelen is om dergelijke ontleed-programma’s met behulp van een generator
automatisch te laten genereren.
2.1
Scanner
TEX is een krachtig tekstopmaaksysteem waarmee professioneel drukwerk gemaakt kan worden.
TEX is een wereldwijde standaard voor het opmaken van wetenschappelijke artikelen en boeken.
TEX is met name geschikt als de tekst veel wiskundige en formules bevat, maar ook met platte
tekst heeft TEX geen enkele moeite. Door de vele commando’s van TEX lijkt het ‘opmaken’ van
TEX documenten een beetje op programmeren.
LATEX is een uitgebreid en krachtig macropakket voor TEX, dat eenvoudiger te gebruiken en
te leren is dan TEX. Zo ondersteunt LATEX bijvoorbeeld een tabular-omgeving, waarmee het
eenvoudig is om een tabel weer te geven.
Voorbeeld. Beschouw het volgende voorbeeld van een tabular-specificatie (ook beschikbaar als
sample-1.tex op de Vertalerbouw website).
% An example to test the Tabular application.
\begin{tabular}{lcr}
Aap & Noot & Mies \\
9
10
Week 2 – Recursive-Descent Parsing
latexTabular
colsSpec
rows
::=
::=
::=
row
entries
::=
::=
otherEntries
::=
|
|
|
entry
beginTabular
endTabular
num
::=
::=
::=
::=
identifier
::=
|
|
|
digit
letter
::=
::=
BSLASH
DOUBLY BLASH
LCURLY
RCURLY
AMPERSAND
BEGIN
END
TABULAR
::=
::=
::=
::=
::=
::=
::=
::=
|
Figuur 2.1:
BNF
beginTabular colsSpec rows endTabular
LCURLY identifier RCURLY
rows row
ǫ
entries DOUBLE BSLASH
entry otherEntries
ǫ
AMPERSAND entries
ǫ
num | identifier | ǫ
BSLASH BEGIN LCURLY TABULAR RCURLY
BSLASH END LCURLY TABULAR RCURLY
num digit
digit
identifier letter
identifier digit
letter
“0” | “1” | . . . | “9”
“a” | “b” | . . . | “z”
“A” | “B” | . . . | “Z”
“ \”
“ \\”
“{”
“}”
“&”
“begin”
“end”
“tabular”
grammatica van LATEX’s tabular omgeving.
Wim & Zus & Jet \\
1
& 2
& 3
\\
Teun & Vuur & Gijs \\
\end{tabular}
Het begin (resp. einde) van een tabular-omgeving wordt aangegeven door de string
\begin{tabular} (resp. \end{tabular}). Een tabular-omgeving verwacht één argument mee tussen accolades. In het voorbeeld is dat de letter-combinatie lcr. Dit argument geeft
aan hoe de kolommen van de tabel geformatteerd moeten worden: l staat voor links-uitgevuld, c
staat voor gecentreerd en r staat voor rechts-uitgevuld. Het aantal letters in het argument geeft
het aantal kolommen weer. Bij dit practicum zal het argument van de tabular-omgeving niet
gebruikt worden: we controleren het aantal kolommen niet en de uitlijning van de kolommen
gebruiken we ook niet.
De elementen (i.e. entries) van een tabular worden rij-gewijs gespecificeerd. Per rij worden
de elementen van elkaar gescheiden door een ampersand-teken (“&”). Een rij wordt afgesloten
door twee backslashes (“\\”). Bij dit practicum zijn de tabel-elementen of een num (een getal) of
een identifier (een letter gevolgd door nul of meer letters of cijfers).1
1
Zij opgemerkt dat we bij dit practicum een vereenvoudigde versie van de tabular-omgeving beschouwen. De
officiële LATEX-tabular is veel uitgebreider en er zijn geen beperkingen voor de tabel-elementen.
2.1 Scanner
In figuur 2.1 staat een
t.a.v. deze grammatica:
+ 2.1.1
11
BNF -grammatica
voor de tabular-omgeving. Enkele opmerkingen
De ǫ-tekens in de grammatica (bijvoorbeeld bij rows) staan voor ‘leeg’, corresponderend met
de lege string.
In colsSpec wordt het argument voor de tabular-omgeving als een identifier gespecificeerd. De eis dat deze identifier uit alleen de letters l, c en r mag bestaan komt dus niet tot
uitdrukking in de grammatica.
De grammatica van Fig. 2.1 staat nog niet in de juiste vorm om als basis te dienen voor
het algoritme van paragraaf 4.3.4 van Watt & Brown. Gebruik de paragrafen 4.2.2 en 4.2.3
van Watt & Brown om de grammatica in EBNF-formaat te herschrijven en zorg ervoor dat
de grammatica geen links-recursieve productieregels meer bevat.
Op de website van Vertalerbouw kunt u het bestand Token.java vinden. Dit bestand definieert
de klasse Token voor het opslaan van de tokens (d.w.z. terminals) van de latexTabular-taal. Zoals
u kunt zien worden de strings “begin”, “end” en “tabular” beschouwd als aparte keywords.
Ter vergelijking: In paragraaf 4.1.1 en aan het einde van paragraaf 4.5 van Watt & Brown
wordt de klasse Token voor Mini-Triangle besproken.
+ 2.1.2
Schrijf nu een klasse Scanner die een latexTabular-specificatie kan scannen. De klasse
dient tenminste de volgende twee methoden te ondersteunen.
public class Scanner {
private InputStream
in;
/**
* Constructor.
* @param in the InputStream from which the characters will be read
*/
public Scanner(InputStream in) {
this.in = in;
}
/**
* Returns the next Token from the input.
* @return the next Token
* @throws SyntaxError when an unknown or unexpected character
has been found in the input.
*
/
*
public Token scan() throws SyntaxError
}
Op de website van Vertalerbouw kunt u een ‘lege huls’ van de de klasse Scanner.java
vinden.
U dient rekening te houden met het volgende.
Als de Scanner een karakter inleest dat het niet kent dient er een SyntaxErrorexceptie gegooid te worden. U dient deze (triviale) klasse SyntaxError zelf te definiëren.
12
Week 2 – Recursive-Descent Parsing
Een LATEX tabular-specificatie kan ook TEX commentaar bevatten (zie bijvoorbeeld
het eerdere voorbeeld, sample-1.tex. Commentaar in TEX begint met een procentteken (‘%’) en het commentaar loopt door tot het einde van de regel.
Hint: In paragraaf 4.5 van Watt & Brown wordt een scanner voor Mini-Triangle ontwikkeld.
+ 2.1.3
Voeg aan de klasse Scanner een methode main toe, waarmee de Scanner getest kan
worden. Voor elk token dat de scanner vindt, moet de naam van het token en de representatie
van het token afgedrukt worden.
Gegeven het voorbeeld uit de inleiding (sample-1.tex), zou bijvoorbeeld de volgende
uitvoer gegenereerd kunnen worden:
BSLASH
BEGIN
LCURLY
TABULAR
RCURLY
LCURLY
IDENTIFIER
RCURLY
IDENTIFIER
AMPERSAND
IDENTIFIER
AMPERSAND
IDENTIFIER
DOUBLE_BSLASH
IDENTIFIER
AMPERSAND
IDENTIFIER
AMPERSAND
IDENTIFIER
DOUBLE_BSLASH
NUM
AMPERSAND
NUM
AMPERSAND
NUM
DOUBLE_BSLASH
IDENTIFIER
AMPERSAND
IDENTIFIER
AMPERSAND
IDENTIFIER
DOUBLE_BSLASH
BSLASH
END
LCURLY
TABULAR
RCURLY
Scanning OK. Number
’\’
’begin’
’{’
’tabular’
’}’
’{’
’lcr’
’}’
’Aap’
’&’
’Noot’
’&’
’Mies’
’\\’
’Wim’
’&’
’Zus’
’&’
’Jet’
’\\’
’1’
’&’
’2’
’&’
’3’
’\\’
’Teun’
’&’
’Vuur’
’&’
’Gijs’
’\\’
’\’
’end’
’{’
’tabular’
’}’
of lines: 8
2.2 Parser
2.2
13
Parser
In deze opgave gebruiken we de zojuist ontwikkelde scanner om een recursive-descent parser
te ontwikkelen voor de latexTabular grammatica. Voor iedere non-terminal XYZ schrijven we
een methode parseXYZ die ervoor zorgt dat de non-terminal XYZ ontleed wordt. We baseren ons
uiteraard niet op de BNF-grammatica van Fig. 2.1, maar op de getransformeerde EBNF-grammatica
van Vraag 2.1.1.
+ 2.2.1
Schrijf een klasse Parser die de grammatica van Fig. 2.1 en Vraag 2.1.1 ontleedt. U dient
daarvoor paragraaf 4.3.4 van Watt & Brown te volgen.
Zij opgemerkt dat de Parser (nog) geen uitvoer mag genereren; de invoer mag alleen geparsed worden. Er wordt verder geen abstracte syntax tree opgebouwd.
De klasse dient tenminste de volgende twee methoden te implementeren.
public class Parser {
/**
* Constructor.
* @require scanner != null
scanner the Scanner object to be used for parsing
* @param
/
*
public Parser(Scanner scanner)
/**
* Parses the input as LaTeX tabular specification.
* @returns 0 when parsing was successful
>0 when parsing failed.
*
@note
There
is no obvious need to return an integer here.
*
/
*
public int parse()
}
Enkele opmerkingen:
+ 2.2.2
De methoden parseXYZ moeten als protected methoden gedefinieerd worden. Hierdoor kan de Parser eenvoudig uitgebreid en veranderd worden.
De SyntaxError-excepties die gegooid kunnen worden door de scanner moeten afgevangen worden in de methode parse.
Voeg aan de klasse Parser een methode main toe, waarmee de Parser getest kan worden.
Nu we een scanner en parser hebben om de latexTabular-grammatica te ontleden moeten we er
nog voor zorgen dat er een HTML-tabel gegenereerd wordt. De structuur van een HTML-tabel is
simpel. Een grammatica van een HTML-tabel staat in Fig. 2.2.
Om een HTML-tabel in een webbrowser te kunnen zien dienen er ook nog HTML-document
tags om de tabel gezet te worden. Voor de tabel dient te komen: <html><body>, en na de tabel:
</body></html>.
Voorbeeld. De LATEX tabular-specificatie van het eerdere voorbeeld (sample-1.tex) zou vertaald kunnen worden naar het volgende (complete) HTML-document.
14
Week 2 – Recursive-Descent Parsing
htmlTable
rows
row
entries
entry
BEGIN TABLE
END TABLE
BEGIN ROW
END ROW
BEGIN ENTRY
END ENTRY
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
::=
Figuur 2.2:
BEGIN TABLE rows END TABLE
row∗
BEGIN ROW entries END ROW
entry∗
BEGIN ENTRY any-char∗ END ENTRY
“<table border = "1">”
“<\table>”
“<tr>”
“<\tr>”
“<td>”
“<\td>”
EBNF
grammatica van HTML table.
<html><body>
<table border="1">
<tr>
<td>Aap</td>
<td>Noot</td>
<td>Mies</td>
</tr>
<tr>
<td>Wim</td>
<td>Zus</td>
<td>Jet</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>Teun</td>
<td>Vuur</td>
<td>Gijs</td>
</tr>
</table>
</body></html>
+ 2.2.3
Schrijf een klasse ParserEmit, die een subklasse is van de klasse Parser van
Vraag 2.2.1. Het verschil met Parser is dat ParserEmit tijdens het parsen van een LATEX
tabular meteen HTML ‘code’ wegschrijft.
Hint: Ten opzichte van de superklasse Parser is de Java-code van de klasse ParserEmit
een stuk korter: slechts een paar methoden van Parser hoeven overschreven te worden.
+ 2.2.4
Test tenslotte uw klasse ParserEmit op enkele voorbeeld LATEX tabular-bestanden, die
u op de website van Vertalerbouw kunt vinden.
Week
3
ANTLR
P RACTICUM
In dit practicum maken we kennis met ANTLR, de parser generator die ook bij de eindopdracht
van Vertalerbouw gebruikt zal worden. We gebruiken ANTLR om de vertaler voor een eenvoudig
expressie-taaltje uit te breiden.
3.1
Beginnen met Antlr
Inleiding. De officiële website van ANTLR is http://www.antlr.org.
Deze website heeft onder meer een pagina om te leren omgaan met ANTLR 3.x:
http://www.antlr.org/wiki/display/ANTLR3/FAQ+-+Getting+Started.
Bij het vak Vertalerbouw wordt verder geen extra materiaal over ANTLR beschikbaar gesteld; via
de website van ANTLR is alles te vinden.
Installatie. Van de website van ANTLR dient u de laatste (stabiele) versie van ANTLR te downloaden: versie 3.2.1 Er zijn verschillende distributies, maar het is het eenvoudigst om de complete JAR archive te downloaden: antlr-3.2.jar (grootte: 1.8 Mb). Deze file dient in Java’s
CLASSPATH te worden opgenomen.
ANTLR is een command-line compiler generator. De methode main binnen de klasse
org.antlr.Tool (van antlr-3.2.jar) is het startpunt van ANTLR. Om te controleren of de
bestanden goed geinstalleerd zijn (en derhalve in Java’s CLASSPATH staan), kunt u ANTLR als
volgt aanroepen:
java org.antlr.Tool
1
Het is niet toegestaan om een oudere versie van
gebruiken.
ANTLR
15
(d.w.z. versie 2.x) bij Vertalerbouw 2010/2011 kw4 te
16
Week 3 – ANTLR
ANTLR zal nu ‘usage’ informatie geven hoe de tool gebruikt dient te worden. Als de jarbestanden niet allemaal in het CLASSPATH staan zal Java daarentegen een exceptie gooien.
Uit de ‘usage’ informatie kunt u opmaken dat ANTLR tenminste een bestand met extensie .g
nodig heeft met daarin de definitie van grammatica. Gegeven een grammatica file foo.g kunt u
ANTLR als volgt aanroepen om een vertaler te genereren:
java org.antlr.Tool foo.g
Het is bij dit vak overigens ook toegestaan om ANTLRWorks – ANTLR’s ontwikkelomgeving voor
grammatica’s – te gebruiken. De distributie van ANTLRWorks bevat alle benodigde ANTLR software en het is dan ook niet nodig om de source distributie van ANTLR ook nog op te halen. Zoals
gezegd is het gebruik van ANTLRWorks toegestaan. De voorbeelden in deze handleiding zullen er
echter steeds vanuit gaan dat u de command-line versie van ANTLR gebruikt.
Calc Op het inleidende hoorcollege over ANTLR is een eenvoudige, rekenmachine-achtige programmeertaal voor numerieke berekeningen behandeld: Calc. Tijdens het practicum van deze
week gaan we deze taal en de bijbehorende vertaler verder uitbreiden. Aangeraden wordt om de
slides van dit hoorcollege over ANTLR (nogmaals) te raadplegen. Deze slides geven extra uitleg
over de opbouw en ANTLR-constructies van de Calc-vertaler.
Op de website van Vertalerbouw kunt u drie .g files vinden:
Calc.g: de grammatica voor de lexer en parser van Calc,
CalcChecker.g: de grammatica van de tree parser die de contextanalyse verzorgd, en
CalcInterpreter.g: de grammatica van de tree parser die de Calc AST interpreteert.
Deze drie grammatica bestanden definiëren gezamenlijk de vertaler (en interpreter) voor Calc.
Op de website staat ook de Java file Calc.java die de vier Calc-recognizers (nl. lexer, parser en
de twee tree parsers) aan elkaar knoopt. Tenslotte is er een Java file CalcException.java die
gebruikt wordt door CalcChecker.g bij het constateren van Calc-specifieke fouten in de invoer.
Kenmerken van Calc. Een programma in de taal Calc bestaat uit nul-of-meer declaraties gevolgd
door één-of-meer statements. In de declaratie-sectie kunnen variabelen van het type integer
gedeclareerd worden. Een variabele mag maar één keer gedeclareerd worden. De taal Calc heeft
een zogenaamde monolitische blokstructuur.
In het statementgedeelte kunnen assignments en print-statements elkaar afwisselen. Bij een
een assignment krijgt een variabele de waarde van een expressie. Bij een print-statement wordt
een expressie op het beeldscherm getoond. De operatoren van een expressie zijn de binaire optelling en aftrekking. Als operanden kunnen naast de variabelen ook getaldenotaties in expressies
voorkomen. Variabelen mogen alleen in statements gebruikt worden als ze daarvoor gedeclareerd
zijn. Een voorbeeld-programma in Calc is het volgende:
// ex1.calc -- valid Calc program
var n: integer;
var x: integer;
n := 2+4-1;
x := n+3+7;
print(x);
Bij het executeren van het programma zal de waarde 15 op het beeldscherm afgedrukt worden.
+ 3.1.1
Haal alle bronbestanden van Calc (d.w.z. de drie .g bestanden en de twee Java be-
3.1 Beginnen met Antlr
17
standen) van de Vertalerbouw website. Gebruik ANTLR 3.2 om een werkende vertaler voor
Calc te genereren. Schrijf een paar Calc programma’s en controleer dat de compiler naar
behoren werkt.
Het Java programma Calc.java ondersteunt de optie -ast, om de AST van een Calc
programma als een String af te drukken. Bekijk de file Calc.java en experimenteer met
deze optie.
Zoals u wellicht gezien heeft, ondersteunt Calc.java ook een optie -dot. Hiermee
is het mogelijk om een .dot bestand van de AST van het Calc programma te genereren. Een .dot bestand kan gevisualiseerd worden met het graaftekenprogramma GraphViz
(http://www.graphviz.org/). Het is voor dit practicum echter niet nodig om dit programma te
installeren.
Zoals gezegd breiden we in de rest van deze opgave de taal Calc en haar vertaler uit.
+ 3.1.2
Voeg operators voor vermenigvuldiging en deling toe aan de taal Calc. Daarvoor dienen
de lexer, de parser en beide tree parsers van Calc aangepast te worden. Let daarbij op het
volgende.
De gebruikelijke prioriteits-volgorde van operatoren dient in acht te worden genomen
(d.w.z. vermenigvuldiging en deling gaan voor optellen en aftrekken). Het volgende
Calc programma
print(3+4*5);
zal dus 23 op de standaard uitvoer moeten afdrukken.
Binnen CalcInterpreter dient gecontroleerd te worden dat er niet door nul gedeeld
wordt.
Hint: In Excercise 4.14 van het boek van Watt & Brown staat beschreven hoe een grammatica aangepast kan worden zodat het verschil in prioriteit tot uitdrukking komt in de parse
boom. Ook de slides van het hoorcollege over ANTLR stippen de prioriteit van operatoren
(precedence) aan.
+ 3.1.3
Voeg aan Calc, analoog aan print, een procedure swap toe. Een aanroep van swap(x,y)
– waarbij x en y twee gedeclareerde variabelen zijn – heeft als gevolg dat de inhoud van de
twee variabelen x en y verwisseld wordt.
Daarvoor dienen weer alle ANTLR specificaties aangepast te worden.
+ 3.1.4
Aan Calc zal nu een if-then-else expressie toegevoegd worden. Beschouw de expressie if C then E1 else E2. Als de expressie C niet gelijk is aan 0, levert de expressie if
C then E1 else E2 de expressie E1 op. Als de expressie C wel gelijk is aan 0, levert de
expressie if C then E1 else E2 de expressie E2 op.
Breidt de grammatica specificaties van Calc zodanig uit dat nu ook een if-then-else
expressie ondersteund wordt.
+ 3.1.5
Voeg aan de taal Calc de relationele operatoren toe. Het gaat om de gebruikelijke binaire
operatoren <, <=, >, >=, == en !=. Deze operatoren leveren een integer-resultaat op: 1 voor
true, en 0 voor false. De relationele operatoren hebben een lagere prioriteit dan PLUS en
MINUS, maar hoger dan de if-then-else operator.
18
Week 3 – ANTLR
In de oorspronkelijke taal-definitie van Calc worden declaraties en statements strict gescheiden.
Het is uiteraard gebruikersvriendelijker als declaraties en statements elkaar kunnen afwisselen.
+ 3.1.6
Verander de grammaticaspecificaties van Calc dusdanig dat declaraties en statements
elkaar kunnen afwisselen. Het scope regels blijven uiteraard wel hetzelfde: een variabele
mag maar één keer gedeclareerd worden en een variabele mag alleen gebruikt worden als
hij daarvoor gedeclareerd is.
Tenslotte moet gelden dat een Calc programma niet met een declaratie mag eindigen.
+ 3.1.7
Verander het assignment statement van Calc nu in een (rechts-associatief) multipleassignment-statement. Het volgende statement wordt dan een geldig Calc statement:
x := y := z := 27;
Eerst krijgt z hier de waarde 27, die vervolgens wordt toegekend aan y, en tenslotte aan x.
Het is bij deze opgave toegestaan om de lookahead-constante k van CalcParser van 1 naar
2 te verhogen.
Het probleem bij multiple-assignment is dat zowel een assignment zelf als een expressie beiden
met een identifier kunnen beginnen. Op grond van slechts één lookahead-symbool kan de parser dan niet beslissen welke van de twee alternatieven gekozen moet worden. Het is echter wel
degelijk mogelijk om multiple-assignment in een LL(1)-grammatica op te lossen.
+ 3.1.8
Bij deze opgave dient u de grammatica in Calc.g zodanig aan te passen dat de lookaheadconstante op 1 kan blijven staan. U dient daarbij de rules van assignment en expr samen
te voegen.
Meerdere keren over een AST lopen
ANTLR bevat een rijke collectie van (run-time) klassen en bijbehorende methoden.
Het verdient aanbeveling om deze verzameling klassen eens aandachtig te bekijken.
Zie de API documentatie op http://www.antlr.org/api/Java/.
Voor de volgende opgave zijn met name de klassen org.antlr.runtime.tree.CommonTreeNodeStream en
org.antlr.runtime.tree.TreeParser van belang.
Voor de volgende taalfeature van Calc (n.l. het do-while statement) dient de CalcInterpreter) meerdere malen over eenzelfde deel van de AST te lopen. Dit kan op (tenminste) twee
manieren die hieronder kort worden uitgelegd. Een soortgelijke aanpak wordt ook besproken op:
http://www.antlr.org/wiki/display/ANTLR3/Simple+tree-based+interpeter
Rewind. Alle door ANTLR gegenereerde vertalers werken min of meer op dezelfde manier: ze
herkennen een zin (sentence) in een stroom (stream) van objecten. Voor een lexer is dit een stroom
van karakters, voor een parser een stroom van tokens, en voor een tree parser een stroom van
Tree nodes. Een ANTLR tree parser loopt dan ook niet over een echte boom, maar over een
‘platgeslagen’ één-dimensionele stroom van Tree objecten: een TreeNodeStream.
In het geval van een Calc tree parser is deze stroom van Tree objecten een CommonTreeNodeStream. De (Common)TreeNodeStream van een tree parser is beschikbaar via de protected
instantievariabele input. Twee handige methoden van de klasse CommonTreeNodeStream zijn
de methoden index en rewind. Gegeven de variable input, levert de aanroep input.index()
3.1 Beginnen met Antlr
19
een int-waarde ix op die correspondeert met de index van het huidige TreeNode in input. Deze
index kan vervolgens gebruikt worden om later weer terug te keren naar de positie ix in de boom
middels een aanroep input.rewind(ix);.
Ter illustratie een klein voorbeeld. Zij gegeven een tree parser FooWalker met de volgende rule
foo:
foo : ˆ(FOO bar)
;
We gaan het gedrag van FooWalker nu wijzigen. Als we nu een FOO node in de AST tegenkomen,
willen we foo hier steeds n-keer overheen laten lopen. Dit kan als volgt:
foo[int n]
@init { int ix = input.index(); }
: ˆ(FOO bar)
{
if (n > 0) {
input.rewind(ix);
foo(n-1);
}
}
;
Overal waar de non-terminal foo voorkomt in de grammatica van FooWalker zal nu foo[n]
moeten worden gebruikt, waarbij n een int-waarde is. Merk op dat we hier ook gebruiken maken
van het feit dat elke rule (zoals foo) vertaald wordt naar een methode.
Nieuwe tree parser. Een andere mogelijkheid om meerdere keren over een deel van de
AST te wandelen is de volgende. We maken een nieuwe tree parser aan met een (Common)TreeNodeStream die start bij de node in de AST waar begonnen moet worden met wandelen. Vervolgens wordt de parse methode aangeroepen (d.w.z. de rule) die moet proberen over
dit deel van de AST te lopen.
Het aanmaken van de CommonTreeNodeStream is eenvoudig. De klasse CommonTreeNodeStream heeft namelijk een constructor die als parameter een CommonTreeNode object meekrijgt
(zie bijvoorbeeld Calc.java). Dit betekent dat van elke node in de AST een CommonTreeNodeStream gemaakt kan worden.
Het eerdere voorbeeld kan nu als volgt worden uitgewerkt.
foo[int n]
: ˆ(f=FOO bar)
{
if (n > 0) {
FooWalker fw = new FooWalker(new CommonTreeNodeStream(f));
fw.foo(n-1);
}
}
;
+ 3.1.9
Voeg aan de Calc-taal een do-while-statement toe. Het do-while-statement heeft de
volgende syntax (gebruikmakend van ANTLR-notatie):
dowhileStatement :
statements
:
DO statements WHILE expression ;
(statement SEMICOLON!)+ ;
20
Week 3 – ANTLR
Het dowhileStatement voert de statements minstens één keer uit. Na afloop wordt
de expression getest. Als de waarde van deze expression niet-nul is, dan worden de
statements nogmaals uitgevoerd. Dit gaat door totdat de expression de waarde nul
oplevert; dan wordt het dowhileStatement beëindigd.
Hieronder volgt een compleet Calc-programma met daarin een do-while-statement.
// dowhile.calc
var n: integer;
n := 10;
do
print(n);
n := n-1;
while n>0;
Op de website van Vertalerbouw kunt u een Calc programma vinden: easter.calc. Dit programma berekent de dag waarop Pasen valt voor de jaren 2004–2013. Het programma easter.calc zal de volgende output genereren.
2004
4
11
+ 3.1.10
Gebruik het programma easter.calc om te controleren of uw complete Calc-compiler
naar behoren werkt.
Week
4
Codegeneratie
P RACTICUM
Tijdens dit vierde practicum wordt eerst ANTLR gebruikt om een compiler te genereren voor het
decluse-taaltje van week 1. Daarna wordt er geoefend met het schrijven van TAM-assembler
programma’s, gebruikmakend van het programma TAM.Assembler. Tenslotte wordt er een codegenerator ontwikkeld voor de Calc-compiler van week 3 die TAM-bytecode zal genereren.
4.1
Nogmaals decluse
In deze opgave gebruiken we ANTLR om een compiler te schrijven voor het decluse taaltje van
Opgave 1.3 van week 1. In tegenstelling tot Opgave 1.3 ontwikkelen we nu een two-pass compiler
inplaats van een one-pass compiler. Voor details t.a.v. het decluse-taaltje verwijzen we naar
Opgave 1.3. Voor de volledigheid staat in Fig. 4.1 (nogmaals) de BNF grammatica van decluse.
Zij opgemerkt dat de specificatie van zogenaamd whitespace niet in Fig. 4.1 is opgenomen. Het
spreekt voor zich dat whitespace (zoals te doen gebruikelijk) genegeerd moet worden.
+ 4.1.1
Gebruik ANTLR om een lexer en parser te genereren voor de decluse grammatica van
Fig. 4.1. Probeer uw parser specificatie zo bondig mogelijk op te zetten.
Het is in de lexer en parser trouwens niet toegestaan om de context beperkingen van decluse
te controleren; er mag geen referentie naar een symbol table o.i.d. in voor komen.
Als er geen syntaxfouten in een decluse ‘programma’ zijn aangetroffen, dienen analoog aan Opgave 1.3 de context constraints gecontroleerd te worden. Zie Opgave 1.3 voor een beschrijving
van deze eisen.
+ 4.1.2
Gebruik ANTLR om een tree parser te genereren die de context constraints van een decluse-programma controleert. U dient hierbij de door u bij Opgave 1.3 geı̈mplementeerde
symbol table bij te gebruiken.
21
22
Week 4 – Codegeneratie
decluse
serie
::=
::=
unit
::=
|
|
|
decl
use
id
::=
::=
::=
letter
::=
|
Figuur 4.1:
BNF
“(” serie “)”
unit serie
ǫ
decl
use
“(” serie “)”
“D:” id
“U:” id
letter id
letter
LOWER | UPPER
grammatica van de decluse-taal.
Vergelijkend met de ad-hoc aanpak van 1.3 is de hier gevolgde aanpak snel, gestructureerd, elegant
en eenvoudig uitbreidbaar. In het algemeen nemen compiler generatoren zoals ANTLR de taal
ontwerper veel werk uit handen. En daarbij zijn de gegenereerde vertalers doorgaans maar een
fractie minder efficient dan met de hand geschreven programmatuur.
4.2
TAM-Assembler
Op de website van Vertalerbouw kunt u het bestand TAM-Assembler.zip vinden met daarin de
assembler TAM.Assembler (inclusief broncode) voor de Triangle Abstract Machine (TAM)1 . De
TAM-machine zelf wordt uitgebreid beschreven in Watt & Brown (2000), met name in Appendix
C.
Met behulp van deze TAM.Assembler kan een tekstrepresentatie van een TAM-assembler
programma omgezet worden naar TAM ‘bytecode’. Het programma TAM.Assembler wordt als
volgt gebruikt (met de aanname dat TAM-Assembler.zip zich in Java’s CLASSPATH bevindt):
java TAM.Assembler foo.tasm foo.tam
waarbij foo.tasm het invoerbestand is en foo.tam het uitvoerbestand voor de TAMbytecode. De TAM.Assembler maakt gebruik van de (in Java 1.4 geı̈ntroduceerde) package
java.util.regex. Dit betekent dat TAM.Assembler alleen te gebruiken is met Java versie
1.4 of hoger.
Ter illustratie staat hieronder een TAM-programma dat twee getallen inleest en vervolgens
controleert of de beide getallen aan elkaar gelijk zijn.
; [file: eqtest.tasm, started: 13-Apr-2003, version: 16-Apr-2004]
; TAM Assembler program which reads two numbers and prints ’Y’ if
; the two numbers are equal and prints ’N’ if the numbers are not equal.
PUSH
LOADA
CALL
2
0[SB]
getint
; reserve space for the 2 numbers
; address of n0: 0[SB]
; read number into n0
1
De TAM-assembler is in de lente van 2003 ontwikkeld door Matthijs Bomhoff, studentassistent Vertalerbouw
2002/2003.
4.3 Codegenerator voor Calc
L1:
L2:
LOADA
CALL
LOAD(1)
LOAD(1)
LOADL
CALL
JUMPIF(0)
LOADL
CALL
JUMP
LOADL
CALL
POP(0)
HALT
1[SB]
getint
0[SB]
1[SB]
1
eq
L1[CB]
89
put
L2[CB]
78
put
2
23
;
;
;
;
;
;
;
;
;
;
;
;
;
address of n1: 1[SB]
read number into n1
load number n0
load number n1
size of the arguments is 1
n0 == n1 ?
if !(n0 == n1) then goto L1
load ’Y’ on the stack
print ’Y’
jump over ’N’ part.
load ’N’ on the stack
print ’N’
pops the 2 numbers
Dit bestand eqtest.tasm is ook aanwezig op het practicumgebied van de website van Vertalerbouw.
Enkele opmerkingen t.a.v. de TAM.Assembler:
+ 4.2.1
Een TAM-assembler programma kan geannoteerd worden met end-of-line commentaar:
commentaar begint met ; en strekt zich uit tot het einde van de regel.
In een TAM-assembler programma kunnen symbolische labels gebruikt worden (zoals L1
en L2 in het voorbeeld programma). De TAM.Assembler zorgt er voor dat de juiste labels
worden ingevuld in de TAM-bytecode. De labels dienen te beginnen met een letter, waarna
nul of meer letters of cijfers kunnen volgen.
Schrijf een TAM-assembler programma dat drie integer getallen inleest van de standaard
invoer en vervolgens het kleinste van deze drie getallen afdrukt op de standaard output.
Voorzie uw programma van zinvol commentaar en zorg ervoor dat uw programma zo efficient mogelijk werkt (en dus geen zinloze instructies bevat).
4.3
Codegenerator voor Calc
Bij deze opgave ontwikkelen we een ANTLR tree parser die gegeven een AST van een Calcprogramma een TAM-assembler programma genereert. Het gegenereerde TAM-assembler programma kan vervolgens met behulp van de TAM.Assembler omgezet worden naar TAMbytecode.
Een Calc-programma foo.calc zou dan bijvoorbeeld als volgt vertaald kunnen worden en vervolgens uitgevoerd te worden:
java Calc -code_generator < foo.calc > foo.tasm
java TAM.Assembler foo.tasm foo.tam
java TAM.Interpreter foo.tam
We gaan er hierbij vanuit dat de optie -code generator bij Calc ervoor zorgt dat de codegenerator wordt gebruikt als tree parser (en dus niet CalcInterpreter). Merk op de gegenereerde
TAM-assembler code hier naar de standaard output wordt geschreven. Voorts gaan we ervan uit
dat zowel TAM.Assembler als TAM.Interpreter zich beiden in Java’s CLASSPATH bevinden.
24
+ 4.3.1
Week 4 – Codegeneratie
Schrijf een codegenerator voor de volledige Calc-taal. De codegenerator dient TAMassembler code te genereren die vervolgens met de TAM.Assembler naar TAM-bytecode
omgezet kan worden. De codegenerator dient als tree parser in ANTLR ontwikkeld te worden.
Pas uw Calc-compiler (met name Calc.java) zodanig aan dat nu óf de CalcInterpreter óf de te ontwikkelen CalcCodeGenerator als laatste pass over de AST-boom gaat.
Zorg ervoor dat de gegenereerde TAM-code hetzelfde gedrag vertoont als de eerder ontwikkelde interpreter die een Calc-programma simuleert over de AST-representatie. Met name
het programma easter.calc dient nog dezelfde uitvoer te genereren.
Merk op dat we nu dus twee mogelijkheden tot onze beschikking hebben om een Calc programma
te executeren; direct met de Interpreter of indirect (via TAM-code) met de CalcCodeGenerator. Interpretatie van de gegenereerde TAM-code (middels TAM.Interpreter) is overigens
bijna een factor tien sneller dan directe interpretatie van de AST met de CalcInterpreter.
Week
5
Triangle Extensies
P RACTICUM
Tijdens dit laatste practicum van het eerste deel van het practicum van Vertalerbouw wordt de
programmeertaal Triangle van Watt & Brown (2000) uitgebreid met twee nieuwe taalelementen:
een repeat-until-statement en een case-statement.
Eindopdracht Vertalerbouw. U kunt alleen deelnemen aan de afsluitende eindopdracht van
Vertalerbouw als alle practica van de eerste vijf weken uiterlijk aan het begin van het practicum
van woensdag 1 juni 2011 afgetekend zijn. Het is dus niet de toegestaan dat er nog op woesndag
1 juni aan de practica van het eerste deel gewerkt wordt: vanaf 1 juni staat het practicum van
Vertalerbouw in het teken van de eindopdracht.
5.1
Uitbreiden van Triangle
Zoals u ongetwijfeld gemerkt heeft, wordt de Triangle compiler in Watt & Brown (2000) als running example gebruikt ter illustratie van de behandelde vertaaltechnieken. Voor nadere uitleg van
opvallende constructies in de Java broncode wordt dan ook verwezen naar de betreffende hoofdstukken in Watt & Brown (2000). Appendix B bevat een semi-formele definitie van de Triangle
taal. Appendix D bevat de klassendiagrammen van de Triangle vertaler. Met name diagram D.1
geeft een goed overzicht van de implementatie van de compiler.
Laatste versie van Triangle. Zorg dat u begint met de verbeterde versie van de Triangle broncode die op de Vertalerbouw-website staat (en dus niet de oorspronkelijke versie van Watt &
Brown, die vol fouten zit).
25
26
Week 5 – Triangle Extensies
Stappenplan. Voordat u begint met het aanpassen van de Triangle compiler is het zaak om eerst
goed na te denken over de aanpassingen aan de Triangle taal:
1. Bedenk hoe de (concrete en abstracte) syntax uitgebreid moet worden voor de betreffende
uitbreiding van Triangle.
2. Geef de context-beperkingen van de nieuwe uitbreiding aan.
3. Beschrijf de semantiek van de nieuwe uitbreiding.
Als u de aanpassingen aan de Triangle taal duidelijk geformuleerd heeft, volgen de aanpassingen
aan de Triangle compiler (in de package directory Triangle) hier haast vanzelf uit.
4. Voeg nieuwe tokens toe door de klasse SyntacticAnalyzer/Token.java aan te passen.
5. Pas eventueel de scanner SyntacticAnalyzer/Scanner.java aan opdat dat de
nieuwe tokens herkend worden.
6. Voeg nieuwe AST-klassen toe in de directory AbstractSyntaxTrees. Zorg er hierbij voor dat de nieuwe AST-klassen een subklasse worden van de meest geschikte ASTsubklasse.
7. Pas de klasse SyntacticAnalyzer/Parser.java aan zodat de nieuwe taaluitbreiding
(d.w.z. de tokens) herkend worden en de nieuwe AST-objecten gegenereerd worden.
8. Zorg dat voor elke nieuwe AST-klasse XYZ er een visitXYZ methode aan de interface
AbstractSyntaxTrees/Visitor.java toegevoegd wordt.
9. Implementeer de context-beperkingen van de taaluitbreiding door nieuwe visitXYZmethoden aan de klasse ContextualAnalyzer/Checker.java toe te voegen.
10. Implementeer de semantiek van de taaluitbreiding door de nieuwe visitXYZ-methoden toe
te voegen aan de code generator klasse CodeGenerator/Encoder.java.
11. (Optioneel) Pas de klasse TreeDrawer/LayoutVisitor.java zodanig aan dat
ook de nieuwe taalconstructies op de juiste manier ‘getekend’ worden door de klasse
TreeDrawer/Drawer.java.
12. Test tenslotte de uitbreiding met enkele Triangle programma’s. Test vooral op randgevallen
en op constructies die niet geldig zijn volgens de contextregels. Op de website van Vertalerbouw staan enkele voorbeeldprogramma’s.
Aangeraden wordt om eerst de scanner en parser helemaal correct te krijgen voordat begonnen
wordt met de Checker en Encoder. Hiertoe moeten de nieuwe visit-methoden van deze twee
Visitor-klassen in eerste instantie leeg gemaakt worden (d.w.z. de body bevat alleen return
null;). De Triangle.Compiler zal dan weliswaar de AST opbouwen en ook de AST twee
keer aflopen, maar niets doen voor nieuwe AST klassen.
+ 5.1.1
(Exercise 9.6a) Voeg een nieuw iteratie-statement toe aan Triangle:
repeat C until E
5.1 Uitbreiden van Triangle
27
Het repeat-until-commando wordt als volgt uitgevoerd. Eerst wordt het commando C
uitgevoerd, waarna de expressie E wordt geëvalueerd. Als de waarde van de expressie E
gelijk is aan true, wordt de iteratie beëindigd, anders wordt de lus weer uitgevoerd. De iteratie blijft net zolang doorgaan totdat E de waarde true oplevert. Merk op dat het commando
C tenminste één keer uitgevoerd wordt. Het type van E moet Boolean zijn.
+ 5.1.2
(Exercise 9.7) Voeg aan de Triangle-taal een case-statement toe:
case E of
IL1 : C1 ;
IL2 : C2 ;
...
ILm : Cm ;
else:
C0
Het case-commando wordt als volgt uitgevoerd. Eerst wordt E geëvalueerd; als de waarde
van E overeenkomt met een ‘Integer-literal’ ILi , dan wordt het commando Ci uitgevoerd.
Als de waarde van E niet overeenkomt met één van de Integer-literals, dan wordt het commando C0 uitgevoerd. De expressie E moet van het type Integer zijn en de verschillende
Integer-literals moeten verschillend zijn. Er zal altijd minstens één ILi -Ci -paar aanwezig
moeten zijn; m.a.w. m ≥ 1.
Bijlage
A
Eindopdracht
P RACTICUM
VB 2010/2011 – Eindopdracht. Tijdens het tweede practische deel van het vak Vertalerbouw
dient een complete vertaler te worden ontwikkeld voor een zelf te definiëren programmeertaal.
Lees deze appendix aandachtig door om te weten wat er van u verwacht wordt. §A.1 geeft een
inleiding op de eindopdracht. §A.2 geeft aan hoe de eindopdracht beoordeeld zal worden. In §A.3
wordt uitgezet waaraan het verslag van de eindopdracht moet voldoen. De laatste twee secties zijn
in het Engels: in §A.4 worden de omschrijvingen van de taalfeatures gegeven en in §A.5 wordt
uiteengezet hoe de compiler getest dient te worden.
A.1
Inleiding
Tijdens de eindopdracht van het vak Vertalerbouw wordt een eigen programmeertaal gedefinieerd
en de bijbehorende vertaler gebouwd. Er wordt daarbij gebruik gemaakt van de compiler-generator
ANTLR , die in de weken 3 en 4 van het practicum ook al voor de eenvoudige taal Calc gebruikt
is. Daarnaast zal er een verslag van de taal en de ontwikkeling van de vertaler worden geschreven.
De eindopdracht wordt beoordeeld op de kwaliteit van de ontwikkelde programmatuur en het
eindverslag. Zie §A.2 voor details.
A.1.1 Randvoorwaarden
Randvoorwaarden voor het ontwikkelen van de compiler zijn de volgende:
Brontaal: zelf te definiëren. Sectie §A.4 geeft de eisen waaraan uw taal(constructies) tenminste moeten voldoen.
A-1
A-2
Bijlage A – Eindopdracht
Targettaal: (in principe) Triangle TAM’s assembler language. U dient hierbij de in week 4
geı̈ntroduceerde TAM.Assembler te gebruiken om TAM-programma’s naar TAM-bytecode
te converteren. Als extra uitdaging zou u ook naar de Java Virtual Machine of naar .NET
kunnen compileren; §A.2 geeft aan hoeveel punten dit extra zou kunnen opleveren.
Compiler generator: ANTLR. U dient ANTLR versie 3.2 te gebruiken1 voor het genereren
van (i) de scanner, (ii) de parser, (iii) de context analyser (als treewalker) en (iv) de code
generator (als treewalker).
Implementatie taal: Java (tenminste versie 5 aka 1.5). Echter, omdat ANTLR ook C++ en C#
kan genereren is het ook toegestaan om C++ of C# als implementatietaal te gebruiken. Er
wordt echter geen ondersteuning geboden voor andere programmeertalen dan Java.
Zorg ervoor dat de ANTLR specificaties zo ‘puur mogelijk blijven’ en zo weinig mogelijk Java
definities bevatten. Alle Java programmatuur die u nodig heeft om uw compiler te implementeren
(b.v. voor de symbol table, context checks, code generatie, error handling, etc.) dienen in aparte
klassen en/of packages terecht te komen. Van deze Java programmatuur hoeft alleen een beknopte
beschrijving in het verslag te worden opgenomen. De complete programmatuur dient wel op een
CD-R worden bijgevoegd.
Als uitgangspunt van uw vertaler zou u naar uw eigen compiler van de Calc-programmeertaal
kunnnen kijken. Let echter wel op: de expressietaal van de eindopdracht verschilt op wezenlijke
punten van het simpele Calc-taaltje.
A.1.2 Globale indeling weken 7 t/m 12
Tijdens de verroosterde practicumuren op woensdagmiddag zijn er studentassistenten aanwezig
voor de begeleiding van de eindopdrachten. Hieronder staat een richtlijn voor de planning van de
implementatie van uw compiler tijdens het tweede deel.
• kw4 wk7 (2011: week 22): scanner en parser
definitie van de programmeertaal: syntax (EBNF grammatica), context-beperkingen
en semantiek;
scanner (= lexer) specificatie in ANTLR;
parser specificatie in ANTLR;
voorbeeld- en testprogramma’s opstellen (zie ook §A.5).
voorbeeldprogramma’s testen (met name de gegenereerde ASTs) met de gegenereerde
scanner en parser;
• kw4 wk8 (2011: week 23): contextchecker
bibliotheek voor de symbol table en acties voor het controleren van context beperkingen.
context checking: specificatie van een treeparser in ANTLR die de context regels van
de AST controleert; deze pass voegt ook identifier informatie aan de AST nodes toe;
context-beperkingen testen met de voorbeeldprogramma’s.
• kw4 wk9 (2011: week 24): codegenerator
1
Het is niet toegestaan om oudere versies van ANTLR te gebruiken.
A.2 Beoordeling
A-3
taalelementen
TAM JVM .NET
basic expression language 6.0
7.0
7.0
+ if en while
6.5
7.5
7.5
+ procedures en functies
7.5
8.5
8.5
+ arrays
8.5
9.5
9.5
Tabel A.1: Beoordeling Vertalerbouw – basiscijfer.
code generatie: specificatie van een treeparser in ANTLR die target code genereert
gegeven de AST gegenereerd door de context checker;
code generatie testen met de voorbeeldprogramma’s
Week 9 is de laatste week dat er begeleiding bij het practicum is.
• kw4 wk10 (2011: week 25): verslaglegging.
• kw4 wk11 en wk12 (2011: weken 26 en 27): eventuele uitloop.
De deadline voor het eindproduct van de eindopdracht Vertalerbouw is woensdag 6 juli 2011.
A.2
Beoordeling
Het cijfer voor de eindopdracht Vertalerbouw hangt af van (i) de taalconstructies die ondersteund
worden in de gedefinieerde taal en de bijbehorende compiler, (ii) de gebruikte doeltaal (TAM,
JVM of .NET), (iii) de kwaliteit van de programmatuur en (iv) het verslag. In §A.4 worden de
randvoorwaarden van de taalconstructies besproken. Alle talen moeten ten minste aan de eisen
van de ‘Basic Expression Language’ van §A.4.1 voldoen.
In Tabel A.1 staat vermeld hoe het basiscijfer voor de eindopdracht Vertalerbouw wordt opgebouwd; vertalen naar JVM of .NET levert dus één punt extra op. De mogelijke uitbreidingen op
de expressietaal dienen in de volgorde van Tabel A.1 geı̈mplementeerd te worden.
Daarnaast zou u kunnen overwegen om extra functionaliteit in uw taal en compiler in te bouwen. Tabel A.2 geeft een overzicht van extra’s die aan de compiler zouden kunnen worden toegevoegd en de bijbehorende waardering in punten. Hoewel het cijfer met deze extra’s theoretisch
boven de 10.0 zou kunnen komen, zal dit in dit praktijk maximaal 10.0 kunnen zijn. Zij opgemerkt dat sommige van de uitbreidingen van Tabel A.2 niet eenvoudig zijn; uw studentassistent
zal niet alle vragen kunnen beantwoorden. Aan de andere kant kunnen sommige uitbreidingen een
extra dimensie aan het practicum Vertalerbouw geven. Extra’s als case-statement, for-statement,
repeat/until-statement worden daarentegen beschouwd als ‘variaties op een thema’ en leveren
geen extra punten op, maar kunnen wel gebruikt worden voor afronding.
Het definitieve cijfer voor de eindopdracht hangt verder af van:
opbouw grammatica en ANTLR specificatie: -1 . . . +1
opbouw Java programmatuur en commentaar: -2 . . . +1
kwaliteit van het verslag: -2 . . . +1
kwaliteit van de tests: -1.5 . . . +1.5
A-4
Bijlage A – Eindopdracht
uitbreiding
TAM JVM .NET
+ enumerated types
+0.25 +0.25 +0.25
+ records
+0.5 +0.5 +0.5
+ pointers
+0.5
+0.5
+ strings
+0.5
+ exception handling
+1.0 +0.5 +0.5
+ dynamic objects, free/delete +1.5 +0.5 +0.5
+ object orientatie (classes)
+1.5 +0.5 +0.5
Tabel A.2: Beoordeling Vertalerbouw – extra functionaliteit.
voor de ANTLR LL(k) parser moet gelden gelden dat k=1; als k>=2, dan kost dit minstens 1
punt op het cijfer voor de eindopdracht.
Zorg dat u zoveel mogelijk de beschrijving van de taal-gedeelten uit §A.4 volgt. Afwijkingen die
de opdracht vereenvoudigen worden streng aangerekend.
Voorbeelden.
Een tweetal studenten implementeert een compiler voor de expressie-taal met if/while voor
de Java Virtual Machine. Daarnaast worden strings aan de compiler toegevoegd. De compiler
is helaas zeer matig getest wat resulteert in een aftrek van 1.0 punt. Het cijfer zou dan zijn:
7.5 + 0.0 - 1.0 = 6.5.
Een tweetal studenten implementeert een compiler voor de expressie-taal voor de Triangle
Abstract Machine. Daarnaast worden enumerated types en records toegevoegd. De verslag
en de tests zijn echter zeer goed, waardoor een punt extra wordt verdiend. Het cijfer zou dan
zijn: 6.0 + 0.25 + 0.25 + 1.0 = 7.5.
De deadline voor het eindproduct van de eindopdracht Vertalerbouw is woensdag 6 juli 2011 om 17.00
uur. Deze deadline voor het inleveren van het verslag is strict. Op het te laat inleveren staat de volgende
sanctie:
A.3
één werkdag te laat: -0.5 punt voor de eindopdracht;
twee werkdagen te laat: -1.0 punt voor de eindopdracht;
drie werkdagen te laat: -1.5 punten voor de eindopdracht;
vier werkdagen te laat: -2.0 punten voor de eindopdracht;
vijf werkdagen te laat: -3.0 punten voor de eindopdracht;
nog later: maximaal een 4.0 voor de eindopdracht.
Verslageisen
Het eindproduct van de eindopdracht Vertalerbouw dient uiterlijk woensdag 6 juli 2011 om 17.00
uur te worden ingeleverd in de doos bij kamer Zilverling 5037 of in het postvakje van Michael
Weber (kamer 5110) op vloer 5 van het Zilverling-gebouw (leerstoel FMT).
Geef op het titelblad van het verslag duidelijk aan wie de auteurs zijn van het verslag. Dit
betekent voor elke student: achternaam, voorletters, studentnummer en adres en de naam van de
studentassistent (plus “herhaler” voor studenten die het vak dit jaar herhalen).
Het eindproduct zelf bestaat uit twee delen:
A.3.1 Programmatuuur
A-5
Een CD-R met de ontwikkelde programmatuur. Hieronder is in meer detail aangegeven wat
zich in ieder geval op de CD-R dient te bevinden. Ook op de CD-R dient duidelijk vermeld
te zijn wie de makers zijn.
Een uitgeprint verslag over de eindopdracht. Hieronder is te lezen wat hier allemaal deel uit
van moet maken.
Daarnaast dient elk verslag vergezeld te worden met, voor iedere student, (i) een ingevuld tentamenbriefje en (ii) een volledig ingevuld evaluatieformulier (beiden zijn beschikbaar bij de kamer
van Michael Weber).
Inleveren per email is niet toegestaan. Wanneer het ingeleverde op wezenlijke punten afwijkt van
het hier gevraagde komt het niet voor beoordeling in aanmerking.
A.3.1 Programmatuuur
De opgeleverde programmatuur (dat wil zeggen, de ingeleverde CD-R) dient de volgende delen te
bevatten:
Een README-file met daarin aanwijzingen voor de installatie en het opstarten van de vertaler,
zoals de voor executie noodzakelijke directories en files en de manier waarop e.e.a. geı̈nstalleerd en aangeroepen dient te worden. Bij het lezen van deze file moet een gebruiker in staat
zijn de vertaler foutloos te genereren, te compileren en op te starten. Indien hieraan niet
voldaan is, komt het programma niet voor beoordeling in aanmerking; niet-compileerbare
programma’s worden niet geaccepteerd.
De volledige ANTLR specificaties en de Java files die door ANTLR gegenereerd zijn;
De Java-code van alle zelfgedefinieerde klassen, in één enkele directory-hiërarchie. De code
dient aan de volgende eisen te voldoen:
–
–
–
–
Foutvrij te compileren;∗
Specificatie (in javadoc) van klassen en methoden;∗
Zinvol gebruik van packages en toegankelijkheden;
Begrijpelijke opmaak en naamgeving, volgens Java-conventies;
De met een sterretje (∗ ) gemarkeerde eisen zijn noodzakelijk om voor de eindopdracht minstens een 4.0 te krijgen.
Documentatie (door javadoc geproduceerd in html) van alle zelfgedefinieerde klassen, in
een eigen directory-hiërarchie (dus niet gecombineerd met de Java-files).
Bytecode van eventuele gebruikte voorgedefinieerde klassen, voor zover het geen zelfgeprogrammeerde of standaard Java-klassen betreft.
Resultaten van alle uitgevoerde tests.
Voor correcte testprogramma’s komt dit per test neer op:
• het correcte programma zelf,
• de gegeneerde TAM-code, en
• enkele testruns (invoer en uitvoer)
Voor incorrecte testprogramma’s komt dit per test neer op:
• het foutieve programma zelf, en
• de uitvoer gegenereerd door de compiler (d.w.z. de gegeneerde foutmeldingen)
A-6
Bijlage A – Eindopdracht
Zorg ervoor dat u het systeem zodanig oplevert dat de beoordelaar na het lezen van de bijgeleverde
README de vertaler kan genereren, compileren en executeren. Het mag dus niet nodig zijn iets in
de source-code te veranderen! Typische gevallen waarin dit fout gaat zijn: namen en paden van
files of andere URLs, zoals van hostmachines van servers. Test dit alvorens uw product op te
leveren.
A.3.2 Verslag
Het verslag moet inzicht geven hoe de taal gedefinieerd is, en hoe de problemen die zich voordeden
bij het maken van de vertaler opgelost zijn. Vermeld ook wie voor welk onderdeel verantwoordelijk is en welke delen samen gemaakt zijn. Het verslag van de practicumopdracht dient in ieder
geval de volgende onderdelen te bevatten:
Inleiding. Korte beschrijving van de practicumopdracht.
Beknopte beschrijving van de programmeertaal (maximaal één A4-tje).
Problemen en oplossingen: uitleg over de wijze waarop je de problemen die je bent tegengekomen bij het maken van de opdracht hebt opgelost (maximaal twee A4-tjes).
Syntax, context-beperkingen en semantiek van de taal met waar nodig nadere uitleg over de
betekenis. Geef de beschrijving bij voorkeur in dezelfde terminologie als die gebruikt is bij
de beschrijving van Triangle in Watt & Brown (hoofdstuk 1 en appendix B).
Vertaalregels voor de taal, d.w.z. de transformaties waaruit blijkt op welke wijze een opeenvolging van symbolen die voldoet aan een produktieregel wordt omgezet in een opeenvolging van TAM-instructies. Vertaalregels zijn de ‘code templates’ van hoofdstuk 7 van Watt
& Brown.
Beschrijving van Java programmatuur. Beknopte bespreking van de extra Java klassen die
u gedefinieerd heeft voor uw compiler (b.v. symbol table management, type checking, code
generatie, error handling, etc.). Geef ook aan welke informatie in de AST-nodes opgeslagen
wordt.
Testplan en -resultaten. Bespreking van de ‘correctheids-tests’ aan de hand van de criteria
zoals deze zijn beschreven in het §A.5 van deze appendix. Aan de hand van deze criteria moet
een verzameling test-programma’s in het taal geschreven worden die de juiste werking van de
vertaler en interpreter controleren. Tot deze test-set behoren behalve correcte programma’s
die de verschillende taalconstructies testen, ook programma’s met syntactische, semantische
en run-time fouten.
Alle uitgevoerde tests moeten op de CD-R aanwezig zijn; van één testprogramma moet de
uitvoer in de appendix opgenomen worden (zie onder).
Conclusies.
In de appendix van het verslag dienen ten minste de volgende onderdelen aan het verslag te worden
toegevoegd:
2
ANTLR Lexer specificatie. Specificatie van de invoer voor de ANTLR scanner generator,
d.w.z. de token-definities van het taaltje.
ANTLR Parser specificatie. Specificatie van de invoer voor de parser generator, d.w.z. de
structuur van de taal en de wijze waarop de AST gegenereerd wordt.2
Net als bij Calc.g is het uiteraard toegestaan om de specificatie van de Lexer en Parser te combineren in
een gezamenlijke grammar specificatie.
A.4 Eindopdrachten
A-7
Alle ANTLR TreeParser specificaties. Waarschijnlijk zult u (tenminste) twee tree parsers
gebruiken: een context checker en een code generator.
Invoer- en uitvoer van één uitgebreid testprogramma. Van één correct en uitgebreid testprogramma (met daarin alle features van uw programmeertaal) moet worden bijgevoegd: de
listing van het oorspronkelijk programma, de listing van de gegenereerde TAM-code (bestandsnaam met extensie .tam) en één of meer executie voorbeelden met in- en uitvoer
waaruit de juiste werking van de gegenereerde code blijkt.
Zorg ervoor dat de bovenstaande listings goed leesbaar zijn; d.w.z. in ieder geval geen linebreaks
in de uitvoer bevatten. Het is toegestaan om de listings in landscape-oriëntatie af te drukken.
Gebruik bij het printen dezelfde tab-stops als u in uw programma-editor gebruikt heeft.
De reden dat de listings van de programmatuur in het verslag moeten worden opgenomen (terwijl ze ook al op de CD-R staan) is tweeledig. Ten eerste geeft het inzicht hoe uw compiler is
opgebouwd; het geeft aan hoe de syntax, contextbeperkingen en vertaalregels in ANTLR zijn uitgewerkt. Ten tweede vergemakkelijkt het de correctie van uw werk: het verslag vormt de primaire
basis van de beoordeling; de programmatuur op de CD-R wordt alleen gebruikt om uw compiler
te testen. Het moet voor de correctie van uw werk niet nodig zijn om uitgebreid de programmatuur op de CD-R te bestuderen; de appendices moeten voldoende inzicht geven in het ontwerp en
opbouw van uw compiler.
A.4
Eindopdrachten
Deze sectie is gebaseerd op sectie “Developing your own language” van [Henk Alblas, Han
Groen, Albert Nymeyer and Christiaen Slot, Student Language Development Environment (The
SLADE Companion Version 2.8), University of Twente, 1998].
In this section we describe a number of versions of a language based on the concept of expressions.
Normally, we differentiate between statements and expressions: the former are used to carry out
actions, and the latter to compute values. In the language that is proposed here, statements not only
carry out actions, they also compute values. Moreover, declarations and statements may occur in
any order. The only restriction is that the declaration of a variable or constant must precede its use.
The student is invited to develop a complete compiler for this language. Using the compiler
generation tool ANTLR, the student has to build
a scanner (= lexer), which translates a stream of characters to a stream of tokens;
a LL(1) parser, which checks whether the syntax of the program is correct and builds a
abstract syntax tree (AST) from the stream of tokens;
a context analyzer (an ANTLR tree parser), which checks whether the scope- and type rules
of language are obeyed; and
a code generator (also an ANTLR tree parser), which generates object code from the AST.
While the student may find suggestions for the syntax of this language in this chapter, the student
is encouraged to choose his or her own syntax. It is suggested that a lexical specification be
developed first, followed by an extended context-free grammar.
We will use three types of data: integer, boolean and character. These data have an external
representation on the keyboard and the screen, and an internal representation in memory. The
boolean and character values are internally represented by integer values. The logical value true
A-8
Bijlage A – Eindopdracht
priority operators
1
(unary) -, +
2
3
4
5
6
valid operand types
int
!
bool
int
*, /, %
+, int
<, <=, >=, >
int
==, <>
int, bool, char
&&
bool
||
bool
result type
int
bool
int
int
bool
bool
bool
bool
Tabel A.3: Arithmetic operators, their relative priorities, and the types of their operands and result.
is internally represented by 1 and the value false by 0. Characters are internally represented
by their ordinal value in the ASCII-set. We begin by describing the basic expression language.
We then describe two extensions to this language: a conditional statement and a while-statement.
These extensions involve extra scope rules. We then present some more complicated extensions:
procedures, functions, pointers (absolute addresses), arrays, and records.
A.4.1 Basic expression language
The basic expression language supports declarations and expressions. Declarations are either constant or variable declarations. An expression can be an arithmetic expression, an assignment
statement, a read statement or a print statement. Every variable and constant must be declared,
and the declaration of a variable or constant (called the defining occurrence) must precede its use
(applied occurrence) in the text.
An arithmetic expression consists of a number of operands separated by operators. An operator
can have the type integer (int), boolean (bool) and character (char). An operand can be a variable,
constant and denotation, and also have the type int, bool and char. Examples of int, bool and char
denotations are 12, true and ’a’ (respectively). Not all types of operands, however, can be used
in combination with all operators. The operators, their relative priorities (from highest to lowest),
and the permitted combination of types is shown in Table A.3. Note that the operators == and <>
are overloaded.
An assignment statement generates a result. This result is the value of the variable on the
left-hand side of the assignment symbol. The type of an assignment statement is the type of its
left-hand side variable. Further, the type of the left-hand side variable must be equal to the type
of the right-hand side. Because an assignment statement has a value, it can be used as a ‘subexpression’ in another statement or expression. Consider the following example:
x := y := x + y;
The value of the assignment statement y:=x+y is the value of x+y. This value is assigned to x,
and is, therefore, also the value of the total statement. Notice that the operator := is implicitly
right-associative. An assignment statement cannot be used everywhere in an expression, however.
The following expression, for example, is not allowed:
x + y := 1 + y;
Depending on their relative priorities, we could evaluate this expression as x+(y:=1)+y, which is
x+2, or as (x+y):=(1+y). Neither is desirable. We avoid this kind of construction by stipulating
that there may only be a single variable on the left-hand side of an assignment operator.
A.4.1 Basic expression language
A-9
A read statement has the general form read(varlist), where varlist is a list of variables
(at least one). A read statement also generates a result. The type of a read statement depends on
what is read:
If only a single variable is read, then the type of the read statement is equal to the type of this
variable, and the result is its value.
If more than one variable is read, then the read statement has type void.
A result that has type void corresponds to a value that cannot be used. More precisely, it corresponds to the empty value. The following expression, for example, is not allowed because the read
statement has type void:
x + read(y, z)
A print statement has the general form print(exprlist), where exprlist is a list of expressions (at least one). A print statement is analogous to a read statement, with the exception that not
only can variables be printed, but also expressions.
Each expression in a print statement must have a type that is not void.
If only a single expression is printed, then the type of the print statement is equal to the type
of this expression, and the result is its value.
If more than one expression is printed, then the print statement itself has type void.
For example, the following statement prints the value of x, and then increments x:
x := print(x) + 1;
In contrast to a statement or expression, a declaration does not generate a result. A declaration is
said to have type no type.
The basic expression language also has a compound expression, which is a sequence of expressions and declarations, separated by semicolons. However, because a compound expression
must also generate a result, we stipulate that a compound expression must end in an expression
(and must not end in a declaration). The result and type, then, of the compound expression is the
result and type of this (final) expression. The scope of any declaration in the compound expression
is the compound expression itself, but declaration must precede use. Consider, for example, the
compound expression that reads two boolean variables and evaluates a boolean expression:
var a: boolean; read(a);
var b: boolean; read(b);
(a && !b) || (!a && b);
This compound expression has type boolean, and generates the exclusive-or of the two variables.
Note that the syntax that we have used here for the declaration is only a suggestion. We could also
have written the declarations using the form var boolean a, for example, or even boolean a.
A compound expression that is enclosed within an open- and close symbol (e.g. using curly
brace: ‘{’ and ‘}’, or begin and end, etc), is called a closed compound expression. The result and
type of a closed compound expression is the same as the result and type of the enclosed compound
expression. Because a closed compound expression generates a result, it can also be an operand.
For example, we can assign the result of the above compound expression to some boolean variable
c as follows:
c := { var a: boolean; read(a);
var b: boolean; read(b);
(a && !b) || (!a && b);
} ;
A-10
Bijlage A – Eindopdracht
Note that in this example we used the curly braces ‘{’ and ‘}’ characters to enclose the compound
expression into a closed compound expression. Further note that the boolean variables a and b are
only defined inside the closed compound expression.
To understand the consequences of treating statements as expressions, particularly from an
implementation point of view, let us look at some program constructs. Consider the following
program fragment:
x := y:= 1; z := 2;
The result of the assignment statement y:=1 is the value 1. This value is then assigned to x. In
a sense, this value is being re-used. The assignment to x also generates a value 1. This value,
however, is redundant and must be discarded. The following assignment statement (z:=2) is then
executed, and generates the value 2. Depending on the context of this fragment, this value may
also have to be discarded. In practice, when an expression is executed, it leaves a value on the
arithmetic stack, ready to be used by another expression. If this value is not used, then it must be
popped off the stack. This situation arises when we have two expressions separated by a semicolon.
3
We will see that there are other situations where values need to be discarded. The value
generated by the last expression in the main program, for example, must also be discarded (the
program does not generate a result). Consider, for example, the following program:
begin
x := 1;
print(x);
end.
When the print-statement is executed, it generates the value of x, namely 1. This value must be
discarded. Care should be taken in building the compiler to ensure that values that are generated
by expressions are either re-used or explicitly discarded.
A.4.2 Conditional statement
We can extend and improve the basic expression language by adding a conditional statement. A
conditional statement adds more expressive power to the language. Like assignment, read and
print statements, a conditional statement generates a result. We can use it in the following way, for
example:
x := if b then 0 else 1 fi;
Depending on the value of b, x will be set to 0 or 1. Because a conditional statement generates a
result, it can be used as an operand. Possible operands, then, in the extended expression language
are conditional statements, closed compound expressions, and variables, constants and denotations
of type integer, boolean and character. The general form of a conditional statement is as follows:
if expr0 then expr1 else expr2 fi
where each expri is a compound expression. Note that this is only a suggested syntax. The else
part (i.e., else expr2 ) in a conditional statement is optional. The following conditions on the
types apply:
3
Instead of always leaving a value on the arithmetic stack (and subsequently discarding the value by popping the
value), the code generator could also be directed to only leave a value on the stack when this value is actually needed.
This is much more elegant and leads to more efficient target code.
A.4.3 While statement
A-11
The type of expr0 must be boolean.
If there is no else part, then the statement has type void.
If there is an else part, then:
• If expr1 and expr2 have the same type, then this is the type of the conditional statement.
• If expr1 and expr2 have different types, then the conditional statement has type void.
Further, the following special scope rules apply.
The scope of declarations in expr0 is all three compound expressions.
The scope of declarations in expr1 is only itself.
The scope of declarations in expr2 is only itself.
A.4.3 While statement
An iterative construct is added to the expression language in the form of a while statement. A
while statement has the general form:
while expr0 do expr1 od
The following conditions on the type and scope apply:
The while statement has type void.
The type of expr0 is boolean.
The scope of declarations in expr0 is both compound expressions.
The scope of declarations in expr1 is only itself.
Because a while statement has type void, it cannot be used as an operand. In fact, a while statement
is an expression, along with assignment statements, read statements and print statements. Consider
the following example of a while statement:
while b do x := x + 1; print(x) od;
The print statement within the while statement generates a result. This result must be discarded
because the while statement has type void.
A.4.4 Procedures and functions
The body of a procedure is a closed compound expression that has type void. This means that a
procedure cannot be an operand. A procedure may have value parameters and reference (= var-)
parameters. Both kinds of parameters are, of course, operands, and can be of type integer, boolean
and character. Recursive calls should be supported.
An example of a declaration and call of a procedure is illustrated the following program:
begin
var a, b: integer;
procedure swap(var x, y: integer) {
var z: integer;
z := x; x := y; y := z;
}
a := 1; b := 2;
swap(a, b);
print(a, b);
end.
A-12
Bijlage A – Eindopdracht
A function is similar to a procedure. The differences are:
A function call is an operand.
The closed compound expression that is the body of the function generates a value. This is
the value returned by the function. The type of this value (either integer, boolean or character)
is also the type of the function.
Recursive function calls should be supported.
An example of a declaration and call of a function is illustrated in the following program:
begin
function fac(n: integer): integer {
return n * fac(n-1);
};
print(fac(10));
end.
A.4.5 Arrays
Arrays allow data to be conveniently structured. For simplicity, we only consider 1 and 2-
dimensional arrays. To declare and use arrays, we add the following language constructs. Note
that the syntax used in the examples shown below is for illustrative purposes only.
type declaration. Array types allow a new array data type to be defined. In the type definition,
bounds are placed on the indices. These bounds must be integer denotations. For example:
type barray = array [1..4] of boolean;
type iarray = array [1..2, 1..2] of integer;
Note that, because the bounds are integers, the bounds can always be statically determined.
variable declaration. Array variables can be declared by using the array type. For example:
var b: barray;
var x, y: iarray;
variable. Array variables and constants can be used in the program text. Arrays can be
assigned (using the assignment operator :=), and they can be compared (the operators ==
and <>). For example:
if x <> y then x := y fi;
where x and y have been declared above as having an array type.
indexed variable. Indexed array variables can be used to access elements in an array. An
index is an integer expression, and is traditionally enclosed in square brackets. For example:
i := 1; x[i] := y[i];
denotation. Array denotations are also possible. For example:
b := [true, false, true, false];
x := [[7, 1], [7, 31]];
will initialise the two arrays declared above.
Array variables and indexed array variables can be used as operands. In the case of array variables,
however, only the operators == and <> can be used. Indexed array variables have the type integer,
boolean and character, and therefore satisfy Table A.3.
A.4.6 Records
A-13
Constant array declarations. Just as ‘variables’ can be declared as constant, it should also be
possible to declare array variables as constant. Consider, for example, the following declaration:
const a: barray = [true, false, true, true];
Note that the implementation of this construct, however, is more difficult than other array constructs.
A.4.6 Records
Records can be seen as a generalisation of arrays. The specification of records, therefore, follows
the same lines as arrays. It involves adding a record type declaration, record variable declaration,
record variables, record field variables (which are analogous to indexed arrays) and record denotations. A record consists of fields, and these fields can be of type integer, boolean and character.
Below we describe how records are declared and used. As with arrays, the syntax used in the
examples is for illustrative purposes only.
type declaration. Record type declarations define the structure of a record. A record consists
of a number of field variables. Each field variable has a certain type. For example:
type mix = record [ a: integer; b: boolean; c: character; ] ;
In this record type declaration, three field variables have been declared.
variable declaration. Record variables can be declared by using the previously declared
record type. For example:
var r, s: mix;
variable. Record variables and constants can be used in the program text. Records can be
assigned (:=) and they can be compared (== and <>). For example:
if r <> s then r := s fi;
field variable. The record field variables are identified by a record and field name, separated
by a dot. For example:
r.a := 1; r.b := false; r.c := ’a’;
denotation. Record denotations can be used to initialise a record. A record denotation consists of the record type, followed by the contents of the fields, and surrounded by square
brackets. For example:
r := [1, true, ’a’] ;
Like variables, constants and denotations, therefore, record variables and field variables are operands. However, only the operators == and <> can be used with record variables. Because field
variables can only have the types integer, boolean and character, field variables satisfy Table A.3.
Constant record declarations. As an optional extra, we could also consider constant record
declarations. Take, for example, the following declaration.
const r: mix = [1, true, ’a’] ;
However, analogous to arrays, the implementation of this construct is more difficult than other
record constructs.
A-14
Bijlage A – Eindopdracht
A.4.7 Pointers
We now add pointers to our expression language. A pointer has a value, which is the address of a
variable, or nil. When a pointer is declared, we must specify the type of the variable to which it
points. For example:
var p, q: pointer to integer;
Pointers are assigned by using an address function, e.g., address(v), where v is a variable. For
example:
p := address(i);
The inverse of this function, e.g., value(p), yields the value pointed to by pointer p. For example:
i := value(p);
where, in this case, the variable i would have to be declared as an integer. The value nil can also
be assigned to a pointer, as in:
q := nil;
Note that nil has no single type, nil can be assigned to a pointer of any type. Like arrays and
records, pointers are variables that can be assigned (e.g. p := q) and compared (e.g. p == q and
p <> q). Note that a pointer to a variable is only valid as long as the variable is within its scope.
A pointer to a variable that has ‘ceased to exist’ is called a dangling pointer. The compiler must
check that pointers do not dangle. Consider, for example, the statement:
p := (var i: integer; i := 17; address(i));
The value of the closed compound expression is the address of i. The pointer dangles because i
is not defined outside the closed compound expression. In general, globally-declared pointers that
point to local variables must not be used outside the scope of these variables.
A.5
Testen
Deze sectie is gebaseerd op sectie “Self-testing your compiler” van [Henk Alblas, Han Groen,
Albert Nymeyer and Christiaen Slot, Student Language Development Environment (The SLADE
Companion Version 2.8), University of Twente, 1998].
To build a compiler, a language specification consisting of a scanner and parser part must first
be developed. This specification must define the syntax of the language that is required. Tree
walkers that perform context analysis and code generation have to be specified as well. The result
of feeding these specifications to ANTLR is a compiler that translates a source program written in
the given language into a target program. The compiler behaves correctly if every target program
conforms to the language specification.
Note that language specifications are themselves not completely formal. The definition of
Triangle (Watt & Brown, 2000, appendix B), for example, uses a formal extended context-free
grammar to define the context-free syntax, but informal natural-language sentences to describe
the context-sensitive syntax and the semantics. To formally verify the correctness of the Triangle
compiler would require a completely formal specification of Triangle, which unfortunately does
not exist.
A.5.1 Basic expression language
A-15
Instead of verifying our compiler, we could increase our confidence in its correctness by applying a series of tests. Note that testing can only ever show the presence of errors, not their absence.
Nevertheless, careful testing is useful and necessary in situations where formal verification is not
possible. The advantage of testing is that it can be carried out independent of the way the compiler
is specified and constructed. Ideally, the tests should consist of all possible programs. Unfortunately, in most languages an infinite number of programs can be written, so all we can hope to do is
to judiciously select a subset of all programs, called a test set, that is in some way representative of
the language. A test set should not only contain correct programs, but also programs that contain
errors, so that we can see how the compiler handles incorrect input. Errors in a program can occur
in the:
lexical syntax (e.g. spelling errors)
context-free syntax (e.g. language-construct errors)
context constraints (e.g. declaration, scope and type errors)
semantics (e.g. run-time errors)
In the next section we will discuss a test set for the basic expression language (see §A.4.1).4
By studying this test set the reader will be able to build an extensive test set for his or her own
language. In subsequent sections we will discuss the construction of test sets for each of the
different extensions to the basic expression language.
A.5.1 Basic expression language
For the basic expression language of §A.4.1 we will construct 4 test programs:
a correct test program
a test program containing spelling and context-free syntax errors
a test program that violates the context constraints
a test program with a run-time error
A correct test program. To test the compiler at the level of syntax a test program must be
written that contains identifiers and denotations of integers and characters, all possible keywords
and symbols, and the booleans true and false. We will include these in a test program in which
also context-free syntax constructs (in the basic expression language these are the declarations and
expressions) are checked. We consider first the declarations and then the expressions, and we distinguish between arithmetic expressions, assignment statements, read statements, print statements
and compound expressions.
Declarations. Every variable and constant must be declared with type integer, boolean or
character. A test program should therefore contain:
var ivar1, ivar2: integer;
var vvar: boolean;
var cvar1, cvar2: character;
const iconst1: integer = 1, iconst2: integer = 2;
const bconst: boolean = true;
const cconst: character = ’c’;
4
The syntax of the language as used in this section just an example. The student is free to choose its own terminals
and symbols for his or her own language.
A-16
Bijlage A – Eindopdracht
begin
var ivar: integer;
ivar :=
{
var ivar1, ivar2: integer;
read(ivar1, ivar2);
write(ivar1, ivar2);
const iconst1: integer = 1;
const iconst2: integer = 2;
ivar2 := ivar1 := +16 + 2 * -8;
write(ivar1 < ivar2 && iconst1 <= iconst2,
iconst1 * iconst2 > ivar2 - ivar1);
ivar1 < read(ivar2) && iconst1 <= iconst2;
ivar2 := write(ivar2) + 1;
} + 1;
var bvar: boolean;
bvar :=
{
var bvar: boolean;
read(bvar);
write(bvar);
bvar := 12 / 5 * 5 + 12 % 5 = 12 && 6 >= 6;
const bconst: boolean = true;
write(!false && bvar == bconst || true <> false);
} && true;
var cvar: character;
cvar :=
{
var cvar1, cvar2: character;
read(cvar1);
const cconst: character = ’c’;
cvar2 := ’z’;
write(’a’, cvar1 == cconst && (cvar2 <> ’b’ || !true));
’b’;
};
write(ivar, bvar, cvar);
end.
Figuur A.1: Test program that checks for correct syntax.
All variables and constants used in a program must be declared. We can use the above-mentioned
declarations in a larger test program to check this. We will do this later.
Operators and operands. Table A.3 shows the operators, their relative priorities, and the
operand and result types. The following expression is a test of the relative priorities of +, - and *,
for example.
+16 + 2 * -8
Expressions with a boolean result type can be built using operands of different types. For example:
12 / 5 * 5 + 12 % 5 == 12 && 6 >= 6
ivar1 < ivar2 && iconst1 <= iconst2
iconst1 * iconst2 > ivar2 - ivar1
cvar1 = const && (cvar2 <> ’b’ || !true)
!false && bvar = bconst || true <> false
A.5.1 Basic expression language
A-17
Note that in these expressions we use all operators and all possible operand types. Finally, a simple
character expression:
’b’
Assignments. There can be simple and multiple assignment statements. For example:
ivar2 := ivar1 := +16 + 2 * -8;
bvar := 12 / 5 * 5 + 12 % 5 == 12 && 6 >= 6;
cvar2 := ’z’;
Read and print. A read statement reads a list of values of variables. Some examples are:
read(ivar1, ivar2);
read(bvar);
read(cvar1);
A print statement can be more complicated as whole expressions must be handled. For example:
write(ivar2);
write(bvar);
write(!false && bvar == bconst || true <> false);
write(ivar1, ivar2);
write(ivar1 < ivar2 && iconst1 <= iconst2,
iconst1 * iconst2 > ivar2 - ivar1);
write(’c’);
write(’a’, cvar1 = cconst && (cvar2 <> ’b’ || !true));
In the following example we check that read and print statements can also occur as operands in an
expression.
ivar1 < read(ivar2) && iconst1 <= iconst2;
ivar2 := write(ivar2) + 1;
Compound expressions. We now consider compound expressions, which are sequences of expressions and declarations, separated (or terminated) by semicolons. Declarations and expressions
may occur in any order as long as declarations of variables and constants always precede their use,
and the compound expression ends in an expression.
In Figure A.1 we show our first test program. It contains three compound expressions composed
from the language constructs that we have discussed so far. The program consists of three assignment statements. The right-hand side of each assignment is a closed compound expression, and
each closed compound expression introduces a scope and delivers a value.
Note that we use the symbols ‘{’ and ‘}’ to enclose a compound expression. We could have
used other syntax here instead (e.g. using the keywords begin and end).
Sample input for this program is: 0 1 1 false c,
which generates the output: 0 1 false true 1 false true a true 3 true b.
A test program containing spelling and context-free syntax errors. In the initial stage of
program development simple syntax errors occur frequently. These range from spelling mistakes
to incorrect program constructs. The scanner and parser generator provide for error recovery, i.e.,
they add special functions to the generated scanner and parser to detect and recover from these
errors. To see how the generated compiler handles these errors we could use the small incorrect
program shown in Figure A.2. Note that Java-style line comments are used are used to specify
comments (i.e., // . . . ).
A-18
Bijlage A – Eindopdracht
begin
var a, b: integer;
// an error in an expression:
a + * b;
// an incomplete assignment token:
a :-b;
// non-existing and misspelled keywords:
for gebin ned repeat;
// hurray, finally something good:
a + b;
END.
Figuur A.2: A test program that contains spelling and syntax errors.
A test program that violates the context constraints. This can be a difficult source for errors
because errors in the context constraints are usually concerned with the actions in the context
analyzer. We highlight here a few of the more common error conditions, and give a test program
to test for these errors.
Incorrect assignments. There may only be a single variable on the left-hand side of an assignment
operator, and constants, denotations and expressions are not allowed. Some incorrect assignments
are:
var x, y: integer;
const z: integer = 1;
z := 10; 12 := 10;
x + y := 10;
Type errors. Operators must be applied to operands of the correct type. Below we present some
incorrect combinations.
- ’a’;
+ true;
var c: character;
!c;
var b: boolean;
b + 10 * c % 2;
’a’ < c;
b && 10 || c;
De binary operators == and <> are overloaded, i.e., they may be applied to integers, booleans
and characters, but both operands of these operators must be of the same type. The following
combinations are therefore not allowed (see the aforementioned declarations).
’a’ == b;
b <> 10;
x + y == c;
The left and right-hand sides of an assignment must be of the same type. The following assignments are therefore incorrect.
A.5.2 Conditional statement
A-19
c := x + y;
b := 10;
Missing declarations. The declaration of a variable or constant must precede its use. In the
following program fragment the variable or constant p is not declared, and the declaration of q
comes too late.
p; q;
var q: boolean;
A compound expression without a result. Declarations and expressions may occur in any order.
However, because a compound expression must generate a result, a compound expression must
end in an expression. The following closed compound expression ends in a declaration, and is
therefore incorrect.
{ 2 + 4 * 3; var w: integer; }
Operations on operands of type void. Operators may not be applied to operands of type void. The
following constructs are therefore not allowed.
read(x, y) + 10;
’c’ <> write(x, y);
10 + { var u, v: integer; read(u, v); write(u, v); };
Note that in the last line of the program, the statement write(u, v) delivers a result of type
void, and as a result, the closed compound expression is of type void. We now combine all the
above erroneous constructs into one test program that checks for violation of context constraints.
We show this program in Figure A.3. Note, however, that it usual more convenient to store the
individual tests in small test programs to ease the unit testing of your compiler.
A test program with a run-time error. We complete our test set with a program that contains
a run-time error. In the case of simple languages (i.e., those without conditional and repetitive
statements, procedures, functions, and structured variables), the context analyzer could check at
compile time whether all variables appearing in an expression have been assigned. This means
that division by 0 (or using the MOD-operator) is the only thing that might go wrong during the
execution of a program in the basic expression language. The program in Figure A.4 exhibits this
run-time error. Other run-time errors can occur in more involved languages. For example, the use
of non-assigned variables, index of an array out of bounds, and reference to a non-initialised or
null pointer.
A.5.2 Conditional statement
If the basic expression language is extended with a conditional statement, then some tests need to
be developed that check combinations of the conditional statement and other constructs of the basic
expression language. Because of the special type conditions, the use of a conditional statement
as an operand requires special attention. Furthermore, the scope rules that apply to conditional
statements must be checked.
A.5.3 While statement
The extension of the basic expression language with a while statement requires similar tests on the
type and scope. The fact that a while statement has void, and thus cannot be used as an operand,
A-20
Bijlage A – Eindopdracht
begin
var x, y: integer;
const z: integer = 1;
z := 10;
12 := 10;
x + y := 10;
- ’a’;
+ true;
var c: character;
!c;
var b: boolean;
b + 10 * c % 2;
’a’ < c;
b && 10 || c;
’a’ = b;
b <> 10;
x + y = c;
c := x + y;
b := 10;
p; q;
var q: boolean;
{ 2 + 4 * 3; var w: integer; };
read(x, y) + 10;
’c’ <> write(x, y);
10 + { var u, v: integer; read(u, v); write(u, v); };
end.
Figuur A.3: Test program that checks context constraints.
begin
10 / 0
end.
Figuur A.4: Test program that contains a run-time error.
requires special attention. Furthermore, the scope rules that apply to the boolean expression should
be taken into account.
A.5.4 Procedures and functions
For a language with procedures we again need more test programs. The body of a procedure is
a closed compound expression. The tests that we applied to a closed compound expression can
therefore also be used to check procedure bodies. However, because a procedure body is of type
void, a procedure cannot be used as an operand.
The concept of a procedure introduces new scope rules. These scope rules will require extra
tests to check the visibility of variables and constants. The visibility of a procedure name is
A.5.5 Arrays
A-21
similar to the visibility of a variable, i.e., a procedure may only be called within the scope of its
declaration, and a procedure must be declared before it is called. The scope of a parameter is the
block of the procedure declaration.
The correspondence between the arguments of the call statement and the parameters of the
procedure declaration (i.e., the number and types of the arguments) is another aspect that needs
to be tested. Special attention should be paid to the correct use of value and reference (= var) parameters. In the case of a value parameter the argument must be an expression, and in the
case of a reference parameter, the argument must be a variable or a reference parameter from a
surrounding procedure. Finally, note that a procedure can call itself recursively.
Functions are similar to procedures and thus require similar tests. The main differences are
that a function call is an operand, and the body of a function generates a return value, which must
be of the same type as the function.
A.5.5 Arrays
Adding arrays to the the basic expression language requires test programs that check whether array
types can be defined, and array variables can be declared by using these array types. Moreover,
indexed array variables can be used to access elements in an array, and these indexed variables can
be assigned and used in expressions.
The use of array indices requires bound checking at run-time. This means that run-time tests
are needed to check if the bound-checking algorithm of the compiler is correct. Special tests are
required for array variables and constants (denotations) because complete arrays can be assigned
and compared.
A.5.6 Records
Records are a generalisation of arrays and require similar tests. However, the definition of field
variables and the form of their assignment and use are different.
A.5.7 Pointers
In a sense, pointers are similar to variables. They need to be declared before use, they point to
variables of a certain type, and they can be used and assigned. The difference is that they have
in fact two values, a direct value (an address value or nil) and an indirect value (the value of
the variable pointed to). A test set for pointers should test the address function, its inverse, the
assignment and comparison of address values, and the assignment and use of the value nil. A
pointer to a variable is only valid as long as the variable is within its scope. Special tests are
needed to check how the compiler handles dangling pointers.
Download