Programmiersprachen und ¨Ubersetzer

Werbung
Institut für Praktische Informatik
Fachgebiet Programmiersprachen und Übersetzer
Prof. Dr. Rainer Parchmann
Programmiersprachen und Übersetzer - SS 2009
Übungsblatt 2 vom 15.05.2009 (praktische Übung)
Abgabe der Lösungen am 12.06.2009 per Email an [email protected]
Aufgabe 1
(8 Punkte)
Implementieren Sie einen Recursive-Descent-Parser zur Überprüfung der Syntax von bps und stellen
Sie eine Rechtsableitung in umgekehrter Reihenfolge her. Erweitern Sie hierfür die vorhandene Klasse
bpsc.syntactical.SimpleRecursiveDescentParser. In dieser Klasse ist eine Hilfsmethode useProduction
implementiert, die zur Erzeugung der Rechtsableitung dienen soll. Dazu muss, nachdem eine Produktion vollständig geparst wurde, die Funktion useProduction mit der entsprechenden Produktionsnummer
aufgerufen werden. Für die Produktion mit der Nummer (0) könnte das wie folgt aussehen:
private void parseProgram() throws CompilerException {
if (scanner.getCurrent() != TokenType.PROGRAM)
throw new SyntaxException(scanner.getRow(), scanner.getCol(), "keyword program expected");
if (scanner.nextToken() != TokenType.IDENT)
throw new SyntaxException(scanner.getRow(), scanner.getCol(), "identifier expected");
if (scanner.nextToken() != TokenType.SEMIK)
throw new SyntaxException(scanner.getRow(), scanner.getCol(), "’;’ expected");
scanner.nextToken();
parseDeclarations();
parseCompoundStatement();
if (scanner.getCurrent() != TokenType.POINT)
throw new SyntaxException(scanner.getRow(), scanner.getCol(), "’.’ expected");
scanner.nextToken();
useProduction(0);
}
An dem Beispiel sieht man auch, dass im Fall eines syntaktisch nicht-korrekten Programms eine Fehlermeldung vom Typ bpsc.syntactical.SyntaxException ausgelöst werden soll.
Getestet werden kann die korrekte Funktionsweise des Parsers mit Hilfe des JUnit-Tests bpsc.syntactical.SimpleRecursiveDescentParserTest. Es kann jedoch nicht von der korrekten Abarbeitung der Tests
auf eine korrekte Funktionsweise des Parsers geschlossen werden.
Aufgabe 2
(6 Punkte)
Implementieren Sie nun den für bps eigentlich benötigten Recursive-Descent-Parser bpsc.syntactical.RecursiveDescentParser, der nicht nur die Syntax eines Programms überprüft, sondern zusätzlich den
Zwischencode für die weiteren Phasen des Compilers erzeugt. Verwenden Sie als Vorlage den Parser aus
Aufgabe 1 und erweitern Sie die Parsing-Methoden zusätzlich um die Erzeugung des Zwischencodes. Als
weitere Hilfestellung existiert der generierte Parser bpsc.syntactical.GeneratedParser, der auf Basis
des Parser-Generators cup erzeugt wurde.
Der Zwischencode im bps-Compiler wird als Syntax-Bäume realisiert. Die Implementation befindet sich
im Package bpsc.il und besitzt folgende Struktur:
Seite 1
bpsc.il.Node:
Diese Klasse repräsentiert den allgemeinen Knotentyp.
bpsc.il.Program:
Dieser Knotentyp entspricht dem Wurzelknoten nach dem Parsen eines bps-Programms. Er
enthält Informationen über den Namen, die auf Basis der Definitionen erstellte Symboltabelle
und das Statement, das das auszuführenden Program darstellt.
bpsc.il.Expression:
Mit diesem Knotentyp werden sämtliche Berechnungen in bps gekapselt.
bpsc.il.Binary:
Dieser Knotentyp entspricht einer Berechnung mit einem binären Operator. Ein Knoten
dieses Typs enthält Informationen über die verwendete Operation (vom Typ bpsc.il.Operation)
sowie den linken und den rechten Operanden.
bpsc.il.LValue:
Dieser Knotentyp stellt all die Ausdrücke dar, die links von einer Wertzuweisung stehen
dürfen.
bpsc.il.Array:
Mit Hilfe eines Knotens von diesem Typ werden Arrayzugriffe dargestellt. Sie besitzen
die Information über das Symbol, das als Array verwendet werden soll und über die
Berechnung des Index.
bpsc.il.Identifier:
Dieser Knotentyp kapselt einen einfachen Zugriff auf ein Symbol.
bpsc.il.Negation:
Mit diesem Knoten wird der unäre Operator - gekapselt.
bpsc.il.Number:
Dieser Knotentyp stellt die Verwendung einer numerischen Konstante dar. In einem NumberKnoten wird zusätzlich der Wert der Konstanten gespeichert.
Statement:
Dieser Knotentyp kapselt eine beliebige bps-Anweisung.
bpsc.il.Assignment:
Mit diesem Knotentyp werden Wertzuweisungen dargestellt. Als zusätzliche Informationen
werden die Variable oder das Array als bpsc.il.LValue, die Berechnung des neuen Wertes
und die Operation, die bei der Wertzuweisung ausgeführt werden soll, gespeichert.
bpsc.il.Compound:
Dieser Knotentyp kapselt eine Menge von beliebigen Statements.
bpsc.il.If:
Mit diesem Knotentyp können if-Abfragen dargestellt werden. Dazu werden zusätzlich
die Bedingung sowie die Anweisung, die im Wahr-Fall ausgeführt wird und optional die
Anweisung, die im Falsch-Fall ausgeführt wird, gespeichert.
bpsc.il.Read:
Dieser Knoten kapselt eine read-Anweisung. Als zusätzliche Information wird der LValue
benötigt, in den der eingelesene Wert gespeichert wird.
bpsc.il.While:
Dieser Knotentyp wird verwendet um eine while-Schleife zu kapseln. Als Informationen
werden die Abbruchbedingung und der Schleifenrumpf mit im Knoten gespeichert.
bpsc.il.Write:
Mit einem Knoten dieses Typs werden Ausgaben dargestellt. Als zusätzliche Information
wird die Berechnung der Ausgabe gespeichert.
Das Beispiel aus Aufgabe
1
würde mit Zwischencode-Erzeugung wie folgt aussehen:
private Program parseProgram() throws CompilerException {
if (scanner.getCurrent() != TokenType.PROGRAM) throw ...
Program program = new Program(scanner.getRow(), scanner.getCol(), null, symtab, null);
Seite 2
if (scanner.nextToken() != TokenType.IDENT) throw ...
program.setName((String) scanner.getValue());
if (scanner.nextToken() != TokenType.SEMIK) throw ...
scanner.nextToken();
parseDeclarations();
program.setStatement(parseCompoundStatement());
if (scanner.getCurrent() != TokenType.POINT) throw ...
scanner.nextToken();
return program;
}
Zusätzlich sollen Variablen im Definitionsteil in die Symboltabelle eingefügt und überprüft werden, ob bereits ein Symbol mit gleichem Namen existiert. Falls dies der Fall ist, soll ein bpsc.semantical.SemanticalException
ausgelöst werden. Bei der späteren Verwendung von Variablen soll dann in der Symboltabelle nach den
definierten Symbolen gesucht werden und falls ein nicht-definiertes Symbol verwendet wird, ebenfalls eine
Fehlermeldung ausgegeben werden.
Getestet werden kann die korrekte Funktionsweise des Parsers mit Hilfe des JUnit-Tests bpsc.syntactical.RecursiveDescentParserTest.
Aufgabe 3
(3 Punkte)
Erweitern Sie die Programmiersprache bps um ein leeres Statement während eines Compound-Statements,
d.h. es gibt zusätzlich die folgenden Produktionen:
<opt-statement> → <statement>
<opt-statement>
|
Die Definition von <statement-list> ändert sich wie folgt:
<statement-list>
→ <opt-statement> <statement-list-rest>
<statement-list-rest> → semik <opt-statement> <statement-list-rest>
|
Dadurch ist auch folgendes Programm ein syntaktisch korrektes:
program emptystatement;
begin
;; write 10;;
end.
Aufgabe 4
(5 Punkte)
Erweitern Sie die Programmiersprache bps um ein switch-Statement im Stil von Java oder C, so dass
folgendes Progamm richtig verarbeitet wird:
program switchIt;
var i, j;
begin
i := 5;
j := 3;
switch (i)
case 5:
write j
case j:
write i
case else:
begin
write i;
Seite 3
write j;
end
end switch
end.
Auch komplett leere switch-Statements oder Statements ohne else sollten möglich sein.
Bemerkungen
• Exportieren Sie für die Abgabe dieses Aufgabenblattes am Besten einmal den Stand nach Aufgabe
2 und einmal den Stand nach Aufgabe 4 und schicken beide Versionen per Email.
• Die Auswahl des manuellen Parsers beim Ausführen des Compilers können Sie beeinflussen über
die Zeile 34 in der Klasse bpsc.bpsc:
– Verwenden Sie die folgende Zeile, wenn Sie den manuellen Scanner verwenden wollen:
AlgorithmType parsertype = AlgorithmType.MANUAL;
– Um den generierten Scanner zu verwenden, muss die Zeile wie folgt aussehen:
AlgorithmType parsertype = AlgorithmType.GENERATED;
• Verwenden Sie folgende Grammatik zum Parsen von bps:
Seite 4
(0)
<program>
→
program ident semik <declarations> <compound-statement> point
(1)
(2)
<declarations>
→
|
var <variable-list> semik
(3)
(4)
(5)
<variable-list>
<variable-list-rest>
→
→
|
<variable> <variable-list-rest>
comma <variable> <variable-list-rest>
(6)
(7)
(8)
<variable>
<opt-arraysize>
→
→
|
id <opt-arraysize>
lbrace number rbrace
(9)
<compound-statement>
→
begin <optional-statement> end
(10)
(11)
<optional-statement>
→
|
<statement-list>
(12)
(13)
(14)
<statement-list>
<statement-list-rest>
→
→
|
<statement> <statement-list-rest>
semik <statement> <statement-list-rest>
(15)
(16)
(17)
(18)
(19)
(20)
(21)
(22)
<statement>
→
|
|
|
|
|
→
|
<lvalue> assign-op <expression>
<compount-statement>
if <expression> then <statement> <opt-else>
while <expression> do <statement>
read <lvalue>
write <expression>
else <statement>
(23)
(24)
(25)
<lvalue>
<opt-index>
→
→
|
ident <opt-index>
lbrace <expression> rbrace
(26)
(27)
(28)
(29)
(30)
(31)
(32)
(33)
(34)
(35)
(36)
(37)
(38)
<expression>
<rel-expression>
→
→
|
→
|
→
|
→
|
|
|
→
|
<rel-expression>
<add-expression>
<rel-expression> rel-op <add-expression>
<mul-expression>
<add-expression> add-op <mul-expression>
<simple-expression>
<mul-expression> mul-op <simple-expression>
<sign> <simple-expression>
number
<lvalue>
lbracket <expression> rbracket
"+"
"-"
<opt-else>
<add-expression>
<mul-expression>
<simple-expression>
<sign>
Seite 5
Herunterladen