Compiler für unsere eigene Programmiersprache

Werbung
Compiler für unsere eigene Programmiersprache
Eigene Programmiersprache
Im letzten Praktikum haben Sie einen Scanner und einen Parser programmiert,
welche arithmetische Ausdrücke mit natürlichen Zahlen auswerten und daraus
Bytecode erzeugen konnte.
Jetzt wollen wir einen Schritt weiter gehen und den Parser so ergänzen, dass er
unsere eigens kreierte Programmiersprache verstehen kann. Diese
Programmiersprache soll neben der Auswertung von arithmetischen Ausdrücken
auch Zuweisungen und Sprünge (unbedingte und bedingte) verarbeiten können.
Die Programmiersprache soll konkret folgender Grammatik entsprechen:
expr = term {("+" | "-”) term}
term = factor {("*" | "/") factor}
factor = number | ident | "(" expr ")"
assignement = ident "=" expr
statement = [number] (assignment | condition | "goto" number)
condition = "if" ["not"] expr "goto" number
program = statement {statement}
Assignement: einer lokalen Variablen wird ein Ausdruck zugewiesen, z.B. a = 6+2.
Zusätzlich sollen in Ausdrücken (expr) ebenfalls Variablen zugelassen werden, z.B. i
=i -1
Statement: eine Anweisung, die optional mit einem Label (als Nummer) beschriftet
ist. Über diese Nummer kann mit goto auf diese gesprungen werden, z.B. 10 a = 6
+ i oder goto 10.
Condition: überprüft einen Wert auf 0 oder ungleich 0. Wird die Bedingung erfüllt
(Wert != 0), wird mit goto number zur gewünschten Anweisung gesprungen, z.B. if i
goto 10
Program: besteht aus einer oder mehreren Anweisungen.
Aufgabe 1
Als erstes sollen Sie den Parser so erweitern, dass auch lokale Variabeln
gespeichert und verarbeitet werden können. Für diesen Zweck müssen Sie factor
entsprechend erweitern und die Zuweisung implementieren.
Eine solche Zuweisung besteht grundsätzlich aus einem Variabelnamen (der ein
Speicherplatz zugewiesen wurde) und einem Ausdruck. Damit der Parser weiss,
welcher Speicherplatz für welche Variabel reserviert wurde, wird eine Liste (locals)
geführt.
Beispiel-Programm
x = arg0
a = 1
b = 2
c = 3
return = a*x*x + b*x + c
Die Parameter seinen in den vordefinierten Variablen arg0, arg1, … und der
Rückgabe Wert in return gespeichert (werden von codeGen automatisch definiert).
Hinweis:
Die Methode local liefert die Nummer des Slots der benannten lokalen Variablen
oder erzeugt bei Bedarf eine neue Slot-Nummer; diese können für die
entsprechenden Load und Store Operation verwendet werden.
Aufgabe 2
Die neue Programmiersprache soll auch Bedingungen verarbeiten können.
Das Konstrukt einer Bedingung sieht folgendermassen aus:
"if" ["not"] expr "goto" number
•
"if" soll prüfen, ob ein Ausdruck ungleich 0 ist und wenn dies zutrifft mit
goto zu einem durch number bestimmten Label im Programm springen
(ähnliche wie C). Für die Auswertung soll der Ausdruck vor dem 0-Test in
eine Ganzzahl umgewandelt werden (D2I Anweisung).
•
"if not" soll prüfen, ob ein Ausdruck gleich 0 ist und wenn dies zutrifft mit
goto zu einem durch number bestimmten Punkt im Programm springen
Ergänzen Sie als erstes die Methode condition(), damit die beiden erwähnten
Fälle verarbeitet werden können.
Auf die Kondition folgt ein goto number. Die number zeigt auf eine bestimmte
Stelle im Code.
Nun kann es sein, dass die number auf eine Passage im Code zeigt, die erst folgt
d.h. sie wissen im Moment der Codeerzeugung gar noch nicht, wo sich diese
Passage befindet bzw. wohin Sie springen müssen. Es hat sich als praktisch
erwiesen, zuerst in allen Fällen einmal Code zu erzeugen, der noch ungültige (=null)
Zieladressen (targets) enthält. Die später zu korrigierenden Instruktionen
(Oberklasse: BranchInstruktion) werden sich in einer Hashtable gotos gemerkt und in
einer fixup-Methode ergänzt - in der nächsten Aufgabe.
Beispiel-Code – output = Summe von 1 bis 10
m = 10
return = 0
10
return = return + m
m = m - 1
if m goto 10
Aufgabe 3
Ihr Parser ist jetzt im Stand Bytecode zu erzeugen. Damit der Parser wirklich
funktionieren kann, müssen Sie noch das am Schluss von Aufgabe 2 erwähnte
"Goto-Problem" lösen.
Ein Statement kann optional mit einer Nummer beschriftet werden.
statement = [number] (assignment | "goto" number)
Mit goto number kann dann zu dieser Code-Passage gesprungen werden.
Der Parser generiert eine Hashtable (labels), welche alle mit einem Label
versehenen Statements speichert.
In Aufgabe 2 haben wir alle goto’s (BranchInstruction) zwischengespeichert jedoch
noch keine Ziele angegeben. Da jetzt der ganze Code geparst und erstellt wurde,
können jetzt alle richtigen Zieladressen zugewiesen werden. Ergänzen Sie die
Methode fixup() damit jede Sprunganweisung eine korrekte Zieladresse bekommt.
Hinweis:
•
Bei jedem il.append wird ein InstructionHandle zurückgegeben, der sich für die
Zieladresse gemerkt werden kann. Jedes Label definiert ein mögliches Ziel einer
Sprungstelle.
•
setTarget der Klasse BranchInstruction kann verwendet werden, um die Adresse
nachträglich zu setzen.
Aufgabe 4
Nun sollten Sie einen funktionstüchtigen Parser besitzen, welcher die Syntax der
neuen Programmiersprache in Bytecode übersetzten kann.
Schreiben sie zur Kontrolle ein Programm, das die Fakultät einer Zahl berechnet.
Herunterladen