aboutsummaryrefslogtreecommitdiff
path: root/interpreter.py
diff options
context:
space:
mode:
authormryouse2022-06-04 03:13:30 +0000
committermryouse2022-06-04 03:13:30 +0000
commited78cd23f92b8b96dd7ffa3313be2262b0b29ff0 (patch)
tree34fd6ef2feeb82223e7e3ec7c5a79b5091d6ca08 /interpreter.py
parentb395d5d1cd122bb07017f23110f76249398a61bb (diff)
refactor: more literal types
Diffstat (limited to 'interpreter.py')
-rw-r--r--interpreter.py190
1 files changed, 94 insertions, 96 deletions
diff --git a/interpreter.py b/interpreter.py
index 04160cd..ef0bc4a 100644
--- a/interpreter.py
+++ b/interpreter.py
@@ -1,4 +1,4 @@
-from structs import Literal, Symbol, List
+from structs import *
from pathlib import Path
from glob import glob
import subprocess
@@ -126,12 +126,11 @@ 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 not isinstance(ev, Literal) and ev.value not in (True, False):
+ if not isinstance(ev, Bool):
raise Exception("'or' needs boolean arguments")
if ev.value == True:
return ev
- return Literal(False)
+ return Bool(False)
GLOBALS.register("or", Builtin(interpretOr))
@@ -141,12 +140,11 @@ 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 not isinstance(ev, Literal) and ev.value not in (True, False):
+ if not isinstance(ev, Bool):
raise Exception("'and' needs boolean arguments")
if ev.value == False:
return ev
- return Literal(True)
+ return Bool(True)
GLOBALS.register("and", Builtin(interpretAnd))
@@ -157,29 +155,31 @@ def interpretEq(symbol, args, env):
second = evaluate(args[1], env)
if not (isinstance(first, Literal) and isinstance(second, Literal)):
raise Exception("'eq?' can only compare literals")
- if first.value == second.value:
- return Literal(True)
+ # compare types because 0 != #false in neb
+ # TODO number equality?
+ if type(first) == type(second) and first.value == second.value:
+ return Bool(True)
else:
- return Literal(False)
+ return Bool(False)
GLOBALS.register("eq?", Builtin(interpretEq, 2))
def interpretComparison(symbol, args, env):
left = evaluate(args[0], env)
- if not isinstance(left, Literal) or type(left.value) not in (int, float):
+ if not (isinstance(left, Int) or isinstance(left, Float)):
raise Exception("'left' must be a number")
right = evaluate(args[1], env)
- if not isinstance(right, Literal) or type(right.value) not in (int, float):
+ if not (isinstance(right, Int) or isinstance(right, Float)):
raise Exception("'right' must be a number")
if symbol.name == ">":
- return Literal(left.value > right.value)
+ return Bool(left.value > right.value)
elif symbol.name == ">=":
- return Literal(left.value >= right.value)
+ return Bool(left.value >= right.value)
elif symbol.name == "<":
- return Literal(left.value < right.value)
+ return Bool(left.value < right.value)
elif symbol.name == "<=":
- return Literal(left.value <= right.value)
+ return Bool(left.value <= right.value)
GLOBALS.register(">", Builtin(interpretComparison, 2))
GLOBALS.register(">=", Builtin(interpretComparison, 2))
@@ -192,7 +192,7 @@ def interpretTerm(symbol, args, env):
res = None
for arg in args:
ev = evaluate(arg, env)
- if not isinstance(ev, Literal) or type(ev.value) not in (int, float):
+ if not (isinstance(ev, Int) or isinstance(ev, Float)):
raise Exception("term must be a number")
if res is None:
res = ev.value
@@ -200,7 +200,10 @@ def interpretTerm(symbol, args, env):
res += ev.value
elif symbol.name == "-":
res -= ev.value
- return Literal(res)
+ if isinstance(res, float):
+ return Float(res)
+ else:
+ return Int(res)
GLOBALS.register("+", Builtin(interpretTerm))
GLOBALS.register("-", Builtin(interpretTerm))
@@ -208,61 +211,64 @@ GLOBALS.register("-", Builtin(interpretTerm))
def interpretFactor(symbol, args, env):
if symbol.name == "/":
num = evaluate(args[0], env)
- if not isinstance(num, Literal) or type(num.value) not in (int, float):
+ if not (isinstance(num, Int) or isinstance(num, Float)):
raise Exception("numerator must be a number")
denom = evaluate(args[1], env)
- if not isinstance(denom, Literal) or type(denom.value) not in (int, float):
+ if not (isinstance(denom, Int) or isinstance(denom, Float)):
raise Exception("denominator must be a number")
ret = num.value / denom.value
if int(ret) == ret:
- return Literal(int(ret))
+ return Int(int(ret))
else:
- return Literal(ret)
+ return Float(ret)
else:
if len(args) < 2:
raise Exception("'*' requires at least two operands")
first = evaluate(args[0], env)
- if not isinstance(first, Literal) or type(first.value) not in (int, float):
+ if not (isinstance(first, Int) or isinstance(first, Float)):
raise Exception("'*' operand must be a number")
res = first.value
for arg in args[1:]:
tmp = evaluate(arg, env)
- if not isinstance(tmp, Literal) or type(tmp.value) not in (int, float):
+ if not (isinstance(tmp, Int) or isinstance(tmp, Float)):
raise Exception("'*' operand must be a number")
res = res * tmp.value
- return Literal(res)
+ if isinstance(res, float):
+ return Float(res)
+ else:
+ return Int(res)
GLOBALS.register("*", Builtin(interpretFactor))
GLOBALS.register("/", Builtin(interpretFactor, 2))
def interpretNot(symbol, args, env):
res = evaluate(args[0], env)
- if not isinstance(res, Literal) or res.value not in (True, False):
+ if not isinstance(res, Bool):
raise Exception("'not' only works on booleans")
- return Literal(not res.value)
+ return Bool(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 not isinstance(cond, Literal) or cond.value not in (True, False):
+ if not isinstance(cond, Bool):
raise Exception("'if' condition must be boolean")
if cond.value:
return evaluate(args[1], env)
elif len(args) == 3:
return evaluate(args[2], env)
- return None # this shouldn't be reached
+ return List([]) # this shouldn't be reached
GLOBALS.register("if", Builtin(interpretIf, 2, 3))
def interpretPrint(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal) or not isinstance(ev.value, str):
+ if not isinstance(ev, String):
raise Exception("can only 'print' strings")
print(ev.value)
- return None # print returns nothing
+ return List([]) # print returns nothing
GLOBALS.register("print", Builtin(interpretPrint, 1))
@@ -275,13 +281,7 @@ def interpretDef(symbol, args, env):
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
+ return List([])
GLOBALS.register("def", Builtin(interpretDef, 2))
@@ -294,7 +294,7 @@ def interpretRedef(symbol, args, env):
ev = evaluate(args[1], env)
env.reregister(name, ev)
- return None
+ return List([])
GLOBALS.register("redef", Builtin(interpretRedef, 2))
@@ -309,10 +309,12 @@ GLOBALS.register("lambda", Builtin(interpretLambda))
def interpretToString(symbol, args, env):
ev = evaluate(args[0], env)
- if isinstance(ev, Literal):
- return Literal(str(ev.value))
+ if isinstance(ev, String):
+ return ev
+ elif isinstance(ev, Literal):
+ return String(str(ev))
else:
- return Literal(f"{ev}")
+ return String(f"{ev}")
GLOBALS.register("->string", Builtin(interpretToString, 1))
@@ -323,24 +325,26 @@ def interpretConcat(symbol, args, env):
out = ""
for arg in args:
tmp = evaluate(arg, env)
- if not isinstance(tmp, Literal) and not isinstance(tmp.value, str):
+ if not isinstance(tmp, String):
raise Exception("'concat' arguments must be strings")
out += tmp.value
- return Literal(out)
+ return String(out)
GLOBALS.register("concat", Builtin(interpretConcat))
def interpretForCount(symbol, args, env):
# for-count int exprs
num = evaluate(args[0], env)
- if not isinstance(num, Literal) or type(num.value) is not int:
+ if not isinstance(num, Int):
raise Exception("'for-count' count must be an integer")
new_env = Environment(env)
ret = None
for idx in range(0, num.value):
- new_env.register("idx", Literal(idx + 1))
+ new_env.register("idx", Int(idx + 1))
for arg in args[1:]:
ret = evaluate(arg, new_env)
+ if ret is None:
+ return List([])
return ret
GLOBALS.register("for-count", Builtin(interpretForCount))
@@ -356,6 +360,8 @@ def interpretForEach(symbol, args, env):
new_env.register("_item_", item)
for arg in args[1:]:
ret = evaluate(arg, new_env)
+ if ret is None:
+ return List([])
return ret
GLOBALS.register("for-each", Builtin(interpretForEach))
@@ -369,6 +375,8 @@ def interpretPipe(symbol, args, env):
if pipe is not None:
new_env.register("items", pipe)
pipe = evaluate(arg, new_env)
+ if pipe is None:
+ return List([])
return pipe
GLOBALS.register("|", Builtin(interpretPipe))
@@ -382,7 +390,7 @@ def interpretBranch(symbol, args, env):
cond = evaluate(arg.args[0], env) # this is the condition
if cond.value:
return evaluate(arg.args[1], env)
- return None
+ return List([])
GLOBALS.register("branch", Builtin(interpretBranch))
@@ -398,7 +406,7 @@ def interpretFunc(symbol, args, env):
func = interpretLambda(None, args[1:], env)
env.register(name, func)
- return None
+ return List([])
GLOBALS.register("func", Builtin(interpretFunc))
@@ -411,7 +419,7 @@ def interpretReadLines(symbol, args, env):
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], True) # all lines are strings
+ out = List([String(d) for d in data], True) # all lines are strings
return out
GLOBALS.register("read-lines", Builtin(interpretReadLines, 1))
@@ -419,30 +427,33 @@ GLOBALS.register("read-lines", Builtin(interpretReadLines, 1))
# - strip whitespace from string
def interpretStrip(symbol, args, env):
out = evaluate(args[0], env)
- return Literal(out.value.strip())
+ return String(out.value.strip())
GLOBALS.register("strip", Builtin(interpretStrip, 1))
# - string->int and string->float
def interpretStringToInt(symbol, args, env):
+ ev = evaluate(args[0], env)
+ if not isinstance(ev, String):
+ raise Exception("'string->int' expects a string")
try:
- val = int(args[0].value)
- return Literal(val)
+ val = int(ev.value)
+ return Int(val)
except:
- raise Exception(f"can't convert {args[0].value} to an int")
+ raise Exception(f"can't convert {ev} to an int")
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, Literal) or not isinstance(target.value, str):
+ if not isinstance(target, String):
raise Exception("'split' expects a string")
splitter = evaluate(args[1], env)
- if not isinstance(splitter, Literal) or not isinstance(splitter.value, str):
+ if not isinstance(splitter, String):
raise Exception("'split' expects a string as it's splitter")
ret = target.value.split(splitter.value)
- return List([Literal(r) for r in ret], True)
+ return List([String(r) for r in ret], True)
GLOBALS.register("split", Builtin(interpretSplit, 2))
@@ -450,7 +461,7 @@ GLOBALS.register("split", Builtin(interpretSplit, 2))
def interpretListLength(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'first' expects a List")
+ raise Exception("'list-length' expects a List")
return Literal(len(ev.args))
GLOBALS.register("list-length", Builtin(interpretListLength, 1))
@@ -487,10 +498,7 @@ 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)
- #ev = evaluate(List([func, arg_ev]), env)
- #out.append(Literal(ev)) # TODO this is probably wrong
out.append(ev)
return List(out, True)
@@ -507,12 +515,6 @@ def interpretZip(symbol, args, env):
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))
@@ -543,7 +545,6 @@ def interpretApply(symbol, args, env):
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)
@@ -553,23 +554,20 @@ GLOBALS.register("apply", Builtin(interpretApply, 2))
def interpretGlob(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal):
+ if not isinstance(ev, String):
raise Exception("'glob' expects a string")
items = glob(ev.value)
- out = []
- for item in items:
- out.append(Literal(item))
- return List(out, True)
+ return List([String(item) for item in items], True)
GLOBALS.register("glob", Builtin(interpretGlob, 1))
def interpretShell(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal):
+ if not isinstance(ev, String):
raise Exception("'$' expects a string")
# TODO either fail or throw exception (?) on error
ret = subprocess.run(shlex.split(ev.value), capture_output=True)
- return List([Literal(r) for r in ret.stdout.decode("utf-8").split("\n")], True)
+ return List([String(r) for r in ret.stdout.decode("utf-8").split("\n")], True)
GLOBALS.register("$", Builtin(interpretShell, 1))
@@ -577,7 +575,7 @@ def interpretEmpty(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
raise Exception("'empty?' expects a List")
- return Literal(len(ev.args) == 0)
+ return Bool(len(ev.args) == 0)
GLOBALS.register("empty?", Builtin(interpretEmpty, 1))
@@ -593,7 +591,7 @@ GLOBALS.register("shuf", Builtin(interpretShuf, 1))
def interpretIsList(symbol, args, env):
ev = evaluate(args[0], env)
- return Literal(isinstance(ev, List))
+ return Bool(isinstance(ev, List))
GLOBALS.register("list?", Builtin(interpretIsList, 1))
@@ -608,8 +606,8 @@ GLOBALS.register("block", Builtin(interpretBlock))
def interpretExit(symbol, args, env):
if len(args) > 1:
raise Exception("'exit' only takes one (optional) argument")
- status = 0 if len(args) == 0 else args[0].value
- if type(status) is not int:
+ status = 0 if len(args) == 0 else evaluate(args[0], env).value
+ if not isinstance(status, int):
raise Exception("'exit' requires an :int")
sys.exit(status)
return List([])
@@ -618,7 +616,7 @@ GLOBALS.register("exit", Builtin(interpretExit, 0, 1))
def interpretUnlink(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal):
+ if not isinstance(ev, String):
raise Exception("'unlink' expects a string")
target_path = Path(ev.value).resolve()
if not target_path.exists():
@@ -631,7 +629,7 @@ GLOBALS.register("unlink", Builtin(interpretUnlink, 1))
def interpretArgv(symbol, args, env):
out = []
for arg in sys.argv[1:]:
- out.append(Literal(arg))
+ out.append(String(arg))
return List(out, True)
GLOBALS.register("argv", Builtin(interpretArgv, 0))
@@ -645,9 +643,9 @@ def interpretIn(symbol, args, env):
raise Exception("'in?' expects a list as its second argument")
for arg in lst.args:
ev = evaluate(arg, env)
- if isinstance(ev, Literal) and ev.value == target.value:
- return Literal(True)
- return Literal(False)
+ if type(ev) == type(target) and ev.value == target.value:
+ return Bool(True)
+ return Bool(False)
GLOBALS.register("in?", Builtin(interpretIn, 2))
@@ -666,9 +664,9 @@ def interpretJoin(symbol, args, env):
if not isinstance(lst, List):
raise Exception("'join' expects a List as its first argument")
target = evaluate(args[1], env)
- if not isinstance(target, Literal):
+ if not isinstance(target, String):
raise Exception("'join' expects a :string as its second argument")
- return Literal(target.value.join(lst.args))
+ return String(target.value.join(lst.args))
GLOBALS.register("join", Builtin(interpretJoin, 2))
@@ -676,7 +674,7 @@ def interpretWithWrite(symbol, args, env):
if len(args) == 0:
raise Exception("'with-write' expects at least one argument")
target_file = evaluate(args[0], env)
- if not isinstance(target_file, Literal):
+ if not isinstance(target_file, String):
raise Exception("'with-write' expects a filename as its first argument")
new_env = Environment(env)
target_path = Path(target_file.value).resolve()
@@ -692,7 +690,7 @@ GLOBALS.register("with-write", Builtin(interpretWithWrite))
def interpretWrite(symbol, args, env):
# write :string :filehandle
line = evaluate(args[0], env)
- if not isinstance(line, Literal):
+ if not isinstance(line, String):
raise Exception("'write' expects a string as its first argument")
handle = evaluate(args[1], env)
handle.args[0].write(line.value) # TODO wrong! how do we evaluate a handle?
@@ -701,33 +699,33 @@ def interpretWrite(symbol, args, env):
GLOBALS.register("write", Builtin(interpretWrite, 2))
def interpretNewline(symbol, args, env):
- return Literal("\n")
+ return String("\n")
GLOBALS.register("newline", Builtin(interpretNewline, 0))
def interpretExists(symbol, args, env):
file_or_dir = evaluate(args[0], env)
- if not isinstance(file_or_dir, Literal):
+ if not isinstance(file_or_dir, String):
raise Exception("'exists?' expects a string as its first argument")
- return Literal(Path(file_or_dir.value).resolve().exists())
+ return Bool(Path(file_or_dir.value).resolve().exists())
GLOBALS.register("exists?", Builtin(interpretExists, 1))
def interpretFirstChar(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal):
+ if not isinstance(ev, String):
raise Exception("'first-char' expects a string")
if len(ev.value) == 0:
raise Exception("string is empty")
- return Literal(ev.value[0])
+ return String(ev.value[0])
GLOBALS.register("first-char", Builtin(interpretFirstChar, 1))
def interpretRestChar(symbol, args, env):
ev = evaluate(args[0], env)
- if not isinstance(ev, Literal):
+ if not isinstance(ev, String):
raise Exception("'rest-char' expects a string")
- return Literal(ev.value[1:])
+ return String(ev.value[1:])
GLOBALS.register("rest-char", Builtin(interpretRestChar, 1))
@@ -736,12 +734,12 @@ def interpretSlice(symbol, args, env):
if not isinstance(lst, List):
raise Exception("'slice' expects a list as its first argument")
idx = evaluate(args[1], env)
- if not isinstance(idx, Literal):
+ if not isinstance(idx, Int):
raise Exception("'slice' expects an integer as its second argument")
if len(args) == 2:
return List(lst.args[idx.value - 1:])
length = evaluate(args[2], env)
- if not isinstance(length, Literal):
+ if not isinstance(length, Int):
raise Exception("'slice' expects an integer as its third argument")
diff = idx.value - 1 + length.value
return List(lst.args[idx.value - 1:diff])