Bevor es nun daran geht, das Programm „Übersetzer“ auf Basis bekannter Definitionen neu zu beschreiben, möchte ich nochmals auf die Klassifizierung von Programmiersprachen eingehen und anstelle der gängigen Interpreter und Compiler Definitionen ein alternatives 10 Phasen Modell vorschlagen. Darin lassen sich die gängigen Programmiersprachen klassifizieren, aber auch andere mit ähnlichen Techniken arbeitende Werkzeuge, die weniger als Programmiersprache angesehen werden.
Der Sinn eines Übersetzers ist die Transformation von einem Quelltext in ein Format, das die Maschine in irgendeiner Form abarbeiten kann. Dem Entwickler geht es dabei natürlich nicht darum, dass die Maschine das Programm abarbeiten könnte, sondern dass sie es tut. 
Ein Übersetzer ist also ein Hilfsmittel, um eine vereinfachte Beschreibung zu einer Darstellung zu überführen - bei Programmiersprachen handelt es sich um die Übersetzung eines Programmtextes in ein laufendes Programm. Für das Modell ist dabei nicht das lauffähige „Executable“ auf der Festplatte gemeint, sondern die tatsächliche Ausführung, die die Darstellung des Programmtextes repräsentiert. 
Zum besseren Verständnis denke man an eine Skriptsprache, die nicht kompiliert wird, aber dennoch übersetzt. Der Übersetzer (z.B. Basic, PHP, Bash-Skript) wird hier nicht verwendet, um das Skript in ein anderes Format zu transformieren, sondern die Anweisungen sollen möglichst schnell zur Ausführung gebracht werden. 
Die Unterteilung der Sprachen erfolgt über die Anzahl der Schritte, die durch die Sprache unterstützt werden müssen, bis das Programm ausgeführt wird.
Der Weg von Quelltext zum laufenden Programm kennt dabei verschiedene Wege und vor allem eine Reihe von Abkürzungen.
Dieses Modell ist nicht nur für eine theoretische Klassifizierung interessant, es wird nachher wieder aufgegriffen und hilft dabei, zu entscheiden, wo wertvolle Ergebnisse für die Weiterverarbeitung abzugreifen wären.
In Kapitel 9 wurde gezeigt, dass die Bedeutung der Begriffe von Interpretern und Compilern zu ungenau sind, um Programmiersprachen zu unterteilen. Es wird eine genauere Klassifizierung erforderlich, deren Zuordnung über die Compilerphasen geschehen soll. Die Unterteilung soll über die letzte durchlaufende Phase vor der eigentlichen Ausführung bestimmt werden. Dabei dürfen durchaus Phasen (z.B. die Optimierungsphasen) übersprungen werden, ausschlaggebend bleibt die zuletzt ausgeführte Compilerphase. Hier reichen die 6 Compilerphasen jedoch auch nicht aus, da zum Beispiel auch fertig kompilierte Java-Class-Dateien 6 Phasen durchlaufen haben, aber dennoch nicht lauffähig sind. Nach dem Kompilieren muss in Java offensichtlich noch mehr geschehen als in anderen kompilierenden Sprachen und genau daran lässt sich der Unterschied zwischen z.B. Java und einer anderen kompilierenden Sprache, wie zum Beispiel C finden.
Um den Weg vom Quelltext zum laufenden Programm aufzuzeigen, füge ich weitere Phasen ein. Die Phasen 1-6 entsprechen wie gehabt der Kompilierung. Die Phasen 7-10 beschreiben den Übergang des Produktes nach dem Compilerlaufs bis zum wirklichen laufenden Programm. Diese Einteilung erweitert das übliche 6 Phasen Modell zur Kompilierung, entsprechend konnte ich keine Namen für die zusätzlichen Phasen übernehmen. Aus diesem Grund habe ich mich bemüht, die zusätzlichen Phasen mit angemessenen, eigenen Namen zu versehen.
Programm, als serialisierte Zeichenfolge (Quelltext)
| Phase | Name | Produkt | 
|---|---|---|
| 1 | Lexikalische Analyse | Serialisierte Token | 
| 2 | Syntaktische Analyse | Programm als Parserbaum | 
| 3 | Semantische Analyse | Programm als Parserbaum, semantisch korrekt | 
| 3b | Parserbaum Optimierung | Programm als Parserbaum, semantisch korrekt | 
| 4 | Zwischencodeerzeugung | Programm als flacher, serialisierter Pseudo-Code | 
| 5 | Codeoptimierung | Programm als flacher, serialisierter Pseudo-Code | 
| 6 | Codegenerierung | Compilerprodukt (z.B. Objektfile) | 
| 7 | statisches Linken | Binden mehrerer Objektdateien | 
| 8 | Produktinterpretation | Endprodukt wird erneut gelesen | 
| 9 | dynamisches Linken | Binden einer Speicherdarstellung aus mehreren Compiler-Produkten | 
| 10 | JIT-Kompilierung | Endprodukt wird plattformoptimiert | 
Ausführung über nativ laufende Anwendung
Das Objektfile beschreibt hier eine Datei, die viele Compiler erzeugen: ein nicht ausführbares Objekt-File, dass Maschinen-Code enthält. Es gibt jedoch auch Compiler, die sofort ausführbaren Code erzeugen oder andere Produkte (z.B. LaTeX), die kein Binden erfordern und somit die 7. Phase überspringen können.
Als nativ laufende Anwendung ist ein auf der Plattform laufendes Programm definiert. Dies bedeutet allerdings nicht zwangsläufig die Übersetzung in ein nativ laufendes Compilerprodukt. Das Compilerprodukt kann die nativ laufende Anwendung darstellen, aber auch ein natives Programm, das den Quelltext parst und direkt ausführt, oder das Compilerprodukt weiterverarbeitet. 
Wichtig ist die letzte Phase, an der der Übersetzer zuletzt Daten verändert hat. 
Ein Bash-Skript wird durch die Bash geparst und zur Ausführung gebracht, ein Perl-Programm wird übersetzt und direkt zur Ausführung gebracht, ein C-Programm liegt nativ auf der Platte vor und kann selbstständig laufen, ein Postscript-File ist zum Beispiel durch einen LaTeX-Kompiler gelaufen, wird allerdings durch ein nativ laufendes Programm zur Darstellung gebracht und eine Java-Klasse liegt kompiliert vor, muss jedoch durch die nativ laufende Virtual Maschine weiterverarbeitet werden.
Die Entscheidung, ab welcher Compilerphase das Programm zur Ausführung oder Darstellung kommt, ist für alle mir bekannten Sprachen in einem engen Bereich festgelegt und verschiebt sich bei Sprachen wie C oder Java maximal um eine Phase. Die meisten Sprachen sind fest auf eine Phase festgelegt und können so eindeutig in diesem Modell klassifiziert werden.
Diese Klassifizierung basiert auf einer eindeutigen Eigenschaft des jeweiligen Kompilers und damit auch der mit diesem Kompiler übersetzten Programmiersprache. Diese Festlegung spezialisiert kompilierende Sprachen für schnelle Berechnungen und disqualifiziert sie für Kleinst-Anwendungen wie beispielsweise Bash-Skripte oder Webdesign und umgekehrt eignen sich Sprachen, die kein natives Produkt liefern nicht für schnelle Berechnungen, da ihre Programme durch einen langsamen Interpreter ausgebremst werden. 
Alle Programmiersprachen haben sich durch Ihren Übersetzertyp klar in engen Grenzen festgelegt und besitzen damit eine Spezialisierung auf die Gebiete, die dieser Übersetzertyp abdeckt. Sie sind entsprechend weniger für Anwendungen geeignet, die mit einem anderen Übersetzertyp besser bedient wären.
Die einfachste Form eines Programms springt sofort nach der ersten Phase zur Ausführung. Als Beispiel sei ein Batch-Skript genannt, das Befehl für Befehl aufruft. Alte Basic-Sprachen aus Zeiten des C-64 fallen ebenfalls in diese Kategorie, die ein Programm bis zu einem syntaktischen Fehler laufen ließen und anschließend das laufende Programm abbrachen. Eine vollständige syntaktische Analyse wurde vor der Ausführung nicht vorgenommen. Ebenfalls lassen sich die automatisierten Webstühle aus dem 19. Jahrhundert widerspruchsfrei in diese Kategorie einordnen. Als moderner Vertreter eines derartigen Basics findet sich die interne Sprache moderner Web-Browser: JavaScript.
Eine Sprache, die nach der syntaktischen Analyse direkt zur Ausführung kommt, bearbeitet auch fehlerhafte Programme bis zu einem Punkt an, dem ein semantischer Fehler auftritt, korrekt. Dies trifft vor allem untypisierte Sprachen, bzw. Sprachen mit untypisierten Variablen. Die Sprachen Prolog und PHP siedeln sich in diesem Bereich an, allerdings auch nur mit einer recht groben Syntaxprüfung vor Ausführung. AmOS gehört ebenfalls in diese Kategorie und prüfte den Quelltext vollständig.
Sprachen, deren Ausführung nach der dritten Phase startet, sind kompilierende Sprachen. Sie bauen einen Parserbaum auf und starten die Programmausführung frühestens, wenn das Programm keine syntaktischen oder semantischen Fehler mehr beinhaltet.
Semantikprüfende Sprachen bauen also ausschließlich auf syntaxprüfenden Sprachen auf und prüfen den durch die Syntaxanalyse vorhanden Syntaxbaum auf Typfehler ab und verhalten sich dann vergleichbar wie Phase 2-Sprachen.
Da es sich anbietet, die ersten drei Phasen in einem One-Pass-Compiler von Phase 2 aus zu steuern und die drei Phasen als ein Frontend zu entwickeln, existieren hier keine Sprachen, die Zwischenergebnisse abgreifen. 
Der Weg vom Quellcode zum semantisch korrekten Parserbaum wird also in allen (mir bekannten) Sprachen ohne Einflussmöglichkeiten innerhalb der ersten drei Compiler-Phasen durchgeführt. Dies ist nachvollziehbar, da es zum einen unsinnig ist, diese 3 ineinander verwobenen Phasen bei einer Kompilierung zu trennen, zum anderen liegen zwischen den Phasen keine für den Benutzer interessanten Zwischenergebnisse vor.
Das Ergebnis der Phase 3 (vor Phase 3b) ist das letzte Zwischen-Ergebnis, das garantiert dem Quelltext entspricht. Es wird von keiner mir bekannten Sprache verwertet oder für andere Software bereitgestellt, bzw. keine mir bekannte Sprache erlaubt den direkten Zugriff auf den Parserbaum.
Dieser Zwischenschritt ist der erste, an dem das Programm als korrekt bezeichnet werden darf. Korrekt bedeutet hier, dass der Compiler die Beschreibung versteht, die der Programmierer ihm mitteilt. Die Beschreibung muss deswegen nicht zwangsläufig die Lösung für das das Problem des Programmierers darstellen oder frei von Laufzeitfehlern sein. Lediglich der Quellcode konnte als syntaktisch und semantisch korrekt bewertet werden.
In diesem Zwischenschritt ist zusätzlich der strukturelle Aufbau des Programms im Parserbaum exakt wie im Quelltext abgebildet. Diese Informationen müssen sich Entwicklungsumgebungen und Debuggingtools zur Zeit selbst erarbeiten - in der Hoffnung, dass sie mit Ihrer Interpretation der Interpretation des Compilers entsprechen.
Entsprechend sind derartige Übersetzer in Editoren und Debuggingtools zu finden, deren Ausführung die anschaulichere Darstellung von Quelltext auf dem Bildschirm darstellt.
Den technischen Aufwand 3 Phasen zu programmieren und den zeitlichen Aufwand ein Programm durch alle drei Phasen zu schicken, lohnt bei Programmiersprachen nur, wenn man diese Informationen anschließend entsprechend weiterverwerten kann. Im Falle einer Programmiersprache lohnen sich derartige statische Tests nur dann, wenn man zur Laufzeit Rechenzeit durch die Vermeidung von dynamischen Tests sparen kann und dafür optimierten Zwischencode erstellt.
Durch diese Form der Klassifizierung fallen die „interpretierenden“ Sprachen JavaScript und Perl in unterschiedliche Kategorien und werden so entsprechend ihrer Leistung und Ausführungsgeschwindigkeit sortiert.
Die Codeoptimierung tritt hier eher als optionales Feature auf. Bezüglich der Klassifizierung entsprechen sie aufwendigeren Phase 4- Sprachen.
Phase 6-Sprachen, also für den Benutzer sichtbaren Code generierende Sprachen, werden umgangssprachlich als „Compiler“ bezeichnet. Diese Form der Sprachen ist wie zuvor beschrieben, heute eher seltener anzutreffen. In (umgangssprachlich) kompilierenden Sprachen werden heutzutage so große Projekte abgewickelt, dass ein vollständiges ‚mal eben schnell’ Kompilieren für einen Test einfach zu lange dauern würde. Da eine Phase 6-Sprache sich dadurch auszeichnet, dass sie sofort lauffähige, vollständige Programme erzeugt, wird dieser Typ eher bei Programmiersprachen für kleinere Projekte benutzt. Als Beispiel ist hier die Sprache PureBasic genannt. Es entsteht kein Objektfile, die 7. Phase wird somit übersprungen. Das Produkt liegt sofort vor.
Sprachen der Phase 7 beherrschen die Möglichkeit ein Programm zu erzeugen, durch mehrere getrennte, produkterzeugende Compilerläufe für jeweils einen Abschnitt des Programms. Die Produkte der einzelnen Abschnitte werden in der 7. Phase zum Endprodukt gebunden werden und können anschließend gestartet werden. Als Beispiele bieten nahezu alle bekannteren Sprachen von hier zum laufenden Programm springen zu können: C/C++, Pascal, Fortran… 
Hier sitzt ein für die Systemprogrammierung wichtiger Punkt. Da alle nachfolgenden Phasen vom Systemlader erledigt werden, muss für alle Programmiersprachen, die über diese Phase hinausgehen müssen, auch ein System vorhanden sein. Das bedeutet, dass alle Sprachen, die diesen Punkt überschreiten müssen, nicht für Embedded-System- oder Systemprogrammierung geeignet sind. 
Sprachen, die hier abbrechen zur Programmausführung springen können, können geeignete Formate erzeugen, um Flash-ROMs zu beschreiben oder Betriebsystem-Kernel zu kompilieren. Statisch bindende Sprachen erzeugen Produkte, die ohne ein anderes Betriebsystem gestartet werden können. 
Folgendes ist zu beachten: Statisch gebundene Programme können durchaus auch einen dynamischen Part besitzen.
Nachdem ein Compilerprodukt durch ein System geladen wurde, kann es erforderlich sein, dass weitere Dateien nachgeladen werden müssen. So sind die C/C++-Standardfunktionen bei Linux in der Datei „libc.o“ ausgelagert und werden durch den Linker üblicherweise nicht eingebunden. Es wird lediglich ein Verweis auf die „libc.o“ in die ausführbare Datei hinzugefügt. Da nahezu jedes Programm Routinen aus dieser Bibliothek verwendet, wären ansonsten manche Funktionen dieser Bibliothek tausendfach auf der Festplatte geschrieben und würden dort nur unnötig Platz verbrauchen. Aus diesem Grund muss der Lader zunächst die entsprechenden Bibliotheken in den Speicher nachladen. Diesen Vorgang nennt man dynamisches Linken. Diese Entwicklung lässt sich unter Linux durch das Erscheinen des Enhanced-Link-Formates zeigen. Das zuvor verwendete a.out-Format unterstützte lediglich das statische Linken, dass in Phase 7 geschieht.
Auch hier finden sich Klassiker wie C/C++ wieder, deren Linker Programme erzeugen kann, die im entsprechenden Format des Betriebsystems sind. Dafür müssen die Linker für Linux das neuere ELF (Enhanced Link Format) beherrschen, bzw. für Windows das PE-Format. Der Systemlader des Betriebsystems versucht nach dem Laden des Programms alle Abhängigkeiten aufzulösen, erst dann wird das Programm gestartet. Diese Programme sind nicht mehr autonom lauffähig. Ohne den Systemlader oder die Dateien, zu denen Abhängigkeiten bestehen, ist die Ausführung nicht möglich.
Warum fallen diese dynamisch bindenden Sprachen dann noch in die Phase 7? 
Weil der Übersetzer für dieses Binden nicht mehr verantwortlich ist. Er hat eine ausführbare Datei für das passende Betriebsystem abgegeben, die Phase ist abgebrochen, das Programm ist ohne weitere Einwirkung des Übersetzers lauffähig. Das eventuell mögliche dynamische Binden, dass das Betriebsystem übernimmt, ist Teil der Architektur, für die kompiliert wurde.
Diese Kategorie durchläuft auch Java oder C#. Wird eine Java-Class-Datei geladen und in dieser Phase werden die „import“-Referenzen nachgeladen. Allerdings kann Java hier noch nicht starten, sondern muss noch mindestens die nachfolgende Phase durchlaufen. Im Gegensatz zum dynamischen Binden durch das System, müssen hier mit Hilfe des Übersetzers oder Tools, die zur Sprache gehören (z.B. die JavaVM oder das .NET-Framework) die Referenzen auf andere Komponenten aufgelöst werden.
Compilerprodukte, die diese Phase erreichen, können nicht selbstständig nativ auf dem Prozessor ablaufen, sondern nur auf einer Zwischenschicht. Bei Java-„Executables“ entspricht dies der Virtual Maschine, .NET-Assemblies werden durch das .NET Framework geladen, PostScript-Dateien werden von einem entsprechenden Anzeige- oder Druckprogramm geöffnet. Diese Produkte sind nicht mehr autonom lauffähig, können also nicht ohne Software, die zusätzlich zum Betriebsystem existiert, benutzt werden.
Nach dem Laden durch den Interpreter für das entsprechende System sind die Programme anschließend darstellbar. Produktinterpretierende Sprachen besitzen jedoch wieder den Vorteil maschinenunabhängig zu sein.
JIT-ausgeführte Sprachen existieren offensichtlich erstmal nur wenige. Vorrangig fällt hier Java auf, das tatsächlich alle 10 Phasen durchlaufen muss, bevor es bedarfsgerecht kompiliert zur tatsächlichen Ausführung kommt. Dies trifft allerdings auch auf Emulatoren zu, die durch JIT-Compiler beschleunigt werden. Ein Macintosh-Programm, das für einen MC680x0-Prozessor kompiliert wurde und dort läuft, konnte auf einem MC680x0-Macintosh in 7 Phasen lauffähig gemacht werden. Auf einem Macintosh mit PowerPC-Prozessor ist der MC68k-MaschinenCode ähnlich eines Java-Class-Files zwar weiterhin das Endprodukt des Compilers, es sind allerdings weitergehende Schritte erforderlich, um dieses Programm tatsächlich darzustellen. Der 68k-Maschinencode ist für den PowerPC Prozessor nicht lesbar, das Compiler-Produkt muss also interpretiert werden und wird anschließend in dieser Phase beschleunigt, so dass Power-PC-Maschinencode abgearbeitet werden kann. 
Java und MSIL (Microsoft Intermediate Language, Entsprechung zum Java-Bytecode) werden beide Just-In-Time kompiliert.
Auffällig ist dass Sprachen bei brauchbarer Implementierung in den ersten 6 Phasen mit jeder durchlaufenden Phase an Ausführungsgeschwindigkeit zulegen. Sie werden mit jeder weiteren Phase in eine für den Computer einfacher und schneller zu interpretierenden Form gebracht: Quelltext, Parserbaum, semantisch fehlerfreier Parserbaum, optimierter Parserbaum, Zwischencode als flache Darstellung des Parserbaums, optimierter Zwischencode und abschließend die Darstellung als native Maschinensprache, so dass auf einen Interpreter vollständig verzichtet werden kann.
Die 7. Phase wirkt sich auf die Ablaufgeschwindigkeit des Programms nicht mehr aus, allerdings auf die Dateigröße der ausführbaren Datei. Ist das Compilerprodukt nicht sofort ausführbar, so bedeutet dies eine Verschlechterung der Startgeschwindigkeit. Dies scheint bei dynamisch bindenden Sprachen durchaus sinnvoll, da mit dem verzögerten Startverhalten wertvoller Festplatten und vor allem Arbeitsspeicher geschont wird, da gemeinsam benutzte Bibliotheken auch nur einmalig auf Festplatte oder im Arbeitsspeicher vorkommen müssen. 
Weiterhin sind die häufig benötigten Objektfiles in der Regel mit dem Betriebsystem bereits geladen, somit ist der verzögerte Start nur beim ersten Laden einer Bibliothek nennenswert.
Produktinterpretierende Sprachen sind mit Zwischencode erzeugenden Sprachen vergleichbar, die Ihren Zwischencode lediglich für die wiederholte Ausführung auf der Festplatte ablegen. 
Die 10. Phase funktioniert gewissermaßen als Rettungsanker, um die aufwendig bearbeiteten Sprachen wieder zu einer brauchbaren Geschwindigkeit zu verhelfen. Hier wird der Aufwand gewissermaßen verdoppelt, in dem zwei Compiler hintereinander geschaltet werden und das Zwischenprodukt auf die Festplatte geschrieben wird. Durch das Laden des zweiten Compilers zur Laufzeit benötigt der erste Start einer JIT-ausgeführenden Sprache extrem lange. Hinzu kommt, dass das Kompilieren zur Laufzeit, die Ausführung ebenfalls verlangsamt, bzw. hohen Speicherverbrauch zur Folge hat. Da die laufende Darstellung einer JIT-ausgeführten Sprache allerdings annähernd die Qualität einer statisch gebundenen Sprache besitzt, unterscheidet sich Laufzeit nach dem Start im optimalen Fall nur unwesentlich. Zusätzlich kann beliebig zwischen nativem Code und Interpretierung gewechselt werden.
Diese Darstellung zeigt, dass das Durchlaufen aller Phasen bis einschließlich Phase 7 die optimale Start- und Ausführungsgeschwindigkeit eines Programms ermöglicht. Beginnend mit Phase 8 erscheinen nützliche Optionen, die sich jedoch negativ auf die Start- und Ausführungsgeschwindigkeit auswirken und die Umgebung, an der diese Programme lauffähig sind, eingrenzen und durch höheren Resourcenverbrauch zusätzlich belasten.
Daraus folgt, dass eine Sprache optimalerweise nach Phase 7 zur Ausführung gebracht werden kann.