Ein Übersetzer ist eine Erweiterung eines Parsers. Genau wie ein Parser analysiert ein Übersetzer die syntaktische Struktur eines Eingabewortes gemäß einer zugrunde liegenden Grammatik, führt aber nebenbei bestimmte Übersetzungsaktionen durch. Hierdurch rechnet er syntaxgesteuert das Eingabewort in einen bestimmten Wert einer Zielmenge um.
Ein Übersetzer berechnet also eine Abbildung von der Sprache, die durch die Grammatik erzeugt wird, in die Zielmenge. Darüber hinaus erzeugt der Übersetzer für alle Wörter, die nicht von der Grammatik erzeugt werden, eine Fehlermeldung.
Die Zielmenge kann auch wieder eine Sprache sein, aber genauso gut eine beliebige andere Menge. Wir werden im ersten einfachen Beispiel das Eingabewort in eine Zahl übersetzen; man kann aber auch einen regulären Ausdruck in einen Automaten oder ein C-Programm in Maschinencode übersetzen1).
Um aus einem Parser einen Übersetzer zu erzeugen, werden jeder Produktion der Grammatik bestimmte Übersetzungsaktionen zugeordnet. Der Übersetzer führt dann parallel zum Parsen die jeweiligen Übersetzungsaktionen aus. Die geeigneten Übersetzungsaktionen richten sich nach der gewünschten Abbildung in die Zielmenge; sie lassen sich durch Untersuchung der Ableitungsbäume von Wörtern ermitteln.
Im Programm unterscheidet sich ein Übersetzer von einem Parser dadurch, dass jede Funktion, die einer Variablen der Grammatik entspricht, einen Rückgabewert liefert, nämlich das Ergebnis der Übersetzung.
Um dieses Prinzip zu veranschaulichen, betrachten wir zunächst ein sehr einfaches Beispiel. Dem allgemeinen Vorgehen zur Konstruktion eines Übersetzers wenden wir uns dann später zu.
Als erstes formen wir den allereinfachsten Parser für einstellige Zahlen mit Vorzeichen in einen Übersetzer um. Das Ergebnis der Übersetzung beispielsweise des Eingabewortes "-5" soll der Zahlenwert -5 sein.
Die Untersuchung eines Ableitungsbaums etwa für "-5" zeigt, dass der Zahlenwert, den number liefert, durch Multiplikation von -1 oder +1 mit dem Zahlenwert, den digit liefert, zustande kommt. Hierbei wird der Vorzeichenfaktor -1 oder +1 von sign geliefert. Bild 1 zeigt den Ableitungsbaum (a) und den entsprechenden Datenflussgraphen (b).
Bild 1: Ableitungsbaum für -5 und entsprechender Datenflussgraph
Somit ordnen wir den Produktionen der Grammatik die folgenden Übersetzungsaktionen hinzu:
number ![]() | return sign()*digit() | |
sign ![]() | return +1 | |
sign ![]() | return -1 | |
sign ![]() | return +1 | |
digit ![]() | return 0 | |
![]() | ![]() | |
digit ![]() | return 9 |
Im Programm gehen wir vom Parser aus und versehen jede Funktion, die einer Variablen der Grammatik entspricht, mit einem geeigneten Rückgabewert, der sich aus der Anwendung der jeweiligen Übersetzungsaktionen ergibt.
In der Funktion digit arbeiten wir mithilfe der Funktion getSymbol das nächste Zeichen des Eingabewortes ab, wandeln es in eine Integer-Zahl um und geben diesen Wert zurück.
Die Funktion sign gibt +1 zurück, wenn ein Pluszeichen gefunden wird, bzw. -1, wenn ein Minuszeichen gefunden wird, ansonsten +1.
Und schließlich fügt die Funktion number die Ergebnisse von sign und digit in geeigneter Weise zusammen:
In folgendem Modul SimpleNumberCompiler sind diese Funktionen zusammengefasst. Die Hilfsfunktionen wie etwa comes oder consume finden sich im Basismodul Parser.
Im Hauptprogramm wird der SimpleNumberCompiler dann folgendermaßen getestet, hierbei wird die Funktion compile aufgerufen und der Rückgabewert gespeichert. Das Eingabewort und der Name der Funktion, die dem Startsymbol der Grammatik entspricht, werden als Parameter übergeben.
1) Übersetzer, die Programmcode in anderen Programmcode übersetzen, werden als Compiler bezeichnet
Weiter mit: [Recursive-Descent-Compiler für mehrstellige Zahlen] oder [up]