aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interpreter.py223
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))