Übersetzung von Programmiersprachen

Begriffsbestimmung

Ein Übersetzer ist ein Programm, das Programme von einer Programmiersprache, der Quellsprache, in eine andere Programmiersprache, die Zielsprache, übersetzt.

***Bild***

Übersetzer können Compiler oder Interpreter sein; dazu gleich noch ein paar Worte. Wichtig ist, daß die Übersetzung gewissen Eigenschaften genügt. So ist es äußerst unerwünscht, daß das Programm in der Zielsprache eine andere Funktionalität hat als das Programm der Quellsprache. Die Sprachdefinition der Quellsprache schafft ja in der Regel keinen unmittelbaren Zugang zum Rechner oder seiner Maschinensprache. Wer heute Software entwickelt, denkt von einem Computer als einer Maschine, die C- oder Pascal-Programme ausführt. Der Übersetzungvorgang wird eher als zeitraubendes Übel angesehen, das nur dazu dient, dem Computer die "Aufgabe beizubringen". In der Anwendungsentwicklung wird aber in abstrakten Datentypen, in Prozeduren oder in Ein-/Ausgabeoperationen gedacht, wobei selbstverständlich vorausgesetzt wird, daß der Compiler das Quellprogramm so übersetzt, daß die Funktionalität der Einzelanweisungen, wie auch die des Gesamtprogramms, erhalten bleibt. Ist dies nicht der Fall, dann kann ein solcher "Compilerbug" außerordentlich lästig sein, da mit irgendwelchen Tricks um diesen Fehler "herumprogrammiert" werden muß. Tatsächlich hat jeder in der Praxis verwendete Compiler mehr oder weniger schwerwiegende Fehler. In diesem Zusammenhang sei davor gewarnt, bei jeder neuen Version eines Compilers oder Betriebssystems das Produkt sofort zu kaufen. Erfahrungsgemäß sind in der Programmierpraxis Compilerfehler, die man kennt, Fehlern in neuen Compilern, die man nicht kennt, vorzuziehen.

Der Übersetzer muß mehrere Aufgaben erfüllen:

 Bauarten der Übersetzer

Compiler

Das heute übliche Verfahren in der Programmierung sieht so aus:
     Editieren
Compilieren
Binden
Laden und Starten

Der Ablauf kann natürlich durch Fehler vorzeitig unterbrochen werden. Das Wesentliche sollte klar sein: Das Programm wird in eine für den Zielprozessor verständliche Maschinensprache übersetzt. Wenn das Programm läuft, ist ein Rückgriff auf die Programmquelle, auch Source-Code genannt, nicht mehr nötig. Dies freut die Softwarehäuser, denn auf diese Art können sie den Kunden Programme verkaufen, ohne die Quellen mitzuliefern. Das Know-How steckt nämlich im Quellprogramm - und auch die Fehler. Vielleicht ist es ganz gut, daß wir Anwender nicht wissen, wie die Quellprogramme der verbreiteten Produkte aussehen...(Ich denke da gerade - fragen Sie mich nicht warum - an ein bestimmtes Textprogramm eines bestimmten Marktführers...)

 Interpreter

Ein Interpreter ist ein Übersetzer, der Befehl für Befehl oder Zeile für Zeile übersetzt und ausführt. Als die Rechner noch wenig Speicherkapazität hatten, waren Interpreter das Gebot der Stunde: Sie erzeugen keinen lauffähigen Code, sondern führen die Anweisung sinngemäß sofort aus - im Gegensatz zu Compilern. Außerdem kann das Programm sofort gestartet werden, ohne es zu übersetzen und zu binden. Gerade das zeilenweise Bearbeiten ist aber auch die empfindliche Stelle des Interpreters: Ein Programm, das Schleifenkonstruktionen verwendet, läuft langsam, da die Befehle in der Schleife so oft übersetzt werden, wie die Schleife durchlaufen wird. Beim Compiler wird nur einmal übersetzt - und damit Schluß. Zusätzlich gibt es noch ein wichtiges Argument gegen Interpreter: Ein Syntaxfehler im Programm wird erst dann gefunden, wenn die fehlerhafte Zeile ausgeführt wird. Es gibt Programme, die anfangs gut laufen, aber von Fehlern wimmeln. Sie findet man erst nach und nach, da es manchmal lange Zeit dauert, bis ein solcher Teil des Programms erstmalig durchlaufen wird. Die Folgerung ist klar: Gerade bei Interpretersprachen ist ein sehr sorgfältiger Test der entwickelten Software notwendig, will man keine späten (und damit unbequemen) Überraschungen erleben. Der klassische Vertreter der Interpretersprache ist BASIC. Es existiert in vielen Varianten, hat aber selten Sprachkonstrukte, die zum strukturierten Programmieren geeignet sind.

