Um ein Quellprogramm in ein entsprechendes Zielprogramm abzubilden, sollten folgende Compilerphasen verwendet werden: - Präprozessor
Der Präprozessor vereinfacht das Quellprogramm für die nachfolgenden Compilerphasen. Diese Vorbereitung auf den eigentlichen Kompiliervorgang bringt ein neues Programm hervor, welches zum Quellprogramm äquivalent ist und zudem immer noch in der Quellsprache liegt. Es werden praktisch alle Elemente, die das Quellprogramm für den Menschen lesbarer machen, durch den Präprozessor eliminiert. Beispielsweise werden Kommentare, welche eigentlich nur für den Menschen vorgesehen sind, entfernt. Auch werden alle im Code verwendeten Makros ersetzt (Makroexpansion). Zudem werden ganze Dateien mittels Dateiinklusion "#include <dateiname>" in den zu kompilierenden Code kopiert. Da nun derartige Nebensächlichkeiten wegfallen, nimmt der Schwierigkeitsgrad für die Entwicklung der nachfolgenden Compilerphasen ab.
 Einfaches Beispiel eines Präprozessors: - Setty−Präprozessor
Im Folgenden wird das durch den Präprozessor veränderte Quellprogramm ebenfalls als Quellprogramm bezeichnet. - Front-End
Das FrontEnd ist der Analyseabschnitt eines Compilers. Dieser Abschnitt ist dafür zuständig, das Quellprogramm zu lesen, zu strukturieren und zu prüfen, ob das Quellprogramm überhaupt „richtig” sein könnte (lexikalische und syntaktische Korrektheit des Quellprogramms). Jede einzelne Phase des FrontEnd kann auf Fehler im Quellprogramm stoßen und eventuell diese auch eigenständig beheben bzw. übergehen. - Lexikalische Analyse (Scanner)
Bei der lexikalischen Analyse wird mit Hilfe des Scanners 1) der Zeichenstrom des Quellprogramms in eine Folge von bedeutungstragenden Symbolen (Tokens) transformiert. Diese Symbole sind üblicherweise Terminalsymbole der Grammatik der Quellsprache. Ein Symbol des Scanners wird als Token bezeichnet. Der Scanner transformiert beispielsweise Folgen von Ziffern in Zahlen (Tokens) und Folgen von Buchstaben in Worte (Tokens). Überflüssige Zeichenfolgen wie beispielsweise Leerzeichen oder Tabulatoren sind in den meisten Sprachen keine Tokens. Dank der Vorarbeit des Scanners muss der Parser nichts über die textuelle Zusammensetzung einer Zahl bzw. eines Wortes bzw. eines Tokens wissen. Da sich der Scanner um diesen „Kleinkram” kümmert, kann sich der Parser bei der syntaktischen Analyse ungestört größeren Aufgaben widmen. :−)
 Scanner-Beispiele: - Setty−Scanner
- Tinyray−Scanner
- Prolog−Scanner
- Syntaktische Analyse (Parser)
Bei der syntaktischen Analyse prüft der Parser, 2) ob die Folge der Tokens, die er vom Scanner erhält, in der (kontextfreien) Grammatik der Quellsprache liegt. Während dieser Prüfung werden die Tokens nach und nach in eine Datenstruktur (spezieller Mehrwegebaum) eingepaßt. Der dabei entstehende Mehrwegebaum repräsentiert das Quellprogramm in Form einer für die nachfolgenden Compilerphasen handhabbaren Datenstruktur, der sogenannte Syntaxbaum (zu englisch: parse tree). Mit der iterativen und vollständigen Abarbeitung des Quellprogramms über die Schnittstelle des Scanners endet der Parsevorgang. Das Endprodukt der syntaktischen Analyse ist der Syntaxbaum. Dieser Syntaxbaum stellt viele nützliche Operationen zur Verfügung, welche die weitere Analyse und Verarbeitung des Quellprogramms (diesmal als Datenmenge des Syntaxbaumes) erheblich erleichtern.
 Parser-Beispiele: - Setty−Parser
- Tinyray−Parser
- Prolog−Parser
- Semantische Analyse
Bei der semantischen Analyse wird der Syntaxbaum auf Fehler geprüft und die Codeerzeugung vorbereitet. Die semantische Analyse wird notwendig, wenn die Quellsprache kontextsensitiv ist. Eine Quellsprache ist kontextsensitiv, wenn sie beispielsweise Variablen und Funktionen verwendet. Bei der Verwendung von typisierten Variablen ist eine Namens− und Typanalyse (type−check) sinnvoll, um sicherzustellen, dass die Variablen auch typgerecht eingesetzt werden.
- Back-End
Das BackEnd ist der Syntheseabschnitt eines Compilers, der für die Erzeugung des Zielprogramms zuständig ist. - Zwischencodeerzeugung
Bei der Zwischencodeerzeugung wird das Zielprogramm generiert, wobei hier die Aspekte der Optimierung noch fehlen. Sinn macht aber auch, die Generierung in eine Pseudosprache, welche sich besonders gut als Grundlage für den Optimierer eignet. - Optimierung
Bei der Optimierung wird der Zwischencode optimiert, d. h. der Zwischencode wird in ein gleichwertiges Programm umgewandelt, welches weniger Speicher und weniger Rechenzeit bei der Ausführung benötigt. Beispielsweise verspricht man sich von der Eliminierung überflüssiger Variablen sowie überflüssiger Funktionen ein effizienteres Zielprogramm. Oft erreicht man durch Aufhebung der Modularität weitaus effizientere Zielprogramme. Zur Optimierung des Zwischencodes finden häufig bewährte Heuristiken, aber auch relationale Datenbankmanagementsysteme Anwendung. - Codeerzeugung
Nun denn, die Ausgabe des optimierten Zielprogramms!
Durch die Aufteilung des Compilers in die eben vorgestellten Phasen, wird es erheblich leichter, einen bereits existierenden Compiler durch entsprechende Anpassung des BackEnd an weitere Zielprogramme anzupassen; umgekehrt kann ein Compiler auch leichter durch entsprechende Anpassung des FrontEnd an weitere Quellsprachen angepasst werden.
|