Das MicroLisp Projekt

Ausgangssituation

Projektziele und Kundenwünsche

Im Rahmen des MicroLisp-Projekts soll eine Umgebung für die Entwicklung von Programmen für die MicroTouch-Plattform produziert werden. Die Entwicklungsumgebung soll sowohl einen Kompilierer als auch einen Emulator bereit stellen, die jeweils den entworfenen Lisp-Dialekt MicroLisp implementieren.

Der Kunde will die Entwicklung von Anwendungen für die MicroTouch-Plattform erleichtern und beschleunigen, indem er den direkten Kontakt mit gerätenahen Sprachen wie C und Assemblersprache vermeidet. Die MicroLisp-Sprache soll durch ihre Dynamik und Ausdrucksstärke einen wesentlich unaufwendigeren Entwicklungsprozess ermöglichen. Sie soll über eine automatisierte Speicherbereinigung verfügen, den Lambda-Calculus implementieren und plattformspeziefische Operationen wie Ein- und Ausgabe unterstützen. Außerdem soll die Sprache durch ein Makrosystem erweiterbar sein.

Mit Beendigung des Projekts soll das Verhältnis zwischen Kosten und Nutzen abgewogen werden. Hierbei soll insbesondere überprüft werden, ob die erwartete Produktivitätssteigerung erreicht wurde.

Teilaufgaben des Projekts

Der Entwicklung der benötigten Softwarekomponenten geht eine umfangreiche Planung voraus. Die Planung beinhaltet alle Komponenten und ihre internen Schnittstellen im System. Zusätzlich muss die Interaktion mit Schnittstellen außerhalb des Systems geplant werden.

Die Entwicklungsumgebung besteht aus mehreren Software-Komponenten die in Zusammenarbeit Programme für die zwei Endpunkte, Kompilation und Emulation, generieren. Zusätzlich gibt es eine Standardbibliothek die die Sprache mit grundlegenden Prozeduren und Makros anreichert.

Die Analysatorkomponente liest eine MicroLisp-Quelldatei und erzeugt einen abstrakten Syntaxbaum. Die Makrokomponente transformiert den abstrakten Syntaxbaum mit Hilfe von Makroprozeduren, die unter anderem in einer Standardbibliothek definiert sind. Danach kann das Programm entweder durch die Emulationskomponente in der Entwicklungsumgebung interaktiv ausgeführt oder durch die Kompilationskomponente in C-Quelltext umgewandelt werden. Das resultierende C-Programm greift auf die Laufzeitkomponente zurück, um erweiterte Funktionalitäten von MicroLisp zu ermöglichen. Die Entwicklung jeder Komponente stellt eine Teilaufgabe dar. Die Komponenten müssen außerdem auf Funktionalität und Korrektheit getestet werden.

1.1 Die Komponenten im Überblick.
1.1 Die Komponenten im Überblick.

Abschließend soll angesichts einer Kosten-Nutzen-Analyse getestet werden ob die MicroLisp-Entwicklungsumgebung die Projektziele erfüllt. Dazu wird eine Testanwendung mit MicroLisp und in ANSI C entwickelt und die Ergebnisse werden verglichen.

Projektumfeld und Schnittstellen

Die MicroLisp-Sprache ist ein Lisp-Dialekt. Eine detaillierte Definition der Sprache kann in der MicroLisp-Bedienungsanleitung gefunden werden.

MicroLisp-Programme sollen in ANSI C-Quelltext und von dort aus wiederum in Maschinenanweisungen für Atmel AVR [Atmel AVR] Prozessoren kompiliert werden. Da die MicroTouch-Plattform sehr stark begrenzte Hardware-Ressourcen bietet, sollen die resultierenden Programme die Ressourcen des Geräts möglichst schonen.

Der größte Teil der Komponenten soll in Common Lisp geschrieben werden, daher wird eine Schnittstelle die SBCL Common Lisp-Implementation sein. Sie wird die Plattform sein, auf der die Entwicklungsumgebung ausgeführt wird. Eine weitere Schnittstelle des Systems wird die GNU Compiler Collection, die die generierten C-Programme in Maschinenanweisungen übersetzen soll.

1.2 Systemlandschaft im Überblick.
1.2 Systemlandschaft im Überblick.