Erst ein Teil der modernen BASIC-Dialekte verfügt über integrierte Oberflächen, die es erlauben, eine Programmzeile unmittelbar bei der Eingabe syntaktisch zu prüfen. Strukturierte Programmierung wird ebenfalls nur in neueren BASIC-Dialekten angeboten.

 Vom Quellprogramm zum lauffähigen Code

Symbole und Objekte

Vor der Betrachtung des Übersetzungs- und Bindeablaufes müssen noch einige Begriffe erklärt werden. Ein Quellprogramm, egal ob in Assemblersprache oder in einer höheren Programmiersprache, enthält in der Regel Bezeichner, also Namen für Objekte. Ein Objekt in diesem Sinn ist alles, was einen Namen hat und eine Speicherfläche belegt. Objekte können sein:

Dynamisch erzeugte Speicherobjekte, z.B. "anonyme Variablen", sind in diesem Zusammenhang von untergeordneter Bedeutung, da sie erst zur Laufzeit entstehen und damit den Prüfungen durch den Compiler weitgehend entzogen sind.

Symbole sind Namen von Objekten und darüberhinaus Namen von Deklarationen, die keinen Speicherplatz belegen. Solche Deklarationen können sein:

Aus der Sicht einer Programmquelle existieren zwei Arten von Symbolen:

Wenn ein Symbol von einer Programmquelle zur Verfügung gestellt wird, wird es exportiert. Aus der Sicht des Gesamtprogramms ist ein solches Symbol ein globales Symbol. Ein Symbol, das von einer Programmquelle genutzt wird, ohne innerhalb dieser Quelle definiert worden zu sein, ist ein importiertes Symbol.

 Der Übersetzungs- und Bindevorgang

Am Anfang der Softwareentwicklung steht die gesamte Papierarbeit, die notwendig ist, um von einer Aufgabenstellung zu einer ausreichend formalen Lösung zu gelangen. "Ausreichend formal" heißt, daß die benötigten Algorithmen feststehen müssen, die geeigneten Datenstrukturen formuliert sind und eine vernünftige Modularisierung der Aufgabenlösung vorliegt. Der anschließende Schritt ist das Schreiben des Quellprogramms, das in einer formal definierten Sprache Arbeitsvorschriften für den Computer vorgibt. Dieser Absatz beschreibt, auf welchem Weg ein Quellprogramm in einen ausführbaren Code umgesetzt wird. Da alle Schritte dieses Weges vom Computer ausgeführt werden, ist gewährleistet, daß das ausführbare Programm in seiner Funktionalität dem Quellprogramm entspricht - korrektes Funktionieren der Übersetzer- und Bindeprogramme vorausgesetzt.

 1. Schritt: Übersetzung der Quelle zum Objektcode - Phasen der Übersetzung

Die Übersetzung eines Quellprogramms in einen Objektcode läßt sich logisch (wenn auch nicht immer ablauftechnisch auf einfache Art) in mehrere Phasen gliedern. Manche Sprachdefinitionen fordern explizit ein bestimmtes Verhalten des Compilers, so z.B. die Sprache C. Bei ihr muß sich der Übersetzer so verhalten, daß der Präprozessorschritt dem eigentlichen Übersetzungslauf vorgeschaltet ist, egal, ob der Compiler ein Ein-Pass- oder ein Mehr-Pass-Compiler ist.

Jedes vollständige Lesen des Quellprogramms bzw. seiner internen Darstellung, heißt Compiler-Durchlauf oder Pass. Außer bei sehr einfachen (und bei sehr raffinierten) Compilern sind mindestens 2 Durchläufe erforderlich. Im allgemeinen enthält jeder Compilerdurchlauf eine oder mehrere Compilerphasen.

 Präprozessor(en)

