Hallo Martin,
Um mal einen Überblick über das Ganze zu geben, eine kleine Übersicht über Programmiersprachen und Compilerbau:
1. Was kann ein Computer
Prozessoren können sehr einfache, binär codierte Befehle ausführen.
Diese Prozessorspache in Klartext nennt sich dann Assembler und sieht etwa so aus:
LOAD <speicheradresse> <registernummer> # Etwas in ein Register (Speicherstelle, auf der ein Prozessor direkt arbeiten kann laden)
STORE <speicheradresse> <registernummer> # Zurück in den Speicher schreiben
ADD <registernummer> <registernummer> <registernummer> # Register addieren
Zwei zahlen addieren würde man also mit:
LOAD 1345 1
LOAD 6546 2
ADD 1 2 3
STORE 0345 3
Es gibt dann natürlich noch ein Haufen weitere Befehle für andere Rechenoperationen, Prozeduraufrufe, Sprünge im Programm usw.
2. Der klassische Compiler
Aufgabe eines Compilers ist es nun, eine Sprache mit komplexen Anweisungen zu verarbeiten und daraus die obige einfache Sprache zu machen.
Die Architektur eines einfachen Compilers sieht dann etwa so aus:
Quelltext --[parsen]--> Abstrakter Syntax Baum (AST) --[Maschinensprache Erzeugen]--> Maschinensprache
Die Programme, die so ein Compiler erzeugt, sind ziemlich ineffizient. Ein "guter" Compiler arbeitet also eher so:
Quelltext --[parsen]--> Abstrakter Syntax Baum (AST) --[Zwischencode erzeugen]--> Zwischencode --[optimieren]--> optimierter Zwischencode --[registerbelegung bestimmen]--> ... --[Maschinensprache erzeugen]--> Maschinensprache
Zwischencode ist dabei eine Art abstrakte Maschinensprache, auf der man Optimierungen durchführen kann (nicht verwendeten Code entfernen, Konstanten direkt einfügen, einfache Prozeduraufrufe durch direktes einfügen von Code ersetzen usw.)
3. Die "neueren" Konzepte: Skriptsprachen, Bytecode, JIT...
Mittlerweile gibt es eine ganze Menge weiterer Konzepte, wie Sprachen ausgeführt werden, von denen einige in diesem Thread auch schon erwähnt wurden:
-
Skriptsprachen (klassisch)
Skriptsprachen werden (klassischerweise) interpretiert. Dabei wird aus der Sprache der oben erwähnte AST erzeugt. Ein Interpreter führt diesen dann direkt aus, indem er die einzelnen Anweisungen erkennt und umsetzt.
Dabei spart man sich erstmal einiges an Arbeit für einen echten Compiler. Außerdem kann die Sprache Eigenschaften haben, die eine compilierte Sprache normalerweise nicht haben kann. Bspw. die Erzeugung oder Veränderung von Code zur Laufzeit eines Programms. (eval()-Funktion bei Javascript). -
Bytecode
Manche Programmiersprachen werden zunächst in eine abstrakte Maschinensprache (oft Bytecode genannt) umgesetzt ähnlich dem Zwischencode eines Compilers. Die Idee davon ist, dass man plattformunabhängige Programme erhält, aber dennoch im Gegensatz zu Skriptsprachen nicht alle Arbeit zur Laufzeit durchführen muss.
Der Bytecode muss natürlich auf jeder Zielplattform wieder irgendwie ausgeführt werden, indem er z.B. interpretiert wird wie eine Skriptsprache.
Sprachen mit diesem Konzept sind bspw. Java, C#, Perl 6 u.a. -
Just in Time Compiler (JIT)
Interpretieren von Sprachen ist nicht besonders performant.
Daher verwenden heutige, Bytecode-basierte (aber vermutlich auch einige Skriptsprachen) keinen oder nicht ausschließlich einen Interpreter. Stattdessen wird der Bytecode (bzw. sehr zentrale Teile davon) in Maschinensprache übersetzt.
4. Wie baue ich am einfachsten selber eine Programmiersprache
Am einfachsten ist es, sich eine Sprache auszusuchen, die ähnliche Konzepte bietet, wie die Sprache, die man selber bauen möchte und einen Cross-Compiler zu schreiben, der die eigene Sprache in die andere Sprache umsetzt.
Viele erste Implementierungen verwenden (zumindest zu Testzwecken) entweder einen einfachen Interpreter oder einen solchen Cross-Compiler.
Um den auch hier notwendigen Syntaxbaum zu erzeugen, gibt es für praktisch jede Programmiersprache Werkzeuge. Um einfach zu experimentieren, ist aber die Idee mit XML als Basis vielleicht auch nicht schlecht. In diesem Fall könnte man seinen "Compiler" von XML nach irgendeiner Hochsprache vermutlich sogar als XSLT-Stylesheet schreiben...
Grüße
Daniel