Ressourcen- und Ablaufplanung

Ressourcenplanung

Die für das MicroLisp-Projekt benötigten materiellen Ressourcen bleiben aufgrund der Software-Lastigkeit des Projekts sehr überschaubar. Für die Planung, Entwicklung und Dokumentation der Komponenten wird mindestens ein Software-Entwickler für die festgesetzte Dauer von 70 Stunden benötigt.

An Sachmitteln fällt ein Arbeitsplatz mit einem Desktop-Computer an. Dieser muss in der Lage sein, ein UNIX-artiges Betriebssystem auszuführen, welches wiederum die GNU Compiler Collection und die SBCL Common Lisp-Implementation unterstützen muss.

Bis auf die Personalkosten, den Desktop-Computer und den Arbeitsplatz fallen keine Kosten an. Die benötigte Software ist Open Source und frei verfügbar. Als Betriebssystem kann eine frei erhältliche UNIX-Variante wie BSD oder Linux verwendet werden.

Ablaufplanung

Zuerst muss das System und seine Architektur geplant werden. Die als erstes zu programmierende Komponente ist der Analysator. Danach können die Makro- und Emulatorkomponenten parallel programmiert werden. Wenn die oben genannten Komponenten einsatzbereit sind, ist bereits eine repräsentative Ausführungsumgebung für die MicroLisp-Sprache implementiert. Somit kann die Programmierung der Standardbibliothek beginnen und eine Test-Suite für die Sprache geschrieben werden.

Mit der Test-Suite müssen dann die primitiven Operatoren der Sprache, sowie die Prozeduren und Makros der Standardbibliothek auf Korrektheit und Integrität getestet werden. Somit soll schon frühzeitig die Funktionalität der Sprache sichergestellt werden können. Außerdem ist es bereits möglich zu prüfen, ob die Sprache die Anforderungen an die Produktivitätssteigerung beim Programmieren erfüllt.

Wenn die Tests zeigen, dass ein zufriedenstellender Stand erreicht ist, kann mit der Programmierung der Laufzeitumgebung und der Kompilationskomponente begonnen werden. Wenn beide Komponenten funktionstüchtig sind, kann MicroLisp-Quelltext in lauffähigen C-Quellcode übersetzt werden.

Um die Integrität der Kompilationskomponente und die Korrektheit der Laufzeitumgebung zu gewährleisten, wird mithilfe der Test-Suite die Funktionalität beider Komponenten getestet. Dabei wird besonderer Wert auf die Korrektheit interner Vorgänge der Laufzeitkomponente gelegt.

Wenn die Tests ein überzeugendes Ergebnis liefern, kann die Kosten-Nutzen-Analyse durchgeführt werden. Dazu wird eine ausreichend komplexe Testanwendung sowohl in MicroLisp als auch in C programmiert. Die resultierenden Anwendungen werden unter den Gesichtspunkten Quelltext-Komplexität, Performanz, Wartbarkeit und Erweiterbarkeit verglichen. Anhand dieses Vergleichs soll die Rentabilität des MicroLisp-Projekts beurteilt werden.

