Prof. Dr. A. Poetzsch-Heffter Dipl.-Inform. M. Gawkowski Technische Universität Kaiserslautern Fachbereich Informatik AG Softwaretechnik Übungsblatt 5: Übersetzer und sprachverarbeitende Werkzeuge (SS 2007) Ausgabe: Abgabe: 18. Mai 2007 25. Mai 2007 Zum Zwecke der nachfolgenden Übungen definieren wir eine Minisprache Let1 : 1. Schritt: Die abstrakte Syntax: Wir spezifizieren die abstrakte Syntax für unsere Sprache und speichern diese in der Datei spec.katja: 1 specification AbstractSyntax 2 3 4 5 6 7 backend java { package Expr.Example import java.lang.String import java.lang.Integer } 8 9 10 external String external Integer 11 12 13 // ein Beispiel fuer ein Kommentar /* ein Beispiel fuer ein Kommentar */ 14 15 Exp = Let | Var | Const | Plus | Mult 16 17 18 19 Let (VarDefs vardefs, Exp exp) VarDefs * VarDef VarDef (Var var, Exp exp) 20 21 22 Var (String name) Const (Integer value) 23 24 25 Plus (Exp left, Exp right) Mult (Exp left, Exp right) 26 27 28 29 Vars * Var Binding (Var var, Const constant) Env * Binding Anschließend geben wir spec.katja als Eingabe für das katja-System2 : gawkowsk@tux1 [LetDemo] java15 -jar katja.jar -b java -o spec.katja -j katja erzeugt eine jar-Datei AbstractSyntax.jar. Die Datei enthält zu jeder in spec.katja definierten Sorte S jeweils eine java- und eine class-Dateien: S.java und S.class. Die Verwendung des Interfaces der Klassen wird im Folgenden anhand eines kleinen Beispiels demonstriert. 2. Schritt: Die Parser-Spezifikation: Wir spezifizieren die konkrete Syntax für unsere Sprache und speichern die Spezifikation in der Datei spec.cup: 1 Alle nachfolgenden Spezifikations- und java-Dateien sowie alle anderen Dateien, die Sie zum Lösen der Aufgaben auf diesem Übungsblatt benötigen, finden Sie auf der Vorlesungsseite “Materialien” in der Datei LetDemo.tar. 2 Die Implementierung des Java Programms LetDemo wurde auf dem Rechner tux1.informatik.uni-kl.de durchgeführt. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java_cup.runtime.*; import Expr.Example.*; import katja.common.*; terminal LET, IN, END, VAL, PLUS, MULT, EQ; terminal java.lang.Integer CONST; terminal java.lang.String VAR; non non non non terminal terminal terminal terminal Exp mytree; Exp expr; VarDefs vardefs; VarDef vardef; precedence left PLUS; precedence left MULT; mytree ::= expr: e expr {: RESULT = e; :}; ::= LET vardefs:l IN expr:e END {: RESULT = AbstractSyntax.Let(l,e); | :} 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 VAR:v {: RESULT = AbstractSyntax.Var(v); :} | CONST:i {: RESULT = AbstractSyntax.Const(i); :} | expr:e1 PLUS expr:e2 {: RESULT = AbstractSyntax.Plus(e1,e2); :} | expr:e1 MULT expr:e2 {: RESULT = AbstractSyntax.Mult(e1,e2); :} ; vardefs ::= vardefs:vdl {: RESULT | {: RESULT ; vardef ::= VAL VAR:v {: RESULT ; vardef:vd = vdl.appBack(vd); :} = AbstractSyntax.VarDefs(); :} EQ expr:e = AbstractSyntax.VarDef(AbstractSyntax.Var(v),e); :} Anschließend geben wir spec.cup als Eingabe für den Parser-Generator CUP: gawkowsk@tux1 [LetBsp] java15 -cp java_cup.jar java_cup.Main spec.cup CUP generiert zwei java-Dateien: parser.java (hier nicht gezeigt, siehe das tar-Archiv LetDemo.tar) und sym.java 1 2 3 4 5 6 7 8 9 10 11 //---------------------------------------------------// The following code was generated by CUP v0.10k // Wed May 16 22:00:37 CEST 2007 //---------------------------------------------------/** CUP generated class containing symbol constants. */ public class sym { /* terminals */ public static final int IN = 3; public static final int MULT = 7; 12 13 14 15 16 17 18 19 20 21 22 public public public public public public public public public static static static static static static static static static final final final final final final final final final int int int int int int int int int EQ = 8; EOF = 0; PLUS = 6; VAR = 10; error = 1; VAL = 5; LET = 2; END = 4; CONST = 9; } 3. Schritt: Die Scanner-Spezifikation: Wir spezifizieren die lexikalische Syntax für unsere Sprache und speichern die Spezifikation in der Datei Yylex: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import java_cup.runtime.Symbol; %% %cup %eofval{ return new Symbol(sym.EOF); %eofval} WHITESPACE=[\ \t\n\r]|\r\f ALPHA=[A-Za-z] DIGIT=[0-9] %% {WHITESPACE}+ { System.out.println("WHITESPACES"); } "let" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.LET); } "in" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.IN); } "val" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.VAL); } "end" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.END); } "*" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.MULT); } "+" { System.out.println(yytext().toUpperCase()); return new Symbol(sym.PLUS); } \= { System.out.println(yytext().toUpperCase()); return new Symbol(sym.EQ); } {ALPHA}({ALPHA}|{DIGIT})* { System.out.println("IDENTIFIER: " + yytext()); return new Symbol(sym.VAR, yytext());} {DIGIT}+ {return new Symbol(sym.CONST, Integer.decode(yytext()));} . { System.out.println("unrecognized input" + "("+yytext()+")"); return new Symbol(sym.EOF); } 4. Schritt: “Glue-Code”: Wir implementieren ein Java-Programm, die Klasse LetDemo, welche die Verwendung des Interfaces der von Katja-System generierten Klassen demonstriert: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import static Expr.Example.AbstractSyntax.*; import java.io.BufferedReader; import java.io.FileReader; import java.io.Reader; import import import import import import import import import import import katja.common.NE; Expr.Example.Binding; Expr.Example.Const; Expr.Example.Env; Expr.Example.Exp; Expr.Example.Let; Expr.Example.Mult; Expr.Example.Plus; Expr.Example.Var; Expr.Example.VarDef; Expr.Example.Vars; class LetDemo { public static void main(String args[]) throws Exception { if(args.length < 1) { System.out.println("usage: java LetDemo <infile>"); System.exit(0); } Reader input = new BufferedReader(new FileReader(args[0])); Yylex MinilangScanner = new Yylex(input); parser parser_obj = new parser(MinilangScanner); Exp prog = (Exp)parser_obj.parse().value; System.out.println(prog.toString()); System.out.println("Test: "+test(prog)); 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 Exp t = test2(); System.out.println("Test2: "+test(t)); } private static String test (Exp expr) throws Exception { if (expr instanceof Const) { return "(Const("+(Integer.toString(((Const)expr).value()))+"))"; } if (expr instanceof Var) { return "(Var("+((Var)expr).name()+"))"; } if (expr instanceof Mult) { String a = test(((Mult)expr).left()); String b = test(((Mult)expr).right()); return "(MULT("+a+","+b+"))"; } if (expr instanceof Plus) { String a = test(((Plus)expr).left()); String b = test(((Plus)expr).right()); return "(PLUS("+a+","+b+"))"; } if (expr instanceof Let){ String cur = ""; for(VarDef vd : ((Let)expr).vardefs()) { String a = "(VarDef("+vd.var().name()+test(vd.exp())+"))"; cur = cur+a; } return "(LET("+cur+")IN("+ (test(((Let)expr).exp())) +")END)"; } throw new Exception ("error in test"); } private static Exp test2(){ int x = 4; int y = 5; String v1 = "var1"; Const const_a = Const(x); Const const_b = Const(y); Var var_1 = Var(v1); Exp e1 = Mult((Exp)const_a, Mult((Exp)var_1,(Exp)const_b)); return e1; } } Aufgabe 1 MIMA Parser Schreiben Sie eine Klasse MIMAParser, die in der Methode main ein Parser-Objekt parser_obj initialisiert, die Methode parser_obj.parse() aufruft und das Ergebnis des Aufrufs, ein abstrakter Syntaxbaum eines MIMAProgramms, ein einer Variable speichert. Aufgabe 2 Erweiterung des Let-Beispiels Schreiben Sie eine Klasse LetInterpreter mit Methoden zu Namensanalyse und Interpretationen der LetAusdrücke, d.h.: a) Schreiben Sie eine Java-Methode, die (1) den generierten Parser für unsere Minisprache initialisiert, (2) die entsprechende Prozedur zum Parsen aufruft, (3) das Result des Parsens als eine Datenstruktur vom Typ Exp in einer temporären Variable e speichert und (4) die Namensanalyse von e durchführt. Hinweis: Die Methode soll die folgende Signatur haben: public bool namensanalyse(Exp e, Vars vs). Dabei soll beim Aufruf der Methode eine leere Bezeichnerliste als Wert des Parameters vs übergeben werden, siehe die Vorlesungsfolien und die Datei spec.katja. b) Schreiben Sie eine Methode, die einen Ausdruck evom Typ Exp und eine leere Umgebung env vom Typ Env als Parameter entgegennimmnt und e in env zu einem Wert vom Typ int auswertet. Hinweis: Die Methode soll die folgende Signatur haben: public int eval(Exp e, Env env) Siehe die Vorlesungsfolien und die Datei spec.katja. Aufgabe 3 Zusatzaufgabe: Fehlerbehandlung a) Erweitern Sie Ihr Programm aus Aufgabe 2 um die Fehlerbehandlung: Beim Auftreten eines Syntaxfehlers soll Ihr Programm die Zeilen- und Spaltennummer der Stelle, an der der Fehler aufgetreten ist, ausgeben.