Systematische Programmentwicklung

Recursive-Descent-Übersetzer

Ein Übersetzer ist eine Erweiterung eines Parsers. Genau wie ein Parser analysiert ein Übersetzer die syntaktische Struktur eines Eingabe­wortes gemäß einer zugrunde liegenden Grammatik, führt aber nebenbei bestimmte Übersetzungs­aktionen durch. Hierdurch rechnet er syntax­gesteuert 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 Fehler­meldung.

Die Zielmenge kann auch wieder eine Sprache sein, aber genauso gut eine beliebige andere Menge. Im ersten einfachen Beispiel wird das Eingabewort in eine Zahl übersetzt. Andere Beispiele wären, einen regulären Ausdruck in einen endlichen Automaten zu übersetzen oder ein C-Programm in Maschinen­code zu übersetzen1).

Um aus einem Parser einen Übersetzer zu erzeugen, ordnen Sie jeder Produktion der Grammatik bestimmte Übersetzungs­aktionen zu. Der Übersetzer führt dann parallel zum Parsen die jeweiligen Übersetzungs­aktionen aus. Die geeigneten Übersetzungs­aktionen richten sich nach der gewünschten Abbildung in die Zielmenge; sie lassen sich durch Untersuchung der Ableitungs­bäume von Wörtern ermitteln.

Im Programm unter­scheidet 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 ver­anschaulichen, betrachten Sie zunächst ein sehr einfaches Beispiel. Dem allgemeinen Vorgehen zur Konstruktion eines Übersetzers wenden Sie sich dann später zu.

Zahlen mit Vorzeichen

Als erstes formen Sie den aller­einfachsten Parser für einstellige Zahlen mit Vorzeichen in einen Übersetzer um. Das Ergebnis der Übersetzung beispiels­weise des Eingabe­wortes "-5" soll der Zahlenwert -5 sein.

Die Untersuchung eines Ableitungs­baums etwa für "-5" zeigt, dass der Zahlenwert, den number liefert, durch Multi­plikation von -1 oder +1 mit dem Zahlenwert, den digit liefert, zustande kommt. Hierbei wird der Vorzeichen­faktor -1 oder +1 von sign geliefert. Bild 1 zeigt den Ableitungs­baum (a) und den ent­sprechenden Daten­fluss­graphen (b).

 

Bild 1: Ableitungsbaum für -5 und entsprechender Datenflussgraph 

Bild 1: Ableitungsbaum für -5 und entsprechender Datenflussgraph

 

Somit ordnen wir den Produktionen der Grammatik die folgenden Übersetzungs­aktionen hinzu:

number geht über nachsign digit return sign()*digit()
sign geht über nach+ return +1
sign geht über nach- return -1
sign geht über nachε return +1
digit geht über nach0 return 0
    drei Punkte übereinander      drei Punkte übereinander 
digit geht über nach9 return 9

Im Programm gehen Sie 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 Übersetzungs­aktionen ergibt.

Die Funktion digit ergänzen Sie, indem Sie für eine jeweilige gelesene Ziffer die ent­sprechende Zahl als Rückgabewert zurückgeben.

def digit():
    if comes("0"):
        match("0")
        return 0
    elif comes("1"):
        match("1")
        return 1
    elif ...
    ....
    else:
        raise Exception("Ziffer erwartet")

 

In der Funktion sign geben Sie +1 zurück, wenn Sie ein Pluszeichen finden, bzw. -1, wenn Sie ein Minuszeichen finden, ansonsten +1.

def sign():
    if comes("+"):
        consume()
        return +1
    elif comes("-"):
        consume()
        return -1
    else:
        return +1

 

Und schließlich fügen Sie in der Funktion number die Ergebnisse von sign und digit in geeigneter Weise zusammen:

def number():
    return sign()*digit()

 

Modul SimpleNumberCompiler

In einem Modul SimpleNumberCompiler fassen Sie diese Funktionen zusammen. Die Hilfs­funktionen wie etwa comes oder consume befinden sich im Basismodul Parser.

Funktion compile

Die Funktion parse im Basismodul Parser ändern Sie noch in der Weise, dass sie einen Wert zurückgeben, und benennen sie in compile um. Diese Funktion muss sich im Modul Parser befinden, da hier die globalen Variablen inputstring usw. definiert werden.

# Uebernimmt einen Eingabestring x und ruft das Startsymbol
# der Grammatik auf; erzeugt ggf. Fehlermeldung und bestimmt
# die Position des Fehlers im Eingabestring.
def compile(x, startSymbol):
    global inputstring, originalinput, errormessage, errorposition
    inputstring=x
    originalinput=x
    errormessage="ok"
    r=None
    try:
        r=startSymbol()
        if inputstring!="":
            raise Exception("Zu viele Zeichen: "+inputstring)
    except Exception as e:
        errormessage=str(e)
    errorposition=len(x)-len(inputstring)
    return r

 

Im Haupt­programm testen Sie den SimpleNumberCompiler dann folgender­maßen, hierbei rufen Sie die Funktion compile auf und speichern den Rückgabewert. Das Eingabewort und den Namen der Funktion, die dem Startsymbol der Grammatik entspricht, übergeben Sie als Parameter.

v=compile("-5", number)
showErrorPosition()
print("Ergebnis: ", v)

1)  Übersetzer, die Programmcode in anderen Programmcode übersetzen, werden als Compiler bezeichnet

 

[up]

 


H.W. Lang   mail@hwlang.de   Impressum   Datenschutz
Created: 05.12.2025   Updated: 06.12.2025
Diese Webseiten sind größtenteils während meiner Lehrtätigkeit an der Hochschule Flensburg entstanden