Aufgaben zur Vorlesung
Aufgabe 1:
Schreiben Sie ein dem obigen funktionalen Programm äquivalentes
Programm in C++ (oder Java) und lassen dieses laufen. Was stellen Sie fest? Was
schließen daraus über die Auswertungsreihenfolge von C?
Aufgabe 2:
Schreiben Sie das obige Programm mit einen Texteditor ihrer
Wahl. Speichern Sie es als FirstProgram.java ab.
Übersetzen Sie es mit dem Java-Übersetzer javac.
Es entsteht eine Datei FirstProgram.class. Führen
Sie das Programm mit
dem Javainterpreter java aus. Führen Sie dieses sowohl einmal auf
Linux als auch einmal unter Windows durch.
Aufgabe 3:
Sollten Sie mit Java noch nicht vertraut sein, so kompilieren Sie die
beiden obigen Klassen. Schreiben Sie eine minimale Testklasse, die ein Paar
und eine Liste erzeugt und auf dem Bildschirm ausgibt. (Hinweis: in der
nachfolgenden Aufgabe finden Sie ein Beispiel für eine main-Methode
und den Ausgabebefehl println.)In dieser Aufgabe soll hauptsächlich der Umgang mit Javacompiler und
Interpreter geübt werden. Eine kleine Testklasse könnte dann wie folgt
aussehen: package name.panitz.util;
public class TestLi {
public static void main(String [] args){
System.out.println(new Pair<String,Integer>("hallo",42));
System.out.println(new Li<String>());
System.out.println(
new Li<String>("hallo"
,new Li<String>("welt"
,new Li<String>())));
}
}
Aufgabe 4:
Mit den zwei oben vorgestellten Klassen, lassen sich Abbildungen
realisieren. Eine endliche Abbildung läßt sich als Liste von
Schlüssel/Wert-Paaren darstellen. Schreiben Sie eine in einer Klasse eine
statische Methode mit folgender Signatur:public static <keyType,valueType>
valueType lookUp(keyType key,Li<Pair<keyType,valueType>> map)
lookUp soll
für ein Schlüsselobjekt einen entsprechend damit gepaarten Wert in einer
Liste von Paaren nachschlagen.
Testen Sie Ihre Methode mit folgender Testklasse:package name.panitz.util;
public class TestStaticMap {
public static void main(String [] args){
Li<Pair<String,Integer>> notenliste =
new Li<Pair<String,Integer>>(
new Pair<String,Integer>("john",4),
new Li<Pair<String,Integer>>(
new Pair<String,Integer>("paul",3),
new Li<Pair<String,Integer>>(
new Pair<String,Integer>("george",1),
new Li<Pair<String,Integer>>(
new Pair<String,Integer>("ringo",4),
new Li<Pair<String,Integer>>(
new Pair<String,Integer>("stu",2),
new Li<Pair<String,Integer>>())))));
System.out.println(notenliste);
System.out.println(StaticMap.lookUp("george",notenliste));
System.out.println(StaticMap.lookUp("stu",notenliste));
System.out.println(StaticMap.lookUp("pete",notenliste));
}
}
Der Rumpf der Methode hat tatsächlich nur drei Zeilen.
- Teste, ob die Liste leer ist. Für leere Liste gebe null
aus. (Alternativ ließe sich hier auch eine Ausnahme werfen.)
- Bei
nichtleeren Liste prüfe das erste Listenelement, im Erfolgsfall gib dessen
Wert aus.
- Ansonsten rekursiver Aufruf auf die Restliste.
Wer
bisher keine Erfahrung in Java hatte, wird wahrscheinlich statt des Aufrufs
der Methode equals den Operator == verwendet haben. package name.panitz.util;
public class StaticMap {
public static <keyType,valueType>
valueType lookUp(keyType key,Li<Pair<keyType,valueType>> map){
if (map.isEmpty()) return null;
if (map.head().e1.equals(key)) return map.head().e2;
return lookUp(key,map.tail());
}
}
Aufgabe 5:
Schreiben Sie jetzt eine
generische Klasse MyMap<keyType,valueType>, die den
Typ Li<Pair<keyType,valueType>> erweitert.
Implementieren Sie in der Klasse MyMap die Methode
public valueType lookUp(keyType key),
die für ein Schlüsselobjekt das mit diesem Schlüssel gepaarte Wertobjekt
zurückgibt.
Testen Sie Ihre Implementierung mit folgender Klasse:package name.panitz.util;
public class TestYourMap {
public static void main(String [] args){
MyMap<String,Integer> notenliste
= new MyMap<String,Integer>("john",4,
new MyMap<String,Integer>("paul",1,
new MyMap<String,Integer>("george",1,
new MyMap<String,Integer>("ringo",5,
new MyMap<String,Integer>("stu",2,
new MyMap<String,Integer>())))));
System.out.println(notenliste.lookUp("john"));
System.out.println(notenliste.lookUp("george"));
System.out.println(notenliste.lookUp("pete"));
}
}
Aufgabe 6:
Implementieren Sie eine zu NNum analoge
Klasse NChar, die primitive Zeichen (char) im Heap darstellen kann.
Aufgabe 7:
Implementieren Sie die Klasse NInd, die Indirektionsknoten im Heap
darstellt.
Aufgabe 8:
Schreiben Sie ähnlich der Klasse EinsZwei eine
kleine Testmethode, die den Graph aus
Abbildung erzeugt.
Aufgabe 9:
Implementieren Sie in der Klasse GmHeap eine aussagekräftige
Methode toString.
Aufgabe 10:
Implementieren Sie eine Klasse Pop, die den
G-Maschinenbefehl Pop umsetzt.
Aufgabe 11:
Implementieren Sie eine Klasse Slide, die den
G-Maschinenbefehl Slide umsetzt.
Aufgabe 12:
Implementieren Sie eine Klasse PushChar, die den
G-Maschinenbefehl PushChar umsetzt.
Aufgabe 13:
Implementieren Sie den Rumpf Methode process nach entsprechender
Spezifikation. Beachten Sie auch hier wieder, zunächst mit dem garbage
collector zu checken, ob es freie Heapzellen gibt.
Aufgabe 14:
Implementieren Sie die Klassen Sub, Mult und Div.
Aufgabe 15:
Schreiben Sie die Klassen für die übrigen
Vergleichsoperatoren: Eq, Gt, Ge und Le.
Aufgabe 16:
Implementieren Sie die Klasse MkAp, die den entsprechenden
G-Maschinenbefehl realisiert. Denken Sie wieder daran, zunächst einen Check
vom garbage collection Objekt durchführen zu lassen.
Aufgabe 17:
Implementieren Sie die Klasse Update.
Aufgabe 18:
Führen Sie die manuelle Ausführung des Programm square in der G-Maschine
fort. Zeichnen Sie für jeden Ausführungsschritt Heap und Stack der Maschine.
Aufgabe 19:
Schreiben Sie ein kleines Testprogramm, das den Code für das
Programm square in der G-Maschine ausführt.
Aufgabe 20:
Es war ein mühsames Geschäft, die G-Maschine von Hand
auszuführen. Schön wäre eine spezielle G-Maschine die Schrittweise alle
Zustände, die sie bei der Ausführung durchläuft, loggt.
In objektorientierten Sprachen läßt sich dieses leicht implementieren, indem
eine Unterklasse von GmState geschrieben wird, die nur die
Methode evalStep überschreibt, so daß in dieser Methode vor dem
Aufruf der gleichen Methode aus der Oberklasse, der Maschinenzustand auf die
Konsole ausgegeben wird.{System.out.println(this);super.evalStep();}
Schreiben Sie eine solche Unterklasse GmStateLog und führen Sie mit
dieser den Code aus der letzten Aufgabe aus.
Aufgabe 21:
Implementieren Sie die Klasse Jump.
Aufgabe 22:
Jetzt sind Sie dran. Ergänzen Sie die Switschanweisung für alle
weiteren Befehle der G-Maschine. In der
Klasse DataInputStream stehen Ihnen
Methoden ReadInt und readChar zur verfügung.
Aufgabe 23:
Mit der obigen Klasse ForwardNode läßt sich eine Garbage Collecion
für die G-Maschine umsetzen. Schreiben Sie eine
Klasse TwoSpaceCopyGC, die die Klasse GC erweitert. Sie hat
einen Konstruktor, in dem die Maschinenzustandsobjekt übergeben wird: public TwoSpaceCopyGC(GmState st)
Beim Aufruf von check(i) sollen, falls keine i Speicherplätze
mehr im Heap sind, alle noch gebrauchten Heapknoten in einen neuen Heap
copiert werden. Der neue Heap wird schließlich der neue Heap im
Maschinenzustand. Denken Sie daran auf Stack und den im Dump gespeicherten
Stacks abgelegten Adressen auf die Adressen im neuen Heap abgelegt werden
müssen. Vergessen Sie nicht auch die globalen Funktionsknoten des Heaps zu
kopieren.
Aufgabe 24:
Erweitern Sie die obige Grammatik so, daß sie mit ihr auch
geklammerte arithmetische Ausdrücke ableiten können. Hierfür gibt es
zwei neue Terminalsymbole: ( und ).
Schreiben Sie
eine Ableitung für den Ausdruck: 1+(2*20)+1
Aufgabe 25:
Betrachten Sie die einfache Grammatik für arithmetische
Ausdrücke aus dem letzten Abschnitt. Zeichnen Sie einen Syntaxbaum für den
Audruck 1+1+2*20.
Aufgabe 26:
Implementieren Sie einen Parser für die Sprache Fab4. Hierzu ist
für die folgende Header-Datei eine Implementierung zu erstellen:#ifndef __FAB4PARSER_H
#define __FAB4PARSER_H
#include "../name/panitz/parser/ParsLib.h"
#include "Fab4.h"
#include "Fab4Lexer.h"
#include <string>
#include <iostream>
using namespace name::panitz::parser;
using namespace std;
namespace fab4 {
ParsResult<Fab4*,Token>* parsFab4(string fileName);
}
#endif
Ihr Programm wird etwas übersichtlicher und
leichter zu lesen, wenn sie sich der Möglichkeit von Typsynonymen
bedienen. Folgende Typsynonyme sind dabei sinnvoll:typedef CAF<Parser<Token*,Token>*> PToken;
typedef CAF<Parser<Operator*,Token>*> POperator;
typedef CAF<Parser<Number*,Token>*> PNumber;
typedef CAF<Parser<StringLiteral*,Token>*> PStringLit;
typedef CAF<Parser<CharLiteral*,Token>*> PCharLit;
Aufgabe 27:
Testen Sie Ihren Parser mit folgendem kleinen Hauptprogramm anhand einer Reihe
unterschiedlicher Fab4-Programme. #include "Fab4Parser.h"
using namespace fab4;
using namespace name::panitz::parser;
int main(int argc,char* args[]){
vector<Token*> xs;
cout<<"PairCount: "<<PairCount<<endl;
cout<<"ParsResultCount: "<<ParsResultCount<<endl;
ParsResult<Fab4*,Token>* res = parsFab4(args[1]);
cout<<show(res)<<endl;
cout<<"PairCount: "<<PairCount<<endl;
cout<<"ParsResultCount: "<<ParsResultCount<<endl;
}
Aufgabe 28:
Ergänzen Sie den Rumpf der Methode eval für NumExpr, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(NumExpr* arg){
}
Aufgabe 29:
Ergänzen Sie den Rumpf der Methode eval für Variablennamen, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird.
Um zu testen, ob der Variablenname ein Funktionsname ist benutzen Sie die
Funktion contains aus Modul Instruction mit folgender
Gleichheitsfunktion: static bool stringEq(string s1,string s2){return s1==s2;}
virtual void eval(Var* arg){
}
Aufgabe 30:
Ergänzen Sie den Rumpf der Methode eval für
Funktionsapplikationen App, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(App* arg){
}
Aufgabe 31:
Ergänzen Sie den Rumpf der Methode eval für
Operatorausdrücke OpExpr, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(OpExpr* arg){
}
Aufgabe 32:
Ergänzen Sie den Rumpf der Methode eval für
Zeichenkonstanten CharExpr, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(CharExpr* arg){
}
Aufgabe 33:
Ergänzen Sie den Rumpf der Methode eval für
Konstruktoraufrufe Constr, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(Constr* arg){
}
Aufgabe 34:
Ergänzen Sie den Rumpf der Methode eval für
case-Ausdrücke caseExpr, so daß
der korrekte G-Maschinencode dem Ergebnisvektor result zugefügt
wird. virtual void eval(CaseExpr* arg){
Diese Lösung ist weit von einer Musterlösung entfernt. Insbesondere
der neue Besucher, der mit new erzeugt wird, sollte auch wieder aus
dem Speicher gelöscht werden. Auch sonst idt die Lösung unübersichtlich und
kaum mehr zu verstehen.
arg->e1->visit(this);
result->insert(result->end(),new Eval());
vector<CaseAlt*>* alts = arg->alts;
vector<int>* jumpPointToEnd= new vector<int>();
vector<Instruction*>* code=new vector<Instruction*>();
CaseJump* jump=new CaseJump();
result->insert(result->end(),jump);
int startAddress = 0;
for (int i=0;i<alts->size();i++){
CaseAlt* alt=(*alts)[i];
(*jump)[constructors[alt->constr]]=startAddress;
vector<Instruction*>* altCode=new vector<Instruction*>();
int n= alt->vars.size();
altCode->insert(altCode->end(),new Split(n));
vector<Pair<string,int> > newEnv=env;
for (int j=0;j<env.size();j++)
newEnv[j].snd=newEnv[j].snd+n;
for (int j=0;j<n;j++){
newEnv.insert(newEnv.begin(),Pair<string,int>(alt->vars[j],j));
}
alt->alt->visit(new GenExprCode
(altCode,newEnv,functions,constructors,constructorArity));
altCode->insert(altCode->end(),new Slide(n));
altCode->insert(altCode->end(),new Jump(0));
code->insert(code->end(),altCode->begin(),altCode->end());;
startAddress=code->size();
jumpPointToEnd->insert(jumpPointToEnd->end(),startAddress-1);
}
for (int i=0;i<jumpPointToEnd->size();i++){
int jumpPoint=(*jumpPointToEnd)[i];
(*code)[jumpPoint]=new Jump(startAddress-jumpPoint-1);
}
result->insert(result->end(),code->begin(),code->end());;
}