java代写-Grundlagen der Programmierung

Praktikum: Grundlagen der Programmierung

Prof. Dr. Alexander Pretschner, J. Kranz, G. Hagerer WS 2018/19

Übungsblatt 12 Abgabefrist: s. Moodle

Σ Punkte: 20

Aufgabe 12.1 (P) JUnit

Im Folgenden gehen wir von einer bereits gegebenen, parallelen Map-Implementierung aus. Eine solche Implementierung ist hilfreich, da somit eine aufwendige, mittels Map zu berechnende Funktion f schneller, da parallel auf mehreren Prozessorkernen, ausgeführt werden kann. Die gegebene Map-Implementierung wollen wir mit Unit-Tests auf Herz und Nieren prüfen, wofür auf das Java-eigene Framework JUnit zurückgegriffen wird.

Hierbei sind zunächst die Funktionsannotationen @Before, @BeforeClass, @After, @Af- terClass und @Test relevant, deren Funktionsweise anhand eines Beispiels auf dieser

Webseite 1 ersichtlich sind. Dabei werden Methoden mit einem vorangehenden @Test

markiert, wenn diese als automatisierter Test ausgeführt werden soll. Mit @After bzw.

@Before annotierte Methoden werden jedes Mal aufgerufen, bevor bzw. nachdem jeweils eine @Test-Methode aufgerufen wird. Methoden mit @BeforeClass bzw. @AfterClass wer- den insgesamt ein einziges Mal aufgerufen bevor bzw. nachdem die erste bzw. die letzte Testmethode aufgerufen wird. Um einen Test, welcher wiederum alle Testmethoden einer Testklasse aufruft, zu starten, bietet Ihre Entwicklungsumgebung einfache Startbefehle über entsprechende Startknöpfe. Auf der Kommandozeile können Sie sich an dem Kom- mando in den test.bash-Skripten der Suchmaschinenaufgabe orientieren.

Für die mittels Map zu berechnende Funktion ist ein Interface Fun<T, R> gegeben, welches die Schnittstelle zu einer (unären) Funktion f : T R beschreibt:

public interface Fun<T, R> {

public R apply(T x);

}

Außerdem stellen wir Ihnen den gesamten Quellcode für eine öffentliche Klasse Map zur Verfügung, welche die Methode

public static <T, R> void map(Fun<T, R> f, T[] a, R[] b, int n)

throws InterruptedException