Präprozessoren sind, wie ihr Name vermuten läßt, der eigentlichen Übersetzung vorgeschaltet. In der Regel dienen sie dazu, eine Vor-Übersetzung auf der Basis reiner Textersetzung durchzuführen. In diesem Sinn handelt es sich bei den Tätigkeiten des Präprozessors hauptsächlich um die Substitution von Strings durch andere Strings, wobei gewisse einschränkende Bedingungen beachtet werden müssen. Ein typischer Vertreter für Präprozessoren ist der C-Precompiler, der die Anweisungen #include, #define, #ifdef usw. auflöst. Dabei findet keine Ersetzung von Texten in konstanten Strings oder von Teilstrings statt. Beispiel in C:
     #define abc 12
...
int abcd = 27;
printf("abc ");
printf(2%d",abcd);

Es erfolgt die Ausgabe "abc 27", nicht die Ausgabe "12 27". Die Bezeichnung der Variablen abcd bleibt unbeeinflußt, da "abc" nur einen Teilstring des Variablennamens darstellt.

Makroexpansion wird üblicherweise nicht rekursiv ausgeführt; bei C ist dies ausdrücklich nicht vorgesehen.

 Lexikalische Analyse

Die lexikalische Analyse zerlegt das Quellprogramm in Tokens ("Atome"), d.h. Folgen von Zeichen, die eine Einheit bilden und im Zuge der Übersetzung nicht weiter als zerlegbar betrachtet werden (das heißt nicht, daß diese Zeichenketten nicht gewissen Bedingungen genügen müssen!). Neben der Zerlegung in Tokens hat die lexikalische Analyse weitere Aufgaben. Sie entfernt für die Funktionalität des Programms überflüssige Zeichen, wie Leerzeichen oder Kommentare. Darüberhinaus findet in der Regel eine Konvertierung des Quellprogramms in eine vereinheitlichte interne Darstellung statt. Der Programmteil, der die lexikalische Analyse durchführt, wird als Scanner bezeichnet.

Das Zerlegen in Tokens ist nicht ganz so trivial, wie es auf den ersten Blick scheint, da in vielen Programmiersprachen Zwischenräume als Trennzeichen zugelassen, aber nicht unbedingt gefordert sind. Das nachstehende FORTRAN-Beispiel kann dies illustrieren:

    IF(5.EQ.MAX)GOTO100

Zerlegung:

     IF   (   5   .EQ.   MAX   )   GOTO   100

Beim Zerlegen werden zunächst IF und ( erkannt. Die Bedeutung der 5 ist im Folgeschritt noch nicht klar:

Erst wenn das auf E folgende Q gelesen ist, wird die Zerlegung klar: Es handelt sich um die Ganzzahlkonstante 5 und den Vergleichsoperator .EQ. .

 Syntaktische Analyse

Die syntaktische Analyse wird durch den Parser durchgeführt. Er hat die Aufgabe, die syntaktische Struktur zu erkennen sowie syntaktische Fehler zu melden. Darüberhinaus ist er für die Erstellung von Symboltabellen zuständig, führt Typkonformitätsprüfungen durch und generiert ggf. einen Zwischencode, der für die nachgeschaltete Codegenerierung die Eingabe darstellt.

Die Erkennung syntaktischer Strukturen kann z.B. durch Baumdiagramme veranschaulicht werden:

     BEGIN
x := 5;
WRITE(x)
END
     ***Bild***

Komplettierung der Symboltabelle ist eine weitere wichtige Aufgabe der syntaktischen Analyse. Oft wird die Symboltabelle schon von der lexikalischen Analyse angelegt, kann aber noch nicht vollständig gefüllt werden, da manche Informationen erst durch den syntaktischen Zusammenhang gewonnen werden. Die Symboltabelle enthält die (internen) Namen der Variablen und ihre Attribute, z.B. Typ oder Gültigkeitsbereich. Weitere Einträge der Symboltabelle sind für importierte oder zu exportierende Symbole zuständig.

Die syntaktische Analyse umfaßt auch die semantische Analyse. Sie kann zwar keine Aussagen über die Semantik eines Programms machen, aber anhand von Typprüfungen zumindest feststellen, ob das Programm in sich schlüssig ist. Die semantische Analyse umfaßt wichtige Typprüfungen:

Daneben werden Gültigkeitsbereiche von lokalen Objekten geprüft.

Die syntaktische Analyse erzeugt in der Regel einen maschinenunabhängigen Zwischencode. Der Zwischencode ist in einer nur intern sichtbaren Zwischensprache verfaßt und dient als Eingabe für den Codegenerator. Der Vorteil des Zwischencodes liegt in der einfacheren Portierung eines Compilers von einem Rechner auf einen anderen. Durch den Zwischencode können die maschinenabhängigen Teile des Compilers zusammengefaßt und kürzer geschrieben werden. Beispiel:
     radius := SQRT ( x * x + y * y ) ;
MPY X X $HV1
MPY Y Y $HV2
ADD $HV1 $HV2 $HV3
CALL SQRT $HV3 $HV4
STORE $HV4 RADIUS

Der hier beispielhaft gezeigte Zwischencode besteht aus sogenannten Dreiadreßbefehlen, da bei jeder Operation maximal 3 Speicheroperanden (=Variable) beteiligt sein können. Das obige Schema wird auch Matrix genannt.

Die für den Programmentwickler wohl wichtigste Aufgabe der syntaktischen Analyse ist die Ausgabe von aussagekräftigen Fehlermeldungen. Eine Aufgabe bei der Entwicklung von Compilern ist auch das Wiederaufnehmen der Übersetzung nach einem schwerwiegenden Fehler. Eine zerstörte Blockstruktur (z.B. durch ein vergessenes END) kann bei ungünstig implementierten Syntaxanalyseverfahren den gesamten restlichen Ablauf der Syntaxprüfung in Frage stellen.

 Maschinenunabhängige Optimierung

Die maschinenunabhängige Optimierung findet auf der Ebene des Zwischencodes statt. Ihr Ziel ist es, den Platz- und Zeitbedarf des später erzeugten lauffähigen Codes zu verringern. Die dazu verwendeten Methoden sind:

Das Thema Codeerzeugung und -optimierung wird separat eingehend behandelt. Hier nur einige knappe Beispiele zur Optimierung:

 Codegenerierung

Dieser Schritt ist von der Zielhardware abhängig. Die Codegenerierung hat folgende Aufgaben:

Maschinenabhängige Optimierung

Sie dient dazu, Laufzeit und Speicher zu minimieren und ist stark von der jeweiligen Zielhardware abhängig.

 2. Schritt: Binden des Objektcodes zum lauffähigen Programm

Ein Objektcode liegt bereits in Maschinensprache vor, enthält aber noch zusätzliche Informationen für das Bindeprogramm, während Anschlüsse für das Betriebssystem fehlen. Aus diesen Gründen ist ein Objektcode nicht ablauffähig. Damit aus einem oder mehreren Objektcodes und ggf. auch Bibliotheken ein lauffähiges Programm entsteht, muß ein Bindevorgang stattfinden.

***Bild***

Der Linker (Binder, Bindeprogramm) erhält als Eingabe einen oder mehrere Objektcodes sowie Bibliotheken. Das Binden faßt diese Objekte zu einem kompletten, ablauffähigen Programm zusammen. Der Linker hat folgende Aufgaben:

 Überblick über die Phasen der Compilierung

Dieser Abschitt faßt die Phasen der Übersetzung in Form eines Diagramms zusammen:

Quellprogramm
***Pfeil***
bbb
***Pfeil***

***BILD***

Methodik der Sprachimplementierung

Dieser Abschnitt beschreibt ein Darstellungsmittel, die sogenannten T-Diagramme, mit dem Zusammenhänge im üblichen Umfeld einer Programmiersprachenumgebung beschrieben werden. Wir werden in Form einer tabellarischen Aufstellung die grundlegenden Definitionen treffen und einige Verfahren kennenlernen, wie sich - zumindest in groben Zügen - Compiler portieren bzw. entwickeln ("Bootstrapping") lassen.

 T-Diagramme

Einige Festlegungen und Folgerungen in tabellarischer Übersicht:

***Bild***     Es seien
  • P ein Programm
  • S (Source Language) eine Sprache, in der P geschrieben ist

Mit Hilfe einer graphischen Notation läßt sich das Programm P wie links gezeigt darstellen.

***Bild***     Compiler sind - als spezielle Programme - von drei Sprachen abhängig:
  • Quellsprache S. In ihr werden die zu übersetzenden Programme geschrieben
  • Zielsprache T. In sie wird das Programm übersetzt
  • Sprache I, in der der Compiler geschrieben ist, bzw. vorliegt

Die Bezeichnungen S, T und I sind Abkürzungen für die entsprechenden englischsprachigen Begriffe Source S, Target T und I Implementation Language. Den Zusammenhang veranschaulicht man sich durch ein T-Diagramm. Im Text wird die Notation SIT verwendet.

Ein konkretes Beispiel für ein T-Diagramm liefert ein Pascal-Compiler, der in 8086-Assembler (Äquivalent: Maschinensprache) geschrieben ist und einen 8086-Maschinencode erzeugt; ein solcher Compiler ist für die MS-DOS-Welt typisch:

***Bild***

Der Compiler selbst muß nicht unbedingt in der 8086-Maschinensprache verfaßt sein; er kann seinerseits durch Übersetzung aus einer Hochsprache erzeugt worden sein. Für die Betrachtung innerhalb des T-Diagramms ist lediglich entscheidend, wie er sich in dieser Situation, z.B. bei der Übersetzung eines Quellprogramms darstellt.

Ein Compiler kann auch einen Code für eine andere Maschine erzeugen als für die, auf der er selbst läuft. Compiler dieser Art nennt man Crosscompiler. Diese werden häufig in der Systementwicklung eingesetzt. So gibt es in der weitverbreiteten MS-DOS-/Windows-Welt Crosscompiler für alle gängigen Mikroprozessoren. Damit kann ein Entwickler Programme für einen Zielrechner schreiben, auf dem keine komfortablen Compiler verfügbar sind. Häufig ist auch der Fall, daß Crosscompiler für noch nicht existierende Zielrechner geschrieben werden, um parallel mit der Hardware-Entwicklung Betriebssystem, Übersetzer und Programmwerkzeuge zur Verfügung zu stellen. Auf diese Art entstehen weniger Zeitverluste beim Entwickeln der Grundsoftware für eine neue Zielmaschine. Ein Pascal-Crosscompiler, der IBM/370-Maschinencode generiert, jedoch in 8086-Maschinencode läuft, sieht im T-Diagramm so aus:

***Bild***

Die Übersetzung eines Programms durch einen Compiler wird im T-Diagramm schematisch so dargestellt:

***Bild***

Wie am vorangegangenen Diagramm zu erkennen ist, können T-Diagramme wie Dominosteine aneinander angefügt werden. Allerdings müssen, genau wie beim Dominospiel, aneinandergrenzende "Steine" die gleichen Bezeichnungen tragen.

***Bild***     Zur Laufzeit wird das übersetzte Programm auf der Maschine T ausgeführt
Ein Programm hat für gewöhnlich:
  • E Eingabe
  • A Ausgabe
***Bild***     Beachten Sie die Ähnlichkeit zum T-Diagramm eines Compilers!
***Bild***     Tatsächlich kann das Übersetzer-Programm U, das auf der Maschine I läuft, als Programm betrachtet werden, das Eingaben der Sprache S verabeitet und Ausgaben in der Sprache T produziert. Die Ähnlichkeit ist also kein Zufall.

Betrachten wir den Übersetzungslauf in Zusammenhang mit der Laufzeit eines Programms, so ergibt sich das nachstehende Diagramm. Der gestrichelte Pfeil zeigt die Übernahme programmtechnischer Teile an:

***Bild***

Es sei an dieser Stelle nochmals daran erinnert, daß T-Diagramme wie Dominosteine aneinandergereiht werden können. Eine 2-Phasen-Übersetzung (hier ist der Begriff "Phase" nicht mit der Übersetzungs-"Phase" zu verwechseln, wie sie im Abschnitt xxx besprochen wird) stellt sich im T-Diagramm wie folgt dar:

***Bild***

Häufig wird eine maschinenunabhängige Zwischensprache Z verwendet. Der Codegenerator für die Zielmaschine M kann als einfacher Übersetzer aufgefaßt werden, der Programme der Sprache Z in für die Zielmaschine M verständliche Programme übersetzt. In diesem Zusammenhang stellt sich die folgende Frage:
 Frage:      Was unterscheidet einen Codegenerator von einem vollständigen Compiler?

Einen Sonderfall der Übersetzung stellt die Interpretation dar. Bei der Intepretation sind Übersetzungs- und Laufzeitschritte ineinander verzahnt.
***Bild***     Es sei ein Interpreter gegeben, der Programme der Sprache S interpretieren kann. Der Interpreter ist in der Sprache U geschrieben. Er erzeugt keinen lauffähigen Code; seine Darstellung im T-Diagramm ist der eines einfachen Programms gleichwertig.
***Bild***     Hingegen wird die Interpretation des Programmes P (mittels Interpreter S) auf dem Computer mit der Maschinensprache M wie folgt dargestellt.Beachten Sie, daß hier SM eine virtuelle Maschine ist!

Interpretation und Compilation können als Techniken kombiniert werden. So ist z.B. ein Compiler denkbar, der in einer Interpretersprache U geschrieben ist, aber Code für die Sprache M erzeugt. Voraussetzung ist, daß ein Interpreter für U verfügbar ist:

***Bild***

Wenngleich die Übersetzung auf diese Art sehr laufzeitintensiv werden kann, werden die Laufzeit und die Abarbeitungsgeschwindigkeit des nach M übersetzten Programms P von der Laufzeit des Interpreters nicht beeinflußt.

Ein ebenfalls mögliches und oft geschicktes Verfahren ist, den Compiler keinen Maschinencode erzeugen zu lassen, sondern einen (maschinenunabhängigen) interpretationsfähigen Zwischencode Z. Das Ausführen bzw. Umsetzen des Zwischencodes kann entweder zur Laufzeit als echte Interpretation erfolgen oder aber auch als ein nachgeschalteter Übersetzungsschritt von vergleichsweise geringem Aufwand. Wir werden dieses Themengebiet in den nachfolgenden Abschnitten gelegentlich streifen.
Wer mit Java vertraut ist, wir das Konzept wiedererkennen; die Zwischenspache Z entspricht dem sog. Bytecode.

 Portierung

Wie stellt man am einfachsten einen Compiler auf einer bestimmten Zielmaschine zur Verfügung? Der häufig verwendete und naheliegendste Weg ist, einen bereits als Quellcode vorliegenden Compiler zu portieren, also auf eine neue Zielmaschine zu übertragen.

Gegeben sei folgende Situation:

Es existiert bereits ein Pascal-Compiler, der in C geschrieben ist und IBM/370-Maschinencode erzeugt:

***Bild***

Dieser Compiler soll nun auf einer /370-Maschine lauffähig werden. Dazu muß er in /370-Code übersetzt werden durch:

***Bild***

Man erhält P/370/370, formal:

***Bild***

Allgemein sieht dieser Mechanismus so aus:

***Bild***

Gemäß der vorhin definierten Formelschreibweise:

LSN + SKM = LMN

Ein häufiges Problem ist, einen Compiler LLN auf eine Maschine N zu portieren (Der Compiler für die Sprache L ist also in L geschrieben und erzeugt Code in N - nur auf N ist er nicht lauffähig). Allerdings - auf einer anderen Maschine M gibt es bereits einen Compiler LMM...

***Bild***

Lösungsschritte: Zuerst wird LLN auf der Maschine M mit dem Compiler LMM übersetzt. Ergebnis ist der Crosscompiler LMN. Mit ihm wird der Compiler LLN nochmals übersetzt. Resultat ist der gewünschte Compiler LNN, der auf der Maschine N lauffähig ist (obwohl er auf der Maschine M übersetzt wurde).

Zusammenfassung:

***Bild***

 Bootstrapping

Die Portierung eines bereits bestehenden Compilers ist nicht immer möglich. Muß ein Übersetzer für eine Zielmaschine gänzlich neu geschrieben werden, dann ist es zweckmäßig, ihn in überschaubaren Teilschritten zu implementieren.

Beim Bootstrapping wird ein Compiler dazu verwendet, sich selbst zu übersetzen. Die Schwierigkeit ist: Wie wird der erste Compiler übersetzt?

Gegeben sei folgende

 Aufgabe:     Für eine neue Sprache L soll auf der Maschine mit Maschinensprache M ein Compiler erstellt werden.



 Lösung:     Zunächst wird eine Teilsprache S von L erstellt und ein Compiler hierfür geschrieben. Bei geschickter Wahl der Teilsprache S wird dies ein überschaubarer Aufwand. In der formalen Darstellung ist das SMM. In der Sprache S wird ein Compiler für L geschrieben, also LSM. Wenn LSM von SMM compiliert wird, erhält man den gewünschten Compiler LMM:

***Bild***

Oft werden mehrere Bootstrapping-Schritte verwendet, insbesondere wenn die Sprache, die der neue Compiler übersetzen soll, komplex ist.

weiter im Text


Stand: 19.11.2002 /
 HPs Home      Compiler/Home