Ein Interpreter nimmt das Ergebnis des Parsevorgangs (i.d.R. einen Abstract Syntax Tree) und führt es aus. Für unsere einfache Programmiersprache (mathematische Terme mit +, -, *, /, (, ), Variablen und Zahlen) bedeutet dies, den Wert des gesamten Terms zu berechnen. Dazu muss der Interpreter natürlich die Belegungen der Variablen kennen, die im Term enthalten sind. Gespeichert werden sie in der gleichnamigen HashMap
, die Zuordnungen von String-Werten zu Double-Werten speichern kann (Methode put(String s, Double d)
) und zu einem gegebenen String-Wert den zugehörigen Double-Wert wieder finden kann (Methode get(String)
):
public class Interpreter { /** * Speichert Zuordnungen von Variablenbezeichnern zu Werten: */ private HashMap<String, Double> variablenbelegung = new HashMap<>(); /** * Belegt die Variable mit Bezeichner bezeichner mit dem Wert wert. * * @param bezeichner * Bezeichner der Variablen * @param wert * Wert der Variablen */ public void belegeVariable(String bezeichner, double wert) { variablenbelegung.put(bezeichner, wert); }
Befüllt wird die Variablenliste vor der Termberechnung mit der Methode belegeVariable
. Die eigentliche Funktionalität zur Berechnung des Termwerts steckt in der Methode interpretiere
. Ihr wird ein Knoten des Abstract Syntax Tree übergeben. Sie berechnet den Wert des Teilbaums, dessen Wurzel der übergebene Knoten ist (also der Teilbaum, der durch den Knoten und alles darunter gebildet wird).
/** * Berechnet - ausgehend vom übergebenen Knoten - den Wert des Terms, der * durch den Knoten und den darunterhängenden Teilbaum gegeben ist. * * @param knoten * Wurzel des Teilbaums, dessen Termwert berechnet werden soll * @return Wert des Terms * @throws Exception */ public double interpretiere(Knoten knoten) throws Exception { switch (knoten.getToken().getTokenType()) { case plus: return interpretiere(knoten.getLinks()) + interpretiere(knoten.getRechts()); case minus: return interpretiere(knoten.getLinks()) - interpretiere(knoten.getRechts()); case mal: return interpretiere(knoten.getLinks()) * interpretiere(knoten.getRechts()); case geteilt: return interpretiere(knoten.getLinks()) / interpretiere(knoten.getRechts()); case negation: return -interpretiere(knoten.getLinks()); case text: String variablenbezeichner = knoten.getToken().getText(); Double wert = variablenbelegung.get(variablenbezeichner); if (wert == null) { throw new Exception("Die Belegung der Variable " + variablenbezeichner + " ist nicht bekannt."); } return wert; case zahl: return knoten.getToken().getZahl(); default: return 0; // sollte nie vorkommen } }
Die Methode sieht sich den TokenType
des Knotens an. Handelt es sich beispielsweise um plus
, so ruft sich die Methode selbst auf, um den Wert des linken Teilbaums zu berechnen, der unter dem Knoten hängt. Dann ruft sie sich selbst auf, um den Wert des rechten Teilbaums zu berechnen. Anschließend addiert sie beide Werte. Im Programm sieht das so aus:
switch (knoten.getToken().getTokenType()) { case plus: return interpretiere(knoten.getLinks()) + interpretiere(knoten.getRechts());
Ist der TokentType
des Knotens „text“, so handelt es sich um eine Variable. Das Programm holt in diesem Fall die Belegung der Variable aus der HashMap
variablenbelegung
und gibt sie als Wert des Teilbaums zurück:
case text: String variablenbezeichner = knoten.getToken().getText(); Double wert = variablenbelegung.get(variablenbezeichner); if (wert == null) { throw new Exception("Die Belegung der Variable " + variablenbezeichner + " ist nicht bekannt."); } return wert;
Ist der TokentType
des Knotens „zahl“, so ist der Wert dieser Zahl im Knoten selbst hinterlegt und kann direkt entnommen und zurückgegeben werden:
case zahl: return knoten.getToken().getZahl();
Die Methode getBelegungAlsString()
dient nur zu Debuggingzwecken, damit aktuell gespeicherte Variablenbelegung am Bildschirm ausgegeben werden kann:
/** * Nur zu Debuggingzwecken * * @return * String, der alle Variablen zusammen mit ihrer Belegung in der Form * variablenbezeichner = wert * enthält. */ public String getBelegungAlsString() { String s = ""; for (String bezeichner : variablenbelegung.keySet().toArray( new String[0])) { s += bezeichner + " = " + variablenbelegung.get(bezeichner) + "\n"; } return s; }
Hier geht's weiter mit einer Beschreibung des Aufrufs/der Verwendung von Lexer, Parser und Interpreter.