diff options
Diffstat (limited to 'interpreter.py')
| -rw-r--r-- | interpreter.py | 223 |
1 files changed, 168 insertions, 55 deletions
diff --git a/interpreter.py b/interpreter.py index 26ac8a9..98eaa1c 100644 --- a/interpreter.py +++ b/interpreter.py @@ -38,7 +38,8 @@ class UserFunction(Function): this_env = Environment(env) for idx, param in enumerate(self.params): # TODO this is wrong!!! this won't always be a literal - this_env.register(param.name, Literal(evaluate(expr.args[idx+1],env))) + #this_env.register(param.name, Literal(evaluate(expr.args[idx+1],env))) + this_env.register(param.name, evaluate(expr.args[idx+1],env)) return interpret(self.body, this_env) class Environment: @@ -50,6 +51,14 @@ class Environment: def register(self, key, value): self.environment[key] = value + def reregister(self, key, value): + if not self.contains(key): + raise Exception(f"undefined symbol: '{key}") + if key in self.environment: + self.register(key, value) + else: + self.parent.reregister(key, value) + def contains(self, key): if key in self.environment: return True @@ -82,12 +91,17 @@ def interpret(exprs, env=GLOBALS): def evaluate(expr, env): if isinstance(expr, Literal): - return expr.value + #return expr.value + return expr elif isinstance(expr, Symbol): if not env.contains(expr.name): raise Exception(f"no such symbol: {expr}") return evaluate(env.get(expr.name), env) + # if it's a literal list, return it + if expr.data: + return expr + if not isinstance(expr.args[0], Symbol): raise Exception("can't evaluate without a symbol") name = expr.args[0].name @@ -104,11 +118,12 @@ def interpretOr(symbol, args, env): raise Exception("'or' has at least two operands") for arg in args: ev = evaluate(arg, env) - if ev not in (True, False): + #if ev not in (True, False): + if not isinstance(ev, Literal) and ev.value not in (True, False): raise Exception("'or' needs boolean arguments") - if ev == True: - return True - return False + if ev.value == True: + return ev + return Literal(False) GLOBALS.register("or", Builtin(interpretOr)) @@ -118,38 +133,45 @@ def interpretAnd(symbol, args, env): raise Exception("'and' has at least two operands") for arg in args: ev = evaluate(arg, env) - if ev not in (True, False): + #if ev not in (True, False): + if not isinstance(ev, Literal) and ev.value not in (True, False): raise Exception("'and' needs boolean arguments") - if ev == False: - return False - return True + if ev.value == False: + return ev + return Literal(True) GLOBALS.register("and", Builtin(interpretAnd)) def interpretEq(symbol, args, env): # equal + # NOTE this currently only works for literals first = evaluate(args[0], env) second = evaluate(args[1], env) - return first == second + if not (isinstance(first, Literal) and isinstance(second, Literal)): + raise Exception("'eq?' can only compare literals") + if first.value == second.value: + return Literal(True) + else: + return Literal(False) GLOBALS.register("eq?", Builtin(interpretEq, 2)) def interpretComparison(symbol, args, env): left = evaluate(args[0], env) - if type(left) not in (int, float): + if not isinstance(left, Literal) or type(left.value) not in (int, float): raise Exception("'left' must be a number") right = evaluate(args[1], env) - if type(right) not in (int, float): + if not isinstance(right, Literal) or type(right.value) not in (int, float): raise Exception("'right' must be a number") if symbol.name == ">": - return left > right + return Literal(left.value > right.value) elif symbol.name == ">=": - return left >= right + return Literal(left.value >= right.value) elif symbol.name == "<": - return left < right + return Literal(left.value < right.value) elif symbol.name == "<=": - return left <= right + return Literal(left.value <= right.value) GLOBALS.register(">", Builtin(interpretComparison, 2)) GLOBALS.register(">=", Builtin(interpretComparison, 2)) @@ -162,15 +184,15 @@ def interpretTerm(symbol, args, env): res = None for arg in args: ev = evaluate(arg, env) - if type(ev) not in (int, float): + if not isinstance(ev, Literal) or type(ev.value) not in (int, float): raise Exception("term must be a number") if res is None: - res = ev + res = ev.value elif symbol.name == "+": - res += ev + res += ev.value elif symbol.name == "-": - res -= ev - return res + res -= ev.value + return Literal(res) GLOBALS.register("+", Builtin(interpretTerm)) GLOBALS.register("-", Builtin(interpretTerm)) @@ -178,43 +200,43 @@ GLOBALS.register("-", Builtin(interpretTerm)) def interpretFactor(symbol, args, env): if symbol.name == "/": num = evaluate(args[0], env) - if type(num) not in (int, float): + if not isinstance(num, Literal) or type(num.value) not in (int, float): raise Exception("numerator must be a number") denom = evaluate(args[1], env) - if type(denom) not in (int, float): + if not isinstance(denom, Literal) or type(denom.value) not in (int, float): raise Exception("denominator must be a number") - return num / denom # TODO floats and ints + return Literal(num.value / denom.value) # TODO floats and ints else: if len(args) < 2: raise Exception("'*' requires at least two operands") first = evaluate(args[0], env) - if type(first) not in (int, float): + if not isinstance(first, Literal) or type(first.value) not in (int, float): raise Exception("'*' operand must be a number") - res = first + res = first.value for arg in args[1:]: tmp = evaluate(arg, env) - if type(tmp) not in (int, float): + if not isinstance(tmp, Literal) or type(tmp.value) not in (int, float): raise Exception("'*' operand must be a number") - res = res * tmp - return res + res = res * tmp.value + return Literal(res) GLOBALS.register("*", Builtin(interpretFactor)) GLOBALS.register("/", Builtin(interpretFactor, 2)) def interpretNot(symbol, args, env): res = evaluate(args[0], env) - if res not in (True, False): + if not isinstance(res, Literal) or res.value not in (True, False): raise Exception("'not' only works on booleans") - return not res + return Literal(not res.value) GLOBALS.register("not", Builtin(interpretNot, 1)) def interpretIf(symbol, args, env): # if cond t-branch [f-branch] cond = evaluate(args[0], env) - if cond not in (True, False): + if not isinstance(cond, Literal) or cond.value not in (True, False): raise Exception("'if' condition must be boolean") - if cond: + if cond.value: return evaluate(args[1], env) elif len(args) == 3: return evaluate(args[2], env) @@ -224,9 +246,9 @@ GLOBALS.register("if", Builtin(interpretIf, 2, 3)) def interpretPrint(symbol, args, env): ev = evaluate(args[0], env) - if not isinstance(ev, str): + if not isinstance(ev, Literal) or not isinstance(ev.value, str): raise Exception("can only 'print' strings") - print(ev) + print(ev.value) return None # print returns nothing @@ -240,14 +262,30 @@ def interpretDef(symbol, args, env): raise Exception("'def' requires a string literal as a name") ev = evaluate(args[1], env) + env.register(name, ev) + ''' if isinstance(ev, UserFunction): env.register(name, ev) else: env.register(name, args[1]) + ''' return None GLOBALS.register("def", Builtin(interpretDef, 2)) +def interpretRedef(symbol, args, env): + if not isinstance(args[0], Symbol): + raise Exception("'redef' requires a string literal as a name") + name = args[0].name # NOTE: we are not evaluating the name!! + if not env.contains(name): + raise Exception("'redef' only works on previously defined variables") + + ev = evaluate(args[1], env) + env.reregister(name, ev) + return None + +GLOBALS.register("redef", Builtin(interpretRedef, 2)) + def interpretLambda(symbol, args, env): if args[0].args[0] != None: func = UserFunction("<lambda>", args[0].args, args[1:]) @@ -258,7 +296,7 @@ def interpretLambda(symbol, args, env): GLOBALS.register("lambda", Builtin(interpretLambda)) def interpretToString(symbol, args, env): - return str(evaluate(args[0], env)) + return Literal(str(evaluate(args[0], env).value)) GLOBALS.register("->string", Builtin(interpretToString, 1)) @@ -269,21 +307,21 @@ def interpretConcat(symbol, args, env): out = "" for arg in args: tmp = evaluate(arg, env) - if not isinstance(tmp, str): + if not isinstance(tmp, Literal) and not isinstance(tmp.value, str): raise Exception("'concat' arguments must be strings") - out += tmp - return out + out += tmp.value + return Literal(out) GLOBALS.register("concat", Builtin(interpretConcat)) def interpretForCount(symbol, args, env): # for-count int exprs num = evaluate(args[0], env) - if type(num) is not int: + if not isinstance(num, Literal) or type(num.value) is not int: raise Exception("'for-count' count must be an integer") new_env = Environment(env) ret = None - for idx in range(0, num): + for idx in range(0, num.value): new_env.register("idx", Literal(idx + 1)) for arg in args[1:]: ret = evaluate(arg, new_env) @@ -291,6 +329,21 @@ def interpretForCount(symbol, args, env): GLOBALS.register("for-count", Builtin(interpretForCount)) +def interpretForEach(symbol, args, env): + # for-each list exprs + lst = evaluate(args[0], env) + if not isinstance(lst, List): + raise Exception("'for-each' expects a list") + new_env = Environment(env) + ret = None + for item in lst.args: + new_env.register("_item_", item) + for arg in args[1:]: + ret = evaluate(arg, new_env) + return ret + +GLOBALS.register("for-each", Builtin(interpretForEach)) + def interpretPipe(symbol, args, env): if len(args) < 2: raise Exception("'|' takes at least two expressions") @@ -299,7 +352,7 @@ def interpretPipe(symbol, args, env): for arg in args: if pipe is not None: new_env.register("items", pipe) - pipe = Literal(evaluate(arg, new_env)) + pipe = evaluate(arg, new_env) return pipe GLOBALS.register("|", Builtin(interpretPipe)) @@ -311,7 +364,7 @@ def interpretBranch(symbol, args, env): if len(arg.args) != 2: raise Exception("'branch' branches have two expressions") cond = evaluate(arg.args[0], env) # this is the condition - if cond: + if cond.value: return evaluate(arg.args[1], env) return None @@ -336,13 +389,13 @@ GLOBALS.register("func", Builtin(interpretFunc)) # THINGS NEEDED FOR AOC # - read the contents of a file def interpretReadLines(symbol, args, env): - target_file_name = evaluate(args[0], env) + target_file_name = evaluate(args[0], env).value target_file = Path(target_file_name).resolve() if not target_file.exists(): raise Exception(f"no such file: {target_file}") with open(target_file, "r") as fil: data = fil.readlines() - out = List([Literal(d) for d in data]) # all lines are strings + out = List([Literal(d) for d in data], True) # all lines are strings return out GLOBALS.register("read-lines", Builtin(interpretReadLines, 1)) @@ -350,14 +403,15 @@ GLOBALS.register("read-lines", Builtin(interpretReadLines, 1)) # - strip whitespace from string def interpretStrip(symbol, args, env): out = evaluate(args[0], env) - return out.strip() + return Literal(out.value.strip()) GLOBALS.register("strip", Builtin(interpretStrip, 1)) # - string->int and string->float def interpretStringToInt(symbol, args, env): try: - return int(args[0].value) + val = int(args[0].value) + return Literal(val) except: raise Exception(f"can't convert {args[0].value} to an int") @@ -366,12 +420,12 @@ GLOBALS.register("string->int", Builtin(interpretStringToInt, 1)) # - split a string by a given field def interpretSplit(symbol, args, env): target = evaluate(args[0], env) - if not isinstance(target, str): + if not isinstance(target, Literal) or not isinstance(target.value, str): raise Exception("'split' expects a string") splitter = evaluate(args[1], env) - if not isinstance(splitter, str): + if not isinstance(splitter, Literal) or not isinstance(splitter.value, str): raise Exception("'split' expects a string as it's splitter") - return List(target.split(splitter)) + return List(target.value.split(splitter.value), True) GLOBALS.register("split", Builtin(interpretSplit, 2)) @@ -380,7 +434,7 @@ def interpretListLength(symbol, args, env): ev = evaluate(args[0], env) if not isinstance(ev, List): raise Exception("'first' expects a List") - return len(ev.args) + return Literal(len(ev.args)) GLOBALS.register("list-length", Builtin(interpretListLength, 1)) @@ -399,7 +453,8 @@ def interpretRest(symbol, args, env): ev = evaluate(args[0], env) if not isinstance(ev, List): raise Exception("'rest' expects a List") - return List(ev.args[1:]) # we don't evaluate the remainder of the list + # TODO do we know it's not evaluated? + return List(ev.args[1:], True) # we don't evaluate the remainder of the list GLOBALS.register("rest", Builtin(interpretRest, 1)) @@ -415,8 +470,66 @@ def interpretMap(symbol, args, env): raise Exception("'map' takes a List as its second argument") out = [] for arg in lst.args: + #arg_ev = evaluate(arg, env) ev = evaluate(List([func, arg]), env) - out.append(Literal(ev)) # TODO this is probably wrong - return List(out) + #ev = evaluate(List([func, arg_ev]), env) + #out.append(Literal(ev)) # TODO this is probably wrong + out.append(ev) + return List(out, True) GLOBALS.register("map", Builtin(interpretMap, 2)) + +def interpretZip(symbol, args, env): + z1 = evaluate(args[0], env) + if not isinstance(z1, List): + raise Exception("'zip' only works on lists") + z2 = evaluate(args[1], env) + if not isinstance(z2, List): + raise Exception("'zip' only works on lists") + if len(z1.args) != len(z2.args): + raise Exception("'zip' expects two lists of the same size") + out = [] + for idx in range(len(z1.args)): + #f = z1.args[idx] + #if not isinstance(f, Literal): + # f = evaluate(f, env) + #s = z2.args[idx] + #if not isinstance(s, Literal): + # s = evaluate(s, env) + f = evaluate(z1.args[idx], env) + s = evaluate(z2.args[idx], env) + out.append(List([f, s], True)) + return List(out, True) + +GLOBALS.register("zip", Builtin(interpretZip, 2)) + +def interpretList(symbol, args, env): + out = [] + for arg in args: + out.append(evaluate(arg, env)) + return List(out, True) + +GLOBALS.register("list", Builtin(interpretList)) + +def interpretListReverse(symbol, args, env): + lst = evaluate(args[0], env) + if not isinstance(lst, List): + raise Exception("'list-reverse' expects a list") + new_args = lst.args[:] # make a copy of the args + new_args.reverse() + return List(new_args, True) + +GLOBALS.register("list-reverse", Builtin(interpretListReverse, 1)) + +def interpretApply(symbol, args, env): + func = args[0] + if not isinstance(func, Symbol): + raise Exception("'apply' takes a function as its first argument") + lst = evaluate(args[1], env) + #lst = args[1] + if not isinstance(lst, List): + raise Exception("'apply' takes a List as its second argument") + new_lst = List([func] + lst.args) + return evaluate(new_lst, env) + +GLOBALS.register("apply", Builtin(interpretApply, 2)) |
