from parser import Expr class Function: def __init__(self, callable_, *arities): self.callable = callable_ if len(arities) == 0: self.arities = None else: self.arities = arities def call(self, expr): if self.arities is not None and len(expr.args) not in self.arities: #if self.arity >= 0 and len(args) != self.arity: fmt = f"[{self.arities[0]}" for arity in self.arities[1:]: fmt += f", {arity}" fmt += "]" raise Exception(f"expected {fmt} arguments, received {len(expr.args)}") return self.callable(expr) class Environment: def __init__(self, parent=None): self.parent = parent self.environment = {} def register(self, key, value): self.environment[key] = value def contains(self, key): if key in self.environment: return True elif self.parent is not None: return self.parent.contains(key) else: return False def get(self, key): if not self.contains(key): raise Exception(f"undefined symbol: '{key}") if key in self.environment: return self.environment[key] else: return self.parent.get(key) def interpret(exprs): ret = None for expr in exprs: ret = evaluate(expr) return ret def evaluate(expr): if isinstance(expr, Expr.Literal): return expr.value elif isinstance(expr, Expr.Symbol): # defined symbols will evaluate to their expression, # undefined will return their own name # TODO this is bad if not GLOBALS.contains(expr.name): raise Exception(f"no such symbol: {expr}") return interpretEnv(expr, GLOBALS.get(expr.name)) name = expr.symbol.name if name == "def": return interpretDef(expr, GLOBALS) elif GLOBALS.contains(name): return GLOBALS.get(name).call(expr) else: raise Exception(f"unable to evaluate: {expr}") GLOBALS = Environment() def interpretOr(expr): # or returns true for the first expression that returns true if len(expr.args) < 2: raise Exception("'or' has at least two operands") for arg in expr.args: ev = evaluate(arg) if ev not in (True, False): raise Exception("'or' needs boolean arguments") if ev == True: return True return False GLOBALS.register("or", Function(interpretOr)) def interpretAnd(expr): # and returns false for the first expression that returns false if len(expr.args) < 2: raise Exception("'and' has at least two operands") for arg in expr.args: ev = evaluate(arg) if ev not in (True, False): raise Exception("'and' needs boolean arguments") if ev == False: return False return True GLOBALS.register("and", Function(interpretAnd)) def interpretEq(expr): # equal first = evaluate(expr.args[0]) second = evaluate(expr.args[1]) return first == second GLOBALS.register("eq?", Function(interpretEq, 2)) def interpretComparison(expr): left = evaluate(expr.args[0]) if type(left) not in (int, float): raise Exception("'left' must be a number") right = evaluate(expr.args[1]) if type(right) not in (int, float): raise Exception("'right' must be a number") if expr.symbol.name == ">": return left > right elif expr.symbol.name == ">=": return left >= right elif expr.symbol.name == "<": return left < right elif expr.symbol.name == "<=": return left <= right GLOBALS.register(">", Function(interpretComparison, 2)) GLOBALS.register(">=", Function(interpretComparison, 2)) GLOBALS.register("<", Function(interpretComparison, 2)) GLOBALS.register("<=", Function(interpretComparison, 2)) def interpretTerm(expr): if len(expr.args) < 1: raise Exception("term has at least one operand") res = None for arg in expr.args: ev = evaluate(arg) if type(ev) not in (int, float): raise Exception("term must be a number") if res is None: res = ev elif expr.symbol.name == "+": res += ev elif expr.symbol.name == "-": res -= ev return res GLOBALS.register("+", Function(interpretTerm)) GLOBALS.register("-", Function(interpretTerm)) def interpretFactor(expr): if expr.symbol.name == "/": num = evaluate(expr.args[0]) if type(num) not in (int, float): raise Exception("numerator must be a number") denom = evaluate(expr.args[1]) if type(denom) not in (int, float): raise Exception("denominator must be a number") return num / denom # TODO floats and ints else: if len(expr.args) < 2: raise Exception("'*' requires at least two operands") first = evaluate(expr.args[0]) if type(first) not in (int, float): raise Exception("'*' operand must be a number") res = first for arg in expr.args[1:]: tmp = evaluate(arg) if type(tmp) not in (int, float): raise Exception("'*' operand must be a number") res = res * tmp return res GLOBALS.register("*", Function(interpretFactor)) GLOBALS.register("/", Function(interpretFactor, 2)) def interpretNot(expr): res = evaluate(expr.args[0]) if res not in (True, False): raise Exception("'not' only works on booleans") return not res GLOBALS.register("not", Function(interpretNot, 1)) def interpretIf(expr): # if cond t-branch [f-branch] cond = evaluate(expr.args[0]) if cond not in (True, False): raise Exception("'if' condition must be boolean") if cond: return evaluate(expr.args[1]) elif len(expr.args) == 3: return evaluate(expr.args[2]) return None # this shouldn't be reached GLOBALS.register("if", Function(interpretIf, 2, 3)) def interpretPrint(expr): ev = evaluate(expr.args[0]) if not isinstance(ev, str): raise Exception("can only 'print' strings") print(ev) return None # print returns nothing GLOBALS.register("print", Function(interpretPrint, 1)) def interpretDef(expr, env): if not isinstance(expr.args[0], Expr.Symbol): raise Exception("'def' requires a string literal as a name") name = expr.args[0].name # NOTE: we are not evaluating the name!! if not isinstance(name, str): raise Exception("'def' requires a string literal as a name") env.register(name, expr.args[1]) return None GLOBALS.register("def", Function(interpretDef, 2)) def interpretEnv(expr, env_expr): return evaluate(env_expr) # TODO more than this