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