diff options
| author | mryouse | 2022-06-04 03:13:30 +0000 |
|---|---|---|
| committer | mryouse | 2022-06-04 03:13:30 +0000 |
| commit | ed78cd23f92b8b96dd7ffa3313be2262b0b29ff0 (patch) | |
| tree | 34fd6ef2feeb82223e7e3ec7c5a79b5091d6ca08 | |
| parent | b395d5d1cd122bb07017f23110f76249398a61bb (diff) | |
refactor: more literal types
| -rw-r--r-- | interpreter.py | 190 | ||||
| -rw-r--r-- | parser.py | 51 | ||||
| -rw-r--r-- | structs.py | 19 |
3 files changed, 123 insertions, 137 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]) @@ -1,42 +1,4 @@ -from structs import TokenType, Literal, Symbol, Type, List -''' -from lexer import TokenType - -class Expr: - - def accept(self, visitor): - raise Exception("needs to be implemented") - - class Literal: - def __init__(self, value): - self.value = value - def accept(self, visitor): - visitor.visitLiteral(self) - def __str__(self): - return f"{self.value}" - - class Type: - def __init__(self, name): - self.name = name - def __str__(self): - return self.name - - class Symbol: - def __init__(self, name): - self.name = name - def accept(self, visitor): - visitor.visitSymbol(self) - def __str__(self): - return f"'{self.name}" - - class List: - def __init__(self, args): - self.args = args - def accept(self, visitor): - visitor.visitNary(self) - def __str__(self): - return "(" + " ".join(f"{arg}" for arg in self.args) + ")" -''' +from structs import * def parseExpression(token, prev, tokens): idx = 0 @@ -66,7 +28,16 @@ def parseSymbol(token, prev, tokens): return Symbol(token.text), 1 def parseLiteral(token, prev, tokens): - return Literal(token.value), 1 + if token.type_ == TokenType.STRING: + return String(token.value), 1 + elif token.type_ == TokenType.INT: + return Int(token.value), 1 + elif token.type_ == TokenType.FLOAT: + return Float(token.value), 1 + elif token.type_ in (TokenType.TRUE, TokenType.FALSE): + return Bool(token.value), 1 + else: + return Literal(token.value), 1 def parseType(token, prev, tokens): return Type(token.text), 1 @@ -67,12 +67,29 @@ class Token: def __str__(self): return f"{self.type_.name} {self.text} {self.line}" +# Literals class Literal: def __init__(self, value): self.value = value def __str__(self): + return f"{self.value}:literal" + +class Int(Literal): + def __str__(self): + return f"{self.value}" + +class Float(Literal): + def __str__(self): return f"{self.value}" +class Bool(Literal): + def __str__(self): + return f"#{str(self.value).lower()}" + +class String(Literal): + def __str__(self): + return f'"{self.value}"' + class Type: def __init__(self, name): self.name = name @@ -90,4 +107,4 @@ class List: self.args = args self.data = data def __str__(self): - return "(" + " ".join(f"{arg}" for arg in self.args) + ")" + return "(" + ",".join(f"{arg}" for arg in self.args) + ")" |