bereitstellt. Die Methode map erstellt n viele Threads. Jedem Thread wird ein unterschied- licher Bereich aus dem Array a zugeteilt, auf dem ein jeweiliger Thread die Funktion f auf die Elemente anwendet. Wenn die Methode zurückkehrt gilt f(a[i]) = b[i] für alle validen Indizes i.

  1. Erörtern Sie gemeinsam die Funktionsweise der parallelen Map-Implementierung.

    Wo liegen die Grenzen hinsichtlich der Anzahl der Threads? Welche Fehler bzw. Exceptions können unter welchen Bedingungen auftreten? Inwieweit ist die Map- Funktion generell limitiert?

    1https://www.mkyong.com/unittest/junit-4-tutorial-1-basic-usage

  2. Schreiben Sie eine Klasse IntToString, welche das Interface Fun<Integer, String> implementiert. Die Methode apply soll einen Integer entgegennehmen und die je- weilige Stringrepräsentation des Integers zurückgeben.

  3. Überlegen Sie sich Variablen, die für jede Testmethode von Map.map() bzw.

    IntToString gesetzt sein müssen. Überlegen Sie sich außerdem mit welchen der Funktionsannotationen Sie dies sicherstellen können. Stellen Sie außerdem selber per Hand sicher, dass nach jeder Testausführung alle Variablen wieder geleert werden. Überprüfen Sie ebendieses Verhalten, nachdem alle Tests ausgeführt wurden.

  4. Überlegen Sie sich, mit welchen nicht sinnvoll behandelbaren bzw. ungültigen Para- metern die Methode map aufgerufen werden kann und fangen Sie diese Fälle über eine IllegalArgumentException ab. Das bedeutet, dass die Methode map für die jeweili- gen Parameter das korrekte Ergebnis liefern oder eine IllegalArgumentException werfen sollte.

    Aufgabe 12.2 (P) Chat-Server und -Client

    In dieser Aufgabe implementieren wir eine Chat-Anwendung, mittels welcher Benutzer von verschiedenen Rechnern aus übers Netzwerk miteinander Textnachrichten austauschen können.

    Diese Anwendung besteht aus zwei Komponenten: einem Chat-Server und einem Chat- Client. Von der Client-Anwendung werden mehrere Instanzen separat gestartet und aus- geführt, wobei die Nutzer Text auf der Kommandozeile eingeben und per Druck auf die Enter-Taste miteinander textuell austauschen. Vom Server braucht es nur eine Instanz, welche Verbindungsanfragen von allen Nutzern bzw. Clients entgegennimmt, deren Text- nachrichten empfängt und diese an alle anderen Clients weiterleitet.

    Diese Anwendung benötigt Threads an mehreren Stellen. Für den Client ist es, eine vor- handene Verbindung zu einem Chat-Server vorausgesetzt, zunächst notwendig, einerseits Texteingaben auf der Kommandozeile einzulesen (Klasse ChatClientMessageSender) und and den Server zu schicken. Andererseits ist die Aufgabe eines Clients, ebensolche Textnachrichten, welche vom Server an den Client weitergeleitet werden, zu empfangen und anzuzeigen (Klasse ChatClientMessageReceiver). Diese zwei Prozesse müssen mit- tels Threads unabhängig voneinander ablaufen, damit diese sich nicht gegenseitig blockie- ren.

    Der Server hingegen muss immer in der Lage sein, einerseits eingehende Verbindungsan- fragen von neuen Clients anzunehmen (Klasse ChatServerConnectionManager). Ande- rerseits muss der Server für jede bereits vorhandene Clientverbindung eingehende Text- nachrichten an alle anderen verbundenen Clients weiterleiten (siehe dazu die Java-Klasse ChatServerClientConnection). Es orientiert sich die Anzahl der vorhandenen Threads hierbei dynamisch an der Anzahl der vorhandenen Clients plus ein zusätzlicher Thread, um neue Verbindungen anzunehmen.

    Implementieren Sie diese Funktionsweise, indem Sie sich am folgenden UML-Klassendia- gramm orientieren:

     
     
     
     
     
     

    Beachten Sie dabei bitte die folgenden Punkte:

    • Objekte der Klasse ChatServerClientConnection sollen von einer Instanz von ChatServerConnectionManager instanziert werden, während Objekte der Java- Klasse ChatClientMessageReceiver von einem ChatClientMessageSender instan-

      ziert werden sollen. Dabei sollen diese instanzierenden Klassen wiederum jeweils aus einer main()-Methode heraus instanziert werden.

    • Jede Klasse im UML-Diagram implementiert ein Runnable. Die dafür zu implemen- tierenden Methode run() ist nicht explizit dargestellt. Überlegen Sie sich, welche Aufgaben in der run()-Methode übernommen werden müssen – für Objekte der Klas-

      sen ChatClientMessageReceiver und ChatServerClientConnection etwa die Aufgabe, Nachrichten zu empfangen.

    • Ebenso sind ggf. benötigte getter-Methoden für private Attribute nicht explizit dar- gestellt.

    • Die Methode ChatClientMessageSender.handleMessage() gehört von der Logik her eigentlich zur Klasse ChatClientMessageReceiver und wird auch von dort

      heraus aufgerufen. Wir werden in der nachfolgenden Aufgabe den Vorteil erkennen, der sich daraus ergibt.

    • Wenn die Nachricht “.bye” an die Methode handleMessage() von einer Instanz von ChatClientMessageSender oder ChatServerConnectionManager übergeben wird, soll der Client und alle damit verbundenen Objekte respektive Threads und Input-

      wie OutputStreams geschlossen und terminiert werden.

    • Der incomingConnectionRequestHandler besitzt eine Methode, die blockierend auf neue Verbindungsanfragen von Clients wartet und ggf. neue Sockets zurückgibt.

      Aufgabe 12.3 (P) Chat-GUI mittels AWT

      Implementieren Sie die Klasse ChatClientUI mittels der GUI-Bibliothek AWT. Diese soll die Funktionalität der beiden ChatClient-Klassen als eine Klasse bereitstellen mit der Ergänzung um eine graphische Benutzeroberfläche. Diese soll lediglich ein Fenster bereitstellen mit zwei Komponenten: ein nicht manuell veränderbares Textfeld mit den ausgetauschten Chatnachrichten und ein herkömmliches Textfeld für die Eingabe der zu versendenden Chatnachricht. Der Text aus dem Eingabetextfeld wird beim Drücken der Enter-Taste gesendet und aus dem Textfeld entfernt.

      Hinsichtlich der Struktur können wir die Klasse ChatClientMessageReceiver wiederver- wenden, da diese bereits die Threadlogik für den Empfang der Nachrichten enthält. Bauen Sie dafür ein Interface ChatClientMessageSenderThread, welches diejenigen Methoden enthält, welche aus ChatClientMessageReceiver heraus aufgerufen werden. Dieses Inter- face soll sowohl von ChatClientMessageSender als auch von ChatClientUI implemen- tiert werden.

      Hierbei wird der Vorteil ersichtlich, der sich aus der Platzierung von der Methode handleMessage() ergibt. Wir können neue Nachrichten entweder auf der Konsole oder auf der GUI anzeigen, während die Threadlogik für den Empfang der Nachrichten unverändert bleibt.

      Die Hausaufgabenabgabe erfolgt über Moodle. Bitte geben Sie Ihren Code als UTF8- kodierte (ohne BOM) Textdatei(en) mit der Dateiendung .java ab. Geben Sie keine Projektdateien Ihrer Entwicklungsumgebung ab. Geben Sie keinen kompilierten Code ab (.class-Dateien). Sie dürfen Ihren Code als Archivdatei abgeben, dabei allerdings ausschließlich das ZIP-Format nutzen. Achten Sie darauf, dass Ihr Code kompiliert. Hausaufgaben, die sich nicht im vorgegebenen Format befinden, werden nur mit Punkt- abzug oder gar nicht bewertet.

      Aufgabe 12.4 (H) AWT-UI für die Suchmaschine [10 Punkte] In dieser Aufgabe sollen Sie eine Nutzeroberfläche für die Suchmaschine implementieren.

      Diese Oberfläche soll mittels AWT umgesetzt werden und soll über das über Netzwerk

      Anfragen an den TestIt-Server aus Aufgabe 11.7 (H) stellen.

      Verwenden Sie für den Server-Teil der Anwendung den bereits vorhandenen Code aus Aufgabe 11.7 (H). Modifizieren Sie den Client-Teil der Anwendung, so dass Sie mit der GUI Dokumente anlegen, hinzufügen, auflisten, nach Relevanz sortieren und durchsuchen können. Die Operationen und Daten sollen, wie in dem bestehenden Code, auf dem Server ausgeführt und verwaltet werden, während die Client-UI nur Anfragen sendet.

      Erstellen Sie dafür ein Fenster mit Knöpfen für die wie folgt aufgelisteten Aktionen:

      • exit: Schließt das Fenster und beendet das Programm.

      • add: Öffnet ein neues Fenster mit einer Eingabemaske zum Hinzufügen eines Doku- ments.

      • list: Öffnet ein neues Fenster mit einem Textfeld, welches alle Dokumente auflistet.

      • query: Neben diesem Knopf befindet sich ein Textfeld für die Suchanfrage. Beim Knopfdruck öffnet sich ein neues Fenster mit einem Textfeld, welches die Ausgabe von dem query-Kommando der Server-Kommandozeilenanwendung enthält.

      • count: Neben diesem Knopf befindet sich ein Textfeld für die Suchanfrage. Beim Knopfdruck öffnet sich ein neues Fenster mit einem Textfeld, welches die Ausgabe von dem count-Kommando der Server-Kommandozeilenanwendung enthält.

      • pageRank: Öffnet ein neues Fenster mit einem Textfeld, welches die Ergebnisse vom pageRank-Kommando enthält.

    Um beim Klicken auf einen Knopf ein neues Fenster zu erstellen, bedienen Sie sich der AWT-EventQueue, indem Sie der invokeLater()-Methode eine parameterlose Lambda- Funktion übergeben, welche ein neues Fenster in Form eines JFrame-Objekts erzeugt:

    1 java.awt.EventQueue.invokeLater(() -> {

    2 javax.swing.JFrame newWindow = new javax.swing.JFrame(“New Window”);

    3

    4 })

    Aufgabe 12.5 (H) Arrays in MiniJava [10 Punkte] Das Ziel dieser Aufgabe ist es, unseren Interpreter und die Übersetzung von Blatt 9 derart

    zu erweitern, dass wir eindimensionale int-Arrays in MiniJava unterstützen. Wie bereits

    vom echten Java bekannt, werden Arrays nicht auf dem Stack, sondern auf dem sog.

    Heap gespeichert. Insgesamt müssen wir dazu die folgenden Änderungen an der bisherigen Implementierung aus Blatt 9 vornehmen:

    1. Wir müssen Heap-Speicher im Interpreter und die dazu passenden Instruktionen hinzufügen. Diese Instruktionen betreffen das Anlegen von Objekten im Heap sowie das Lesen und Schreiben in diese Objekte.

    2. Wir müssen die Klassenhierarchie, die MiniJava-Programme repräsentiert, derart erweitern, dass sie die von Java bekannten Konstrukte zum Anlegen von Arrays, zum Lesen eines bestimmten Indexes innerhalb eines Arrays sowie zum Schreiben eines Wertes in ein Array unterstützt.

    3. Wir müssen die Code-Generierung erweitern, sodass diese die neuen MiniJava- Konstrukte auf passende Interpreter-Instruktionen abbildet. Dazu benötigen wir die neu im Interpreter hinzugefügten Instruktionen.

      Im ersten Schritt soll deswegen der Interpreter um Heapspeicher und die zugehörigen Instruktionen erweitert werden. Wir repräsentieren den Heap durch einen weiteren int– Array im Interpreter, welches eine Größe von 1024 Elementen umfasst. Es werden darauf aufbauend entsprechend der folgenden Tabelle drei neue Instruktionen benötigt. Es sei hier wiederum o1 das oberste Stack-Element und o2 das zweit-oberste Stack-Element. Werden o1 bzw. o2 von einer Instruktion verwendet, werden diese Parameter dabei vom Stack entfernt.

      Instruktion

      Immediate

      Beschreibung

      ALLOC (allocate)

      keins

      Allokiert ein neues Heap-Objekt der Größe

      o1 und legt dessen Adresse auf den Stack

      LFH (load from heap)

      keins

      Legt den Wert an Heap-Adresse o1 auf den

      Stack

      STH (store to heap)

      keins

      Schreibt den Wert o2 in den Heap an Adresse

      o1

      Beachten Sie, dass nun eine weitere Art von Adresse auf unserem Stack liegen kann. Sie haben den Stack auf Blatt 9 bereits für Daten, Programm-Adressen (vgl. CALL) und Stack-Adressen (vgl. auf dem Stack gesicherte Werte des Frame-Pointers) verwendet. Nun kommen Adressen, die in den Heap führen, neu hinzu.

      Unser Interpreter kennt keinerlei Garbage Collection, einmal angelegte Objekte bleiben also jeweils bis zum Ende der Ausführung des Programms bestehen. Um die neuen Instruk- tionen besser zu verstehen, betrachten Sie folgendes MiniJava-Beispielprogramm, welches ein Array der Größe 2 anlegt, die Zahl 42 an die zweite Position des Arrays schreibt und diese anschließend wieder ausliest und ihren Wert zurückgibt:

      1 int main() {

      2 int[] array;

      3 array = new int[2];

      4 array[1] = 42;

      5 return array[1];

      6 }

      Für ein zwei-elementiges Array soll hier ein zwei int-Zahlen großes Heap-Objekt verwen- det werden. Das MiniJava-Programm wird so in folgenden Assembler-Code übersetzt:

      0 DECL 1 // Eine lokale Variable (array) deklarieren

      1 LDI 2 // Größe des Heap-Objektes (2 ints) auf den Stack laden

      2 ALLOC // Heap-Objekt anlegen

      3 STS 1 // Heap-Adresse in Variable ablegen

      4 LDI 42 // Wert laden, den wir in den Heap schreiben wollen

      5 LFS 1 // Heap-Adresse von Array aus Variable laden

      6 LDI 1 // Offset (Index 1 des Objektes) in Heap-Objekt auf Stack laden

      7 ADD // Offset auf Heap-Adresse addieren

      8 STH // Wert (42) an zweite Obj.-Position (Index 1) in den Heap schreiben

      9 LFS 1 // Heap-Adresse aus Variable laden

      10 LDI 1 // Offset (Index 1 des Objektes) in Heap-Objekt auf Stack laden

      11 ADD // Offset auf Heap-Adresse addieren

      12 LFH // Wert aus Heap laden

      13 HALT // Wert zurückgeben

      Wenn mehrere Arrays deklariert werden, werden sie direkt hintereinander im Heap abge- legt. Wenn etwa drei Arrays a mit Größe 9, b mit Größe 7 und c mit Größe 13 erzeugt werden sollen, dann liefert LDI 9; ALLOC die Heap-Adresse 0, die in einer Variablen a gespeichert wird (zB. per STS 1). LDI 7; ALLOC im Anschluss liefert die Heap-Adresse 0 + 9 = 9, die in einer Variablen b gespeichert wird (z.B. per STS 2). LDI 13; ALLOC im Anschluss liefert die Heapadresse 0 + 9 + 7, die in einer Variablen c gespeichert wird (z.B. per STS 3). Der Interpreter muss sich also merken, wie viele Elemente des Heaps bereits für früher deklarierte Arrays verbraucht worden sind. Will man nun in diesem Beispiel auf b[3] zugreifen, geschieht das mit LFS 2; LDI 3; ADD; LFH, was zunächst die (Anfangs-)Adresse von b auf den Stack legt, nämlich 9, dann 3 addiert und so das 12. Element im Heap auf den Stack legt. c[12] erreicht man mittels LFS 3; LDI 12; ADD; LFH, was die (Anfangs-)Adresse von c auf den Stack legt, nämlich 16, 12 addiert und so den 28. Wert im Heap auf den Stack legt.

      Fügen Sie neue Instruktionsklassen für die neuen Instruktionen hinzu. Erweitern Sie das Interface AsmVisitor passend, bevor Sie die neuen Instruktionen in den Visitoren AsmFormatVisitor und Interpreter implementieren. Schreiben Sie außerdem die Klas- se InterpreterHeapTest, in der Sie Ihren erweiterten Interpreter mittels JUnit testen.

      Im zweiten Schritt wollen wir die Klassenhierarchie für MiniJava und die Code-Generier- ung um die Unterstützung für Arrays erweitern. Beachten Sie dabei, dass Ausdrücke (Oberklasse Expression) in MiniJava nun zwei verschiedene Typen haben können – sie können zu einem Array oder einer Zahl evaluieren. Evaluiert ein Ausdruck zu einem Array, entspricht der Wert des Ausdrucks der Heap-Adresse des Arrays. Zum Beispiel wertet sich der Ausdruck 3 + 1 zu der Zahl 4 aus, während der Ausdruck new int[10] zu einer Heap- Adresse ausgewertet wird, die das neue, 10-elementige Array repräsentiert. Sie dürfen während der gesamten Aufgabe stets davon ausgehen, dass das Programm typkorrekt ist. Gehen Sie bei der Erweiterung der Klassenhierarchie wie folgt vor.

      1. Erstellen Sie die Klasse ArrayAllocator, die von Expression erbt und die Erzeu- gung eines neuen Arrays (mit der neuen MiniJava-Syntax new int[size]) reprä- sentiert. Die Klasse verfügt über die Methode

        public Expression getSize(),

        die den Ausdruck zurückliefert, der die Größe des neuen Arrays berechnet. Zum Beispiel liefert getSize() von new int[1 + 2] den Ausdruck new Binary(new Number(1), Binop.Plus, new Number(2)) zurück.

      2. Erstellen Sie die Klasse ArrayAccess, die ebenfalls von Expression erbt und einen Zugriff auf einen bestimmten Array-Index repräsentiert (mit der ebenfalls neuen MiniJava-Syntax array[index]). Die Klasse verfügt über die Methode

        public Expression getArray(),

        die denjenigen Ausdruck, der die Heap-Adresse des zugegriffenen Arrays berechnet, zurückliefert. Die Methode

        public Expression getIndex()

        gibt den Ausdruck, der den zugegriffenen Index (also das, was in eckigen Klammern steht) berechnet, zurück. Betrachten wir zum Beispiel den ArrayAccess numbers[1

        + 2] (MiniJava-Syntax!), der das 3. Element (Zählung ab 0) eines Arrays mit dem Namen numbers berechnet. Hier ergibt der Aufruf getArray() den Ausdruck new Variable(“numbers”), während getIndex() den Ausdruck new Binary(new Number(1), Binop.Plus, new Number(2)) liefert.

      3. Implementieren Sie nun die Klasse ArrayIndexAssignment, bei der es sich um ein Statement handelt, das den Wert eines Ausdrucks an einem bestimmten Index eines Arrays zuweist; dies entspricht dem Konstrukt array[index] = expression; in MiniJava. Die Klasse verfügt über die Methoden aus ArrayAccess und zusätzlich über die Methode

        public Expression getExpression(), über die sich der zugewiesene Ausdruck ermitteln lässt.

      4. Implementieren Sie eine Klasse ArrayLength, die von Expression erbt. Die Klas-

        se repräsentiert den MiniJava-Aufruf length(array), der die Größes eines Arrays ermittelt. Die Klasse bietet die Methode

        public Expression getArray(),

        die denjenigen Ausdruck zurückliefert, der die Heap-Adresse des Arrays, von dem die Länge bestimmt werden soll, berechnet.

        Hinweis: Sie dürfen die Abbildung von Arrays in MiniJava auf Heap-Objekte des Interpreters, welche die Code-Generierung vornimmt, für die Unterstützung von ArrayLength anpassen. Beachten Sie, dass Sie dabei die Semantik der drei neuen Instruktionen nicht ändern dürfen. Diese müssen weiterhin exakt wie vorgegeben funktionieren.

      5. Erweitern Sie die Hierarchie derart, dass diese neben dem Typen int auch den Typen int[] unterstützt. Nutzen Sie dazu das mit der Angabe verteilte Enum Type. Passen Sie insbesondere die Klassen Declaration und Function an. Speichern Sie Parameter innerhalb von Function nicht mehr als String, sondern als Instanz der Klasse Parameter, die ebenfalls als Teil der Angabe verteilt wird. Überladen Sie die Konstruktoren der Klassen Declaration und Function passend, dass die bisherigen Tests weiterhin funktionieren.

      6. Erweitern Sie schließlich das Interface ProgramVisitor und die implementierenden Klassen FormatVisitor und CodeGenerationVisitor, sodass Arrays in MiniJava unterstützt werden.

        Betrachten Sie das folgende Beispiel eines MiniJava-Programms, welches die Summe aller Zahlen von 0 bis n – 1 für eine vom Nutzer eingegebene Zahl n mittels Arrays berechnet:

        1 int[] init(int size) {

        2 int i;

        3 int[] array;

        4 i = 0;

        5 array = new int[size];

        6 while(i < size) {

        7 array[i] = i;

        8 i = i + 1;

        9 }

        10 return array;

        11 }

        12

        13 int sum(int[] array) {

        14 int i;

        15 int sum;

        16 i = 0;

        17 sum = 0;

        18 while(i < length(array)) {

        19 sum = sum + array[i];

        20 i = i + 1;

        21 }

        22 return sum;

        23 }

        24

        25 int main() {

        26 int[] a;

        27 a = init(read());

        28 return sum(a);

        29 }

        Das Programm lässt sich durch den folgenden Java-Code aus den Hierarchie-Klassen er- stellen:

        1 Function init = new Function(Type.IntArray, “init”,

        2 new Parameter[] { new Parameter(Type.Int, “size”)},

        3 new Declaration[] { new Declaration(“i”), new

        ‹→ Declaration(Type.IntArray, “array”)},

        4 new Statement[] {

        5 new Assignment(“i”, new Number(0)),

        6 new Assignment(“array”, new ArrayAllocator(new Variable(“size”))),

        7 new While(new Comparison(new Variable(“i”), Comp.Less, new

        ‹→ Variable(“size”)), new Composite(new Statement[] {

        8 new ArrayIndexAssignment(new Variable(“array”), new

        ‹→ Variable(“i”), new Variable(“i”)),

        9 new Assignment(“i”, new Binary(new Variable(“i”), Binop.Plus,

        ‹→ new Number(1)))

        10 }), false),

        11 new Return(new Variable(“array”))

        12 });

        13

        14 Function sum = new Function(Type.Int, “sum”,

        15 new Parameter[] { new Parameter(Type.IntArray, “array”) },

        16 new Declaration[] { new Declaration(“i”), new Declaration(“sum”) },

        17 new Statement[] {

        18 new Assignment(“i”, new Number(0)),

        19 new Assignment(“sum”, new Number(0)),

        20 new While(new Comparison(new Variable(“i”), Comp.Less, new

        ‹→ ArrayLength(new Variable(“array”))), new Composite(new

        ‹→ Statement[] {

        21 new Assignment(“sum”, new Binary(new Variable(“sum”),

        ‹→ Binop.Plus, new ArrayAccess(new Variable(“array”),

        22 new Variable(“i”)))),

        23 new Assignment(“i”, new Binary(new Variable(“i”), Binop.Plus,

        ‹→ new Number(1)))

        24 }), false),

        25 new Return(new Variable(“sum”))

        26 });

        27

        28 Function main = new Function(“main”,

        29 new String[] {},

        30 new Declaration[] { new Declaration(Type.IntArray, “a”) },

        31 new Statement[] {

        32 new Assignment(“a”, new Call(“init”,

        33 new Expression[] { new Read() })),

        34 new Return(new Call(“sum”, new Expression[] { new Variable(“a”) }))

        35 });

        36

        37 Program p = new Program(new Function[] { init, sum, main });

        Erstellen Sie eine Klasse CodeGenArraysTest, in der Sie Ihren Code-Generator mittels JUnit testen. Nutzen Sie das gegebene Beispiel, erstellen Sie aber auch eigene Tests. Testen Sie dabei insbesondere Folgendes.

        1. Ihre Implementierung kann mit Arrays der Größe 0 umgehen.

        2. Ihre Implementierung kann mehreren Arrays umgehen.

        3. Rekursive Funktionen können Arrays als Parameter erwarten und zurückgeben.

        4. Ihre Implementierung funktioniert für komplexere Beispiele. Nutzen Sie für Ihren Test ein Sortierverfahren.

Hinweis: Behandeln Sie Fehler wie auf Blatt 9 beschrieben. Wie oben erwähnt, können in einem Programm können nun auch Typfehler auftauchen (wenn beispielweise einer int-Variablen ein Array zugewiesen wird). Diese Art von Fehler ist nicht trivial zu er- kennen und muss von Ihnen nicht behandelt werden. Zugriffe auf Indizes außerhalb von Arraygrenzen müssen Sie ebenfalls nicht behandeln.

Hinweis: Verwenden Sie, wie in der Musterlösung von Blatt 9, die Packages asm und codegen. Bauen Sie Ihre Lösung nur dann auf Ihren eigenen Lösungen von Blatt 9 auf, wenn diese die offiziellen Tests von Blatt 9 bestehen!