In Anhang 1 (#section-5-1) wird der oben beschriebene Ablaufplan durch ein Flussdiagramm visuell dargestellt.

Aus der Ablaufplanung lassen sich mehrere zentrale Termine entnehmen. Dazu gehören wichtige Meilensteine in der Programmierung der Komponenten sowie die drei Testphasen. In Abbildung 2.2 werden die Termine tabellarisch zusammengefasst. Die Terminplanung berücksichtigt einen Zeitraum von zehn Stunden für das Verfassen der Projektdokumentation.

TagTeilaufgabe
Tag 2Meilenstein 1: Erste repräsentative Ausführungsumgebung
Tag 4Testphase 1: Analysator-, Makro-, Emulationskomponente und Standardbibliothek
Tag 6Meilenstein 2: Lauffähige C-Programme können erzeugt werden
Tag 7Testphase 2: Laufzeitumgebung und Kompilationskomponente
Tag 8Testphase 3: Kosten-Nutzen-Analyse durch Testanwendung
2.2 Termine aus der Ablaufplanung.

Durchführung und Auftragsbearbeitung

Vorgehensweise & architektonische Entscheidungen

Die Projektdurchführung beginnt, wie in Abbildung 2.1 veranschaulicht wird, mit der Planung der Systemarchitektur. Ich entscheide mich, wie in Abbildung 1.1 dargestellt wird, für eine modulare Architektur. Diese trennt Softwarekomponenten mit unabhängigen Funktionalitäten sauber von einander ab. Quelltext 3.1 veranschaulicht wie das System Subkomponenten, ohne geteilten Status oder verknüpfter Semantik, in einer Oberprozedur kombiniert.

(defun evaluate (expression environment)
  "Evaluate MicroLisp EXPRESSION in ENVIRONMENT."
  (evaluate-expanded-expression
    (expand-expression expression *macros*)
    environment))
3.1 Definition der Emulationsfunktion evaluate.

Diese Art der Programmierung hat den Vorteil, dass ausgehend vom Quelltextbeispiel 3.1 die Prozedur evaluate-expanded-expression unabhängig von der Prozedur expand-expression definiert werden kann. Gleichermaßen benötigt die Oberprozedur evaluate keine Informationen über Implementationsdetails der anderen beiden Prozeduren, um sie anzuwenden. Diese Eigenschaften erleichtern nachträgliche Änderungen und Erweiterungen an Systemkomponenten und beschränken die Komplexität des gesamten Systems.

Die Analysatorkomponente kann mit geringem Aufwand entwickelt werden, da die MicroLisp-Sprache und Common Lisp untereinander Syntaxregeln teilen. Die benötigten Prozeduren zum Einlesen von MicroLisp-Quelldateien werden von Common Lisp zur Verfügung gestellt. Diese generieren einen abstrakten Syntaxbaum der als verschachtelte Listenstruktur repräsentiert wird. Die in Abbildung 3.2 veranschaulichte Repräsentationsform, ermöglicht es MicroLisp-Quelltext mit geringem Aufwand zu verarbeiten.

3.2 Interne Repräsentation des abstrakten Syntaxbaums. Dargestellt durch die so genannte "Box and Pointer"-Notation.
3.2 Interne Repräsentation des abstrakten Syntaxbaums. Dargestellt durch die so genannte "Box and Pointer"-Notation.

Nun können Makrokomponente und Emulationskomponente parallel entwickelt werden. Weil ich alleine am MicroLisp-Projekt arbeite, entscheide ich mich dafür die Makrokomponente als erstes zu implementieren. Diese Komponente sucht in einem Abstrakten Syntaxbaum, ausgegeben von der Analysatorkomponente, nach Ausdrücken, für die in einer Makrotabelle ein Makro definiert ist und dehnt die Ausdrücke mithilfe des Makros aus.

(defun expand-expression (expression macros)
  "Expand MACROS in EXPRESSION and return expanded expression."
  [...])
3.3 Signatur der Prozedur zum Suchen und Ausdehnen von Makroausdrücken.

Ein Makro ist als eine Prozedur implementiert, die als Parameter die Argumente eines Makroausdrucks annimmt und einen neuen Ausdruck als Rückgabewert hat. Quelltext 3.4 zeigt eine einfache Makroprozedur.

(lambda (condition then else)
  `(cond (,condition ,then)
         (t ,else)))
3.4 Makroprozedur die einen klassischen if-Ausdruck zu einem cond-Ausdruck ausdehnt.

Angenommen die Prozedur expand-expression aus Quelltext 3.3 findet einen Ausdruck in der Form (if BEDINGUNG WAHR-AUSDRUCK FALSCH-AUSDRUCK), übergibt diesen Ausdruck an die Makroprozedur aus Quelltext 3.4 und ersetzt ihn mit dem Rückgabewert, dann lautet der ausgedehnte Ausdruck (cond (BEDINGUNG WAHR-AUSDRUCK) (t FALSCH-AUSDRUCK)). So wurde der MicroLisp-Sprache mithilfe des Axioms cond ein neues Kontrollkonstrukt if hinzugefügt.

Die Entwicklung der Emulationskomponente birgt zwei Hauptprobleme: Die Umsetzung von Axiomen und die Implementation von Prozeduren. Die MicroLisp-Axiome und -Datentypen lassen sich mithilfe von Common Lisp mit wenig Aufwand umsetzten, weil beide Sprachen auf den gleichen Kernfunktionen aufbauen. Es müssen lediglich die MicroLisp-Axiome und -Datentypen den entsprechenden Operatoren aus Common Lisp zugeordnet werden.

(add (+ (evaluate (first call-arguments) environment)
        (evaluate (second call-arguments) environment)))
3.5 Ausschnitt aus der Emulationskomponente: Umsetzung des add-Axioms.

Quelltext 3.5 zeigt eine Klausel aus einem case-Ausdruck. Jedes Axiom wird in einer Klausel wie dieser implementiert. Das add-Axiom zum Addieren von Zahlen wird umgesetzt, indem die Argumente (MicroLisp-Ausdrücke) mit der Emulationskomponente rekursiv ausgewertet werden und mit dem Common Lisp Additionsoperator + summiert werden.

MicroLisp-Prozeduren müssen einer essentiellen Anforderung gerecht werden. Sie müssen den lexikalischen Sichtbarkeitsbereich von Bezeichnern innerhalb ihres Körpers, genannt Funktionsabschluss, implementieren. Dafür muss die Zuordnung von Bezeichnern und Werten, auch Environment genannt, zur Zeit der Prozedurdefinition konserviert werden.

(defun make-procedure (lambda-expression environment)
  "Return new procedure consisting of LAMBDA-EXPRESSION and
ENVIRONMENT."
  (unless (lambda-p lambda-expression)
    (error "~a is not a valid lambda expression."
           lambda-expression))
  (list 'procedure
         lambda-expression
         environment))
3.6 Konstruktor für eine Prozedur.

Quelltext 3.6 zeigt wie der Konstruktor make-procedure ein Prozedurobjekt instanziiert. Er übernimmt einen Lambda-Ausdruck und ein Environment als Parameter und gibt ein Objekt zurück, das den Lambda-Ausdruck mit dem Environment verknüpft. Wenn eine Prozedur aufgerufen wird, werden die Bezeichner im Körper der Prozedur mithilfe des konservierten Environments Werten zugeordnet.

Die initiale Standardbibliothek beinhaltet Prozeduren und Makros, die der Entwicklung der Test-Suite erleichtern. Die Implementation dieser ist nicht zwangsweise trivial, jedoch spielt sie im Rahmen des MicroLisp-Projekts eine geringfügige Rolle und wird deswegen nicht genauer erläutert.

Als nächstes wird die wichtigste und komplexeste Komponente, die Laufzeitumgebung entwickelt. Die Repräsentation von Werten stellt die erste Herausforderung dar. Aufgrund der dynamischen Eigenschaften von MicroLisp muss die statische Typisierung von C umgangen werden.

enum type { PROCEDURE, CELL, SYMBOL, NUMBER, CHARACTER };

[...]

typedef struct {
  enum type type;
  struct symbol *symbol;
} symbol;

[...]

union value {
  enum type type;
  procedure procedure;
  cell cell;
  symbol symbol;
  number number;
  character character;
};

typedef union value value;
3.7 Definition des value-Typs durch geschickte Ausnutzung von C struct- und union-Ausdrücken.

Quelltext 3.7 zeigt, wie die Typen der MicroLisp-Sprache zu einem Typ value zusammengefasst werden. Alle Typen sowie die value union haben ein Feld type. Dieses ermöglicht den speziellen Typ eines value-Objekts zu ermitteln. So können MicroLisp-Objekte innerhalb der Laufzeitumgebung einheitlich als value-Objekt herumgereicht werden. Diese Technik impliziert, dass die primtiven Operatoren die Typen von Objekten zur Laufzeit überprüfen müssen, um Speicherzugriffsfehler zu vermeiden.

Anders als bei der Emulationskomponente müssen die Axiome und Datentypen der Sprache von Hand implementiert werden. Beispielsweise müssen in Symbole mit C Strukuren implementiert werden.

/* symbol-structure:  Symbol structure. */

struct symbol {
  unsigned long identifier;
  char *name;
};


/* symbol:  Constants and prototypes for symbol functions. */

#define T new_symbol("T")
#define NIL NULL

symbol *new_symbol (char *);
symbol *symbolic_equality (symbol *, symbol *);
symbol *symbol_p (value *);
3.8 Struktur und Schnittstelle des symbol-Typs.

Quelltext 3.8 zeigt Details der internen Repräsentation von Symbolen. Die Prozedur new_symbol wird verwendet um Symbole aus Literalen zu erzeugen. Sie nimmt einen Namen in Form einer Zeichenkette als Parameter und gibt das entsprechende Symbol zurück. Weil Symbole einzigartig sind und es aber mehrere Instanzen von einem Symbol geben kann werden sie nicht im symbol-Objekt gespeichert. Stattdessen sucht new_symbol in einer Tabelle nach einer symbol-Struktur mit gleichem Namen. Wenn keine passende Struktur gefunden wird, fügt new_symbol einen neuen Eintrag für ein Symbol mit dem Namen und einem eindeutigen Schlüssel in die Tabelle ein. Anschließend wird ein symbol-Objekt mit einem Zeiger auf die entsprechende Struktur zurückgegeben.

Es sind zwei spezielle Symbole T und NIL vordefiniert, konventionell indizieren sie die Wahrheitswerte. Die Prozedur symbolic_equality entspricht dem symbolic=-Axiom. Sie gibt NIL zurück, wenn die Schlüssel der übergebenen Symbole nicht gleich sind. Die Prozedur symbol_p entspricht dem symbol?-Axiom und gibt NIL zurück wenn das übergebene Objekt nicht vom Typ symbol ist, wie sich durch das uniforme type-Feld des value-Typs feststellen lässt.

Die Implementation des symbol-Typs ermöglicht den effizienten Vergleich von Symbolen anhand eines numerischen Schlüssels, anstatt zum Beispiel einer Zeichenkette. Weil Symbole primär als Schlüssel verwendet werden, sind die Eigenschaften dieser Implementation essenziell für die Performanz der Sprache.

Die MicroLisp-Sprache soll automatisierte Speicherbereinigung implementieren. Standardmäßig unterstützt C jedoch nur manuelle Speicherverwaltung. Um diese Diskrepanz zu überwinden, wird ein schlanker Referenzzählender Garbage Collector mit folgender Schnittstelle implementiert.

void use (value *);
void disuse (value *);
void collect_garbage (void);
3.9 Schnittstelle des Garbage Collectors.

Die use-Prozedur inkrementiert den Referenzzähler eines Objekts. Dieser Referenzzähler wird in einer Hashtabelle gespeichert, die die Speicheradressen der Objekte als Schlüssel verwendet. Wenn ein Objekt noch keinen Eintrag in der Referenztabelle hat wird dieser von use eingefügt. Die disuse-Prozedur dekrementiert den Referenzzähler eines Objekts. Die collect_garbage-Prozedur befreit den Speicherplatz, der von Speicheradressen in der Referenztabelle referenziert wird, wenn der entsprechende Referenzzähler null ist.

Die use- und disuse-Prozeduren werden von Prozeduren der Laufzeitumgebung aufgerufen, die Objekte binden. So bindet zum Beispiel die new_procedure-Prozedur, die eine MicroLisp-Prozedur instanziiert, die Objekte in ihrem Environment. Die Verwendung der use- und disuse-Prozeduren garantiert in diesem Fall das die Objekte aus dem Environment solange verfügbar sind wie sie benötigt werden. Die collect_garbage-Prozedur wird periodisch von der Laufzeitumgebung aufgerufen, um den Speicher zu bereinigen.

Es wurde die Referenzzählende Variante des Garbage Collectiors gewählt, weil sie vergleichsweise implementationsunaufwendig ist. Der implementierte Garbage Collector kann die standard Speicherverwaltungsprozeduren malloc und free verwenden und es muss kein spezieller Heap implementiert werden, wie zum Beispiel bei der Mark and Sweep Variante.

Als Letztes muss die Kompilationskomponente entwickelt werden und dank der umfangreichen Laufzeitumgebung muss diese nur noch ein Problem lösen. Die MicroLisp-Sprache behandelt Prozeduren als Objekte erster Klasse. Dementsprechend können Prozeduren an jedem Ort im Quelltext definiert werden. C unterstützt allerdings nur Prozedurdefinitonen auf oberster Ebene des Quelltextes.

(defun nextract-procedures (expanded-expressions)
  "Extract procedures from EXPANDED-EXPRESSIONS, return procedures
and expressions in which procedures are replaced with pointers
(desctructively)."
  [...])
3.10 Prozedursignatur aus der Kompilationskomponente zum Extrahieren von Prozedurdefinitionen.

Quelltext 3.10 zeigt die Signatur der Prozedur zum Extrahieren von Prozedurdefinitionen. Die extrahierten Prozedurdefinitionen werden dann gesammelt am Anfang des resultierenden C-Programms als Prozeduren proc1 bis procN definiert und können aufgrund der vorhersehbaren Namenskonvention von Aufrufen der new_procedure-Prozedur referenziert werden.

Qualitätssicherung

Um die Qualität der Softwarekomponenten während der Entwicklung zu sichern, wird eine Test-Suite geschrieben die die Axiome der Sprache sowie die Prozeduren und Makros der Standardbibliothek abdeckt.

  (test (assert "T does not evaluate to itself."
	        (symbolic= t (quote t)))
        [...])
3.11 Ausschnitt aus der Test-Suite.

Quelltext 3.11 zeigt einen Ausschnitt aus der Test-Suite. Die Test-Suite wird wie in der Ablaufplanung beschrieben Ausgewertet um die Funktionalität der Systemkomponenten zu verifizieren, wenn die Meilensteine erreicht sind. Um die Laufzeitkomponente zusätzlich zu verifizieren wird ein C-Profiler verwendet. Die Internen Vorgänge der Laufzeitumgebung werden Analysiert um subtile Fehler wie Speicherlecks aufzudecken.

Abschließend wird eine Testanwendung sowohl in MicroLisp als auch in ANSI C entwickelt. Die Quelltexte der Testanwendungen units.ml und units-c.c sind unter dem Verzeichnis test/ hinterlegt. Beide Anwendungen sind vom Funktionsumfang her identisch. Sie interpretieren eine einfache Sprache zum Umwandeln von Längeneinheiten und berichten die Ergebnisse. Um beide Entwicklungsumgebungen zu vergleichen, werden relevante Merkmale von units und units-c aus Entwickler- und Benutzersicht gemessen.

Laufzeitunits 1xunits-c 1xunits 1000xunits-c 1000x
real0.008s0.007s7.373s6.378s
user0.003s0.000s2.070s0.540s
sys0.003s0.000s2.976s1.193s
3.12 Messergebnisse von time. real bezeichnet die Zeit zwischen Aufruf und Beendigung des Programs, user bezeichnet die Zeit die im Benutzermodus und sys die Zeit die im Kernelmodus verbracht wird.

Weil der Funktionsumfang beider Anwendungen identisch ist kann aus Sicht des Benutzers nur die Performanz verglichen werden. Mit dem UNIX-Werkzeug time wird die Laufzeit beider Anwendungen gemessen. Die Messergebnisse in Tabelle 3.12 zeigen, dass messbare Performanzunterschiede erst bei sehr langen Berechnungen auftreten. Der Benutzer kann den Performanzunterschied bei den Testanwendungen nicht wahrnehmen.

Aus Sicht des Entwicklers fällt nicht nur auf, dass die C-Anwendung fast doppelt so lang ist. Die Einheitentabelle kann in der MicroLisp-Anwendung mithilfe der primitiven Datentypen zentral und Leserlich definiert werden. In der C-Anwendung muss eine passende Datenstruktur erst geschaffen werden. Dabei kommen einige Implementationsfragen auf, wie zum Beispiel arbiträre Begrenzungen, die nicht relevant für die Logik der Anwendung sind. Um eine neue Längeneinheit zu definieren müssen mehrere Stellen im Quelltext angepasst werden.

Zusätzlich ermöglicht die MicroLisp-Sprache intensive Abstraktion durch Prozeduren höherer Ordnung wie find und map. C erschwert durch das statische Typsystem selbst die Abstraktion von generischen Operationen. Durch die verringerte Abstraktion muss der Entwickler der C-Anwendung auf eine Vielzahl von unrelevanten Details achten.

Der direkte Vergleich zeigt, dass die C-Anwendung nicht nur in der initialen Entwicklung wesentlich aufwendiger ist. Auch die Wart- und Erweiterbarkeit ist erschwert. Es wird sichtbar das die MicroLisp-Entwicklungsumgebung den Aufwand der Anwendungsentwicklung langfristig stark mindert.

Projektergebnisse

Abnahme und Projektübergabe

In das Abnahmeprotokoll fließt eine Analyse von Stand und Funktionalität der Softwarekomponenten sowie die Kosten-Nutzen-Analyse ein. Tabelle 4.1 zeigt das Abnahmeprotokoll.

AbnahmekriteriumErgebnis (Fehlergrad)
Funktionalität durch Test-Suite bestätigtJa
Laufzeitkomponente ist PerformanzoptimiertNein (3)
automatisierte Speicherbereinigung ist funktionalJa
Lambda-Calculus ist implementiertJa
Axiome für Ein- und Ausgabe vorhanden und funktionalJa
Ergebnisse der Kosten-Nutzen-Analyse zufriedenstellendJa
Qualität der Dokumentation zufriedenstellendJa
4.1 Abnahmeprotokoll des MicroLisp-Projekts. Fehlergrad: 1 = hoch, 2 = mittel, 3 = niedrig.

Da das Abnahmeprotokoll keine wesentlichen Mängel aufweist, kann das Projekt wie geplant übergeben werden. Die Laufzeitumgebung wird den Anforderungen an Funktionalität zwar gerecht, ihre Performanz ist allerdings verbesserungswürdig. Weitere Entwicklung im Umfang der Systemwartung soll die Performanz der Laufzeitumgebung optimieren und gegebenenfalls bisher unerkannte Fehler beheben.

Fazit

Im Rahmen des Projekts wurde eine umfangreiche Entwicklungsumgebung für die MicroLisp-Sprache entwickelt. Die geforderten Komponenten sind funktionstüchtig und werden den Kundenwünschen weitgehend gerecht. Das resultierende Softwaresystem ist in der Lage, die MicroLisp-Sprache zu emulieren und nach C zu kompilieren und bietet den gewünschten Makro-Mechanismus zur Erweiterung der Sprache.

Der geplante Ablauf konnte eingehalten werden und die gewählten Methoden haben sich bewährt. Allerdings muss angemerkt werden, dass die der Laufzeitumgebung inhärente Komplexität größer ist als angenommen wurde. Demnach konnte die Qualität der Laufzeitkomponente nur auf einen zufriedenstellenden aber verbesserungswürdigen Stand gebracht werden.

Vor Beginn des Projekts hätte der Aufwand für die Entwicklung der einzelnen Softwarekomponenten intensiver evaluiert werden müssen. Eine detailliertere Abschätzung des Aufwands hätte zu dem Ergebnis geführt, dass das System innerhalb der festgesetzten 70 Stunden zwar vollständig entwickelt werden kann, jedoch reicht die Zeit nicht aus um die gewünschte Qualität aller Softwarekomponenten zu erreichen. Der Projektantrag hätte also spezifizieren müssen, dass das Ziel des Projekts eine Vorabversion der Entwicklungsumgebung ist, die nachträglich iterativ weiterentwickelt werden soll.

Als besonders positiv kann die Test-Suite bewertet werden. Sie stellt einen Qualitätssicherungsmechanismus für mehrere Komponenten des Systems dar, weil sie sowohl die Emulations- und Kompilations- sowie Makro- und Laufzeitkomponente testet. Es stellt sich außerdem heraus, dass der geplante Ablauf, in Form der Emulationskomponente, sehr schnell eine Implementation der konzipierten Sprache produziert. Die initiale Implementation ermöglicht schon früh das Testen der Sprache und stellt eine Referenz für weitere Entwicklungsschritte dar.

Abschließend kann gesagt werden, dass die Entwicklung der MicroLisp-Entwicklungsumgebung noch nicht endgültig abgeschlossen ist. Im Rahmen des MicroLisp-Projekts wurde allerdings ein robustes und erweiterbares System entwickelt, welches fast allen Anforderungen gerecht wird und einen wichtigen Meilenstein darstellt. Somit wurde eine Produktionstaugliche Umgebung für die Entwicklung komplexer Software geschaffen. Dementsprechend ist das Projekt in seiner Gesamtheit als erfolgreich zu bewerten.

Anhang

Anhang 1: Ablaufplan als Flussdiagramm

7.1 Geplanter Ablauf im Flussdiagramm.
7.1 Geplanter Ablauf im Flussdiagramm.