Der Scanner, der den String aufbricht, ist eine einzelne Funktion. Der Parser besteht jetzt ebenfalls aus einfachen Funktionen. Also dachte ich, ich könnte vielleicht komplett ohne Klassen auskommen. Den AST repräsentiere ich in guter Lisp-Tradition als (tief) verschachtelte Tupel:
Code: Alles auswählen
("Suite", [
("Def", "fib", ["n"], ("Suite", [
("If",
("Lt", ("Var", "n"), ("Lit", 2)),
("Return", ("Var", "n")),
("Return",
("Add",
("Call", ("Var", "fib"), [("Sub", ("Var", "n"), ("Lit", 1))]),
("Call", ("Var", "fib"), [("Sub", ("Var", "n"), ("Lit", 2))]))))])),
("Call", ("Var", "fib"), [("Lit", 10)])])
Wären die AST-Knoten Exemplare von Klassen, würde ich entsprechende Methoden definieren:
Code: Alles auswählen
class Var:
def eval(self, f):
return f.get(self.name)
class Add:
def eval(self, f):
return self.left.eval(f) + self.right.eval(f)
Code: Alles auswählen
def _eval(ast, f):
# dies könnte auch ein eigenes dict sein, aber so ist's kürzer:
return globals()[ast[0] + "_eval"](ast, f)
def Var_eval(ast, f):
return f.get(ast[1])
def Add_eval(ast, f):
return _eval(ast[1], f) + _eval(ast[2], f)
Code: Alles auswählen
def _eval(ast, f):
if ast[0] == "Var":
return f.get(ast[1])
if ast[0] == "Add":
return _eval(ast[1]) + _eval(ast[2])
...
Code: Alles auswählen
def _eval(ast, f):
return ast[0](ast, f)
(Add_eval, (Var_eval, "n"), (Lit_eval, 1))
Code: Alles auswählen
def _eval(ast, f):
return ast[0][0](ast, f)
def _set(ast, f, value):
ast[0][1](ast, f, value)
def Var_set(ast, f, value):
f.set(ast[1], value)
def None_set(ast, f, value):
raise SyntaxError
Var = (Var_eval, Var_set)
Add = (Add_eval, None_set)
(Add, (Var, "n"), (Lit, 1))
Mein Interpreter beherrscht leider keine Decorators und keine lokale Funktionen. Bevor ich diese angehe, wäre es einfacher, Klassen zu implementieren
Stefan