from parser import Expr ENVIRONMENT = {} 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 expr.name in ENVIRONMENT: raise Exception(f"no such symbol: {expr}") return interpretEnv(expr, ENVIRONMENT[expr.name]) elif expr.symbol.name == "not": return interpretNot(expr) elif expr.symbol.name in ("*", "/"): return interpretFactor(expr) elif expr.symbol.name in ("-", "+"): return interpretTerm(expr) elif expr.symbol.name in (">", ">=", "<", "<="): return interpretComparison(expr) elif expr.symbol.name == "eq?": return interpretEq(expr) elif expr.symbol.name == "and": return interpretAnd(expr) elif expr.symbol.name == "or": return interpretOr(expr) # flow control elif expr.symbol.name == "if": return interpretIf(expr) # io elif expr.symbol.name == "print": return interpretPrint(expr) # global variables elif expr.symbol.name == "def": return interpretDef(expr) else: raise Exception(f"unable to evaluate: {expr}") 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 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 def interpretEq(expr): # equal if len(expr.args) != 2: raise Exception("'eq?' needs two arguments") first = evaluate(expr.args[0]) second = evaluate(expr.args[1]) return first == second def interpretComparison(expr): if len(expr.args) != 2: raise Exception("comparisons have two operands") 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 def interpretTerm(expr): if len(expr.args) < 1: raise Exception("term has at least one operand") res = 0 for arg in expr.args: ev = evaluate(arg) if type(ev) not in (int, float): raise Exception("term must be a number") if expr.symbol.name == "+": res += ev elif expr.symbol.name == "-": res -= ev return res def interpretFactor(expr): if expr.symbol.name == "/": if len(expr.args) != 2: raise Exception("'/' requires two operands") 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 def interpretNot(expr): if len(expr.args) != 1: raise Exception("'not' takes one operand") res = evaluate(expr.args[0]) if res not in (True, False): raise Exception("'not' only works on booleans") return not res def interpretIf(expr): # if cond t-branch [f-branch] if len(expr.args) not in (2, 3): raise Exception("too many or too few operands") 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 def interpretPrint(expr): if len(expr.args) == 0: print() elif len(expr.args) == 1: ev = evaluate(expr.args[0]) if not isinstance(ev, str): raise Exception("can only 'print' strings") print(ev) else: raise Exception("'print' takes zero or one argument") return None # print returns nothing def interpretDef(expr): if len(expr.args) != 2: raise Exception("'def' requires a name and an expression") 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") ENVIRONMENT[name] = expr.args[1] return None def interpretEnv(expr, env_expr): return evaluate(env_expr) # TODO more than this