aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormryouse2022-06-05 03:06:12 +0000
committermryouse2022-06-05 03:06:12 +0000
commit86104621edc9758155909e0b6e7b9822190815fc (patch)
tree572e8720273daa75e93da50c75808d046ceae3d8
parented78cd23f92b8b96dd7ffa3313be2262b0b29ff0 (diff)
refactor: better error messages
-rw-r--r--exceptions.py10
-rw-r--r--interpreter.py159
-rw-r--r--neb.py5
-rw-r--r--parser.py2
-rw-r--r--structs.py5
5 files changed, 100 insertions, 81 deletions
diff --git a/exceptions.py b/exceptions.py
new file mode 100644
index 0000000..98395ac
--- /dev/null
+++ b/exceptions.py
@@ -0,0 +1,10 @@
+class NebPanic(BaseException):
+ pass
+
+class InterpretPanic(NebPanic):
+ def __init__(self, sym, msg, arg=None):
+ big_message = f"[{sym.line}] '{sym.name}': {msg}"
+ if arg is not None:
+ big_message += f" ({arg})"
+ super().__init__(big_message)
+
diff --git a/interpreter.py b/interpreter.py
index ef0bc4a..47b2363 100644
--- a/interpreter.py
+++ b/interpreter.py
@@ -1,4 +1,5 @@
from structs import *
+from exceptions import *
from pathlib import Path
from glob import glob
import subprocess
@@ -31,7 +32,7 @@ class Builtin(Function):
for arity in self.arities[1:]:
fmt += f", {arity}"
fmt += "]"
- raise Exception(f"expected {fmt} arguments, received {len(expr.args)}")
+ raise InterpretPanic(expr.args[0], f"expected {fmt} arguments, received {len(expr.args) - 1}")
return self.body(expr.args[0], expr.args[1:], env)
class UserFunction(Function):
@@ -58,7 +59,7 @@ class Environment:
def reregister(self, key, value):
if not self.contains(key):
- raise Exception(f"undefined symbol: '{key}")
+ raise NebPanic(f"undefined symbol: '{key}")
if key in self.environment:
self.register(key, value)
else:
@@ -74,7 +75,7 @@ class Environment:
def get(self, key):
if not self.contains(key):
- raise Exception(f"undefined symbol: '{key}")
+ raise NebPanic(f"undefined symbol: '{key}")
if key in self.environment:
return self.environment[key]
else:
@@ -100,7 +101,7 @@ def evaluate(expr, env):
return expr
elif isinstance(expr, Symbol):
if not env.contains(expr.name):
- raise Exception(f"no such symbol: {expr}")
+ raise NebPanic(f"no such symbol: {expr}")
return evaluate(env.get(expr.name), env)
# if it's a literal list, return it
@@ -111,23 +112,23 @@ def evaluate(expr, env):
return expr
if not isinstance(expr.args[0], Symbol):
- raise Exception("can't evaluate without a symbol")
+ raise NebPanic("can't evaluate without a symbol")
name = expr.args[0].name
if name == "def":
return interpretDef(expr.args[0], expr.args[1:], env)
elif env.contains(name):
return env.get(name).call(expr, env)
else:
- raise Exception(f"unable to evaluate: {expr}")
+ raise InterpretPanic(expr.args[0], "unable to evaluate")
def interpretOr(symbol, args, env):
# or returns true for the first expression that returns true
if len(args) < 2:
- raise Exception("'or' has at least two operands")
+ raise InterpretPanic(symbol, "requires at least two arguments")
for arg in args:
ev = evaluate(arg, env)
if not isinstance(ev, Bool):
- raise Exception("'or' needs boolean arguments")
+ raise InterpretPanic(symbol, "requires :bool arguments")
if ev.value == True:
return ev
return Bool(False)
@@ -137,11 +138,11 @@ GLOBALS.register("or", Builtin(interpretOr))
def interpretAnd(symbol, args, env):
# and returns false for the first expression that returns false
if len(args) < 2:
- raise Exception("'and' has at least two operands")
+ raise InterpretPanic(symbol, "requires at least two arguments")
for arg in args:
ev = evaluate(arg, env)
if not isinstance(ev, Bool):
- raise Exception("'and' needs boolean arguments")
+ raise InterpretPanic(symbol, "requires :bool arguments")
if ev.value == False:
return ev
return Bool(True)
@@ -154,7 +155,7 @@ def interpretEq(symbol, args, env):
first = evaluate(args[0], env)
second = evaluate(args[1], env)
if not (isinstance(first, Literal) and isinstance(second, Literal)):
- raise Exception("'eq?' can only compare literals")
+ raise InterpretPanic(symbol, "can only compare :literals")
# compare types because 0 != #false in neb
# TODO number equality?
if type(first) == type(second) and first.value == second.value:
@@ -167,10 +168,10 @@ GLOBALS.register("eq?", Builtin(interpretEq, 2))
def interpretComparison(symbol, args, env):
left = evaluate(args[0], env)
if not (isinstance(left, Int) or isinstance(left, Float)):
- raise Exception("'left' must be a number")
+ raise InterpretPanic(symbol, "first argument must be a :number", left)
right = evaluate(args[1], env)
if not (isinstance(right, Int) or isinstance(right, Float)):
- raise Exception("'right' must be a number")
+ raise InterpretPanic(symbol, "second argument must be a :number", right)
if symbol.name == ">":
return Bool(left.value > right.value)
@@ -188,12 +189,12 @@ GLOBALS.register("<=", Builtin(interpretComparison, 2))
def interpretTerm(symbol, args, env):
if len(args) < 1:
- raise Exception("term has at least one operand")
+ raise InterpretPanic(symbol, "requires at least one argument")
res = None
for arg in args:
ev = evaluate(arg, env)
if not (isinstance(ev, Int) or isinstance(ev, Float)):
- raise Exception("term must be a number")
+ raise InterpretPanic(symbol, "argument must be a :number", ev)
if res is None:
res = ev.value
elif symbol.name == "+":
@@ -212,10 +213,10 @@ def interpretFactor(symbol, args, env):
if symbol.name == "/":
num = evaluate(args[0], env)
if not (isinstance(num, Int) or isinstance(num, Float)):
- raise Exception("numerator must be a number")
+ raise InterpretPanic(symbol, "numerator must be a :number", num)
denom = evaluate(args[1], env)
if not (isinstance(denom, Int) or isinstance(denom, Float)):
- raise Exception("denominator must be a number")
+ raise InterpretPanic(symbol, "denominator must be a :number", denom)
ret = num.value / denom.value
if int(ret) == ret:
return Int(int(ret))
@@ -223,15 +224,15 @@ def interpretFactor(symbol, args, env):
return Float(ret)
else:
if len(args) < 2:
- raise Exception("'*' requires at least two operands")
+ raise InterpretPanic(symbol, "requires at least two arguments")
first = evaluate(args[0], env)
if not (isinstance(first, Int) or isinstance(first, Float)):
- raise Exception("'*' operand must be a number")
+ raise InterpretPanic(symbol, "argument must be a :number", first)
res = first.value
for arg in args[1:]:
tmp = evaluate(arg, env)
if not (isinstance(tmp, Int) or isinstance(tmp, Float)):
- raise Exception("'*' operand must be a number")
+ raise InterpretPanic(symbol, "argument must be a :number", tmp)
res = res * tmp.value
if isinstance(res, float):
return Float(res)
@@ -244,7 +245,7 @@ GLOBALS.register("/", Builtin(interpretFactor, 2))
def interpretNot(symbol, args, env):
res = evaluate(args[0], env)
if not isinstance(res, Bool):
- raise Exception("'not' only works on booleans")
+ raise InterpretPanic(symbol, "requires a :bool", res)
return Bool(not res.value)
GLOBALS.register("not", Builtin(interpretNot, 1))
@@ -253,7 +254,7 @@ def interpretIf(symbol, args, env):
# if cond t-branch [f-branch]
cond = evaluate(args[0], env)
if not isinstance(cond, Bool):
- raise Exception("'if' condition must be boolean")
+ raise InterpretPanic(symbol, "condition must be :bool", cond)
if cond.value:
return evaluate(args[1], env)
elif len(args) == 3:
@@ -265,7 +266,7 @@ GLOBALS.register("if", Builtin(interpretIf, 2, 3))
def interpretPrint(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("can only 'print' strings")
+ raise InterpretPanic(symbol, "requires a :string")
print(ev.value)
return List([]) # print returns nothing
@@ -274,10 +275,10 @@ GLOBALS.register("print", Builtin(interpretPrint, 1))
def interpretDef(symbol, args, env):
if not isinstance(args[0], Symbol):
- raise Exception("'def' requires a string literal as a name")
+ raise InterpretPanic(symbol, "requires a :string name", args[0])
name = 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")
+ raise InterpretPanic(symbol, "requires a :string name")
ev = evaluate(args[1], env)
env.register(name, ev)
@@ -287,10 +288,10 @@ 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")
+ raise InterpretPanic(symbol, "requires a :string name", args[0])
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")
+ raise InterpretPanic(symbol, "not previously defined", args[0])
ev = evaluate(args[1], env)
env.reregister(name, ev)
@@ -321,12 +322,12 @@ GLOBALS.register("->string", Builtin(interpretToString, 1))
def interpretConcat(symbol, args, env):
# concat str1 str2...strN
if len(args) < 2:
- raise Exception("'concat' takes at least two arguments")
+ raise InterpretPanic(symbol, "requires at least two arguments")
out = ""
for arg in args:
tmp = evaluate(arg, env)
if not isinstance(tmp, String):
- raise Exception("'concat' arguments must be strings")
+ raise InterpretPanic(symbol, "requires :string", tmp)
out += tmp.value
return String(out)
@@ -336,7 +337,7 @@ def interpretForCount(symbol, args, env):
# for-count int exprs
num = evaluate(args[0], env)
if not isinstance(num, Int):
- raise Exception("'for-count' count must be an integer")
+ raise InterpretPanic(symbol, "count must be an integer", num)
new_env = Environment(env)
ret = None
for idx in range(0, num.value):
@@ -353,7 +354,7 @@ 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")
+ raise InterpretPanic(symbok, "requires a :list", lst)
new_env = Environment(env)
ret = None
for item in lst.args:
@@ -368,7 +369,7 @@ GLOBALS.register("for-each", Builtin(interpretForEach))
def interpretPipe(symbol, args, env):
if len(args) < 2:
- raise Exception("'|' takes at least two expressions")
+ raise InterpretPanic(symbol, "requires at least two expressions")
new_env = Environment(env)
pipe = None
for arg in args:
@@ -383,11 +384,13 @@ GLOBALS.register("|", Builtin(interpretPipe))
def interpretBranch(symbol, args, env):
if len(args) == 0:
- raise Exception("'branch' takes at least one expression")
+ raise InterpretPanic(symbol, "requires at least one pair of expressions")
for arg in args:
if len(arg.args) != 2:
- raise Exception("'branch' branches have two expressions")
+ raise InterpretPanic(symbol, "each branch requires two expressions")
cond = evaluate(arg.args[0], env) # this is the condition
+ if not isinstance(cond, Boolean):
+ raise InterpretPanic(symbol, "branch condition must be :bool", cond)
if cond.value:
return evaluate(arg.args[1], env)
return List([])
@@ -397,9 +400,9 @@ GLOBALS.register("branch", Builtin(interpretBranch))
def interpretFunc(symbol, args, env):
# func <name> (args) (exprs)
if len(args) < 3:
- raise Exception("'func' takes a name, arguments, and at least one expression")
+ raise InterpretPanic(symbol, "requires a name, argument list, and at least one expression")
if not isinstance(args[0], Symbol):
- raise Exception("'func' requires a string literal as a name")
+ raise InterpretPanic(symbol, "requires a :string name")
name = args[0].name # NOTE: we are not evaluating the name!!
# compose a lambda
@@ -416,7 +419,7 @@ def interpretReadLines(symbol, args, 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}")
+ raise InterpretPanic(symbol, "no such file", target_file)
with open(target_file, "r") as fil:
data = fil.readlines()
out = List([String(d) for d in data], True) # all lines are strings
@@ -427,6 +430,8 @@ GLOBALS.register("read-lines", Builtin(interpretReadLines, 1))
# - strip whitespace from string
def interpretStrip(symbol, args, env):
out = evaluate(args[0], env)
+ if not isinstance(out, String):
+ raise InterpretPanic(symbol, "requires a :string", out)
return String(out.value.strip())
GLOBALS.register("strip", Builtin(interpretStrip, 1))
@@ -435,12 +440,12 @@ GLOBALS.register("strip", Builtin(interpretStrip, 1))
def interpretStringToInt(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'string->int' expects a string")
+ raise InterpretPanic(symbol, "requires a :string", ev)
try:
val = int(ev.value)
return Int(val)
except:
- raise Exception(f"can't convert {ev} to an int")
+ raise InterpretPanic(symbol, "can't convert to an :int", ev)
GLOBALS.register("string->int", Builtin(interpretStringToInt, 1))
@@ -448,10 +453,10 @@ GLOBALS.register("string->int", Builtin(interpretStringToInt, 1))
def interpretSplit(symbol, args, env):
target = evaluate(args[0], env)
if not isinstance(target, String):
- raise Exception("'split' expects a string")
+ raise InterpretPanic(symbol, "requires a :string as its first argument", target)
splitter = evaluate(args[1], env)
if not isinstance(splitter, String):
- raise Exception("'split' expects a string as it's splitter")
+ raise InterpretPanic(symbol, "requires a :string as its second argument", splitter)
ret = target.value.split(splitter.value)
return List([String(r) for r in ret], True)
@@ -461,7 +466,7 @@ GLOBALS.register("split", Builtin(interpretSplit, 2))
def interpretListLength(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'list-length' expects a List")
+ raise InterpretPanic(symbol, "requires a :list", ev)
return Literal(len(ev.args))
GLOBALS.register("list-length", Builtin(interpretListLength, 1))
@@ -470,9 +475,9 @@ GLOBALS.register("list-length", Builtin(interpretListLength, 1))
def interpretFirst(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'first' expects a List")
+ raise InterpretPanic(symbol, "requires a :list", ev)
if len(ev.args) == 0:
- raise Exception("List is empty")
+ raise InterpretPanic(symbol, "list is empty")
return evaluate(ev.args[0], env)
GLOBALS.register("first", Builtin(interpretFirst, 1))
@@ -480,7 +485,7 @@ GLOBALS.register("first", Builtin(interpretFirst, 1))
def interpretRest(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'rest' expects a List")
+ raise InterpretPanic(symbol, "requires a :list", ev)
# TODO do we know it's not evaluated?
return List(ev.args[1:], True) # we don't evaluate the remainder of the list
@@ -492,10 +497,10 @@ def interpretMap(symbol, args, env):
# TODO: to support lambdas, we can't assume the func is defined
func = args[0]
if not isinstance(func, Symbol):
- raise Exception("'map' takes a function as its first argument")
+ raise InterpretPanic(symbol, "requires a symbol as its first argument", func)
lst = evaluate(args[1], env)
if not isinstance(lst, List):
- raise Exception("'map' takes a List as its second argument")
+ raise InterpretPanic(symbol, "requires a :list as its second argument", lst)
out = []
for arg in lst.args:
ev = evaluate(List([func, arg]), env)
@@ -507,12 +512,12 @@ 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")
+ raise InterpretPanic(symbol, "requires two :lists", z1)
z2 = evaluate(args[1], env)
if not isinstance(z2, List):
- raise Exception("'zip' only works on lists")
+ raise InterpretPanic(symbol, "requires two :lists", z2)
if len(z1.args) != len(z2.args):
- raise Exception("'zip' expects two lists of the same size")
+ raise InterpretPanic(symbol, "requires two :lists of the same size")
out = []
for idx in range(len(z1.args)):
f = evaluate(z1.args[idx], env)
@@ -533,7 +538,7 @@ 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")
+ raise InterpretPanic(symbol, "requires a :list", lst)
new_args = lst.args[:] # make a copy of the args
new_args.reverse()
return List(new_args, True)
@@ -543,10 +548,10 @@ 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")
+ raise InterpretPanic(symbol, "requires a symbol as its first argument", func)
lst = evaluate(args[1], env)
if not isinstance(lst, List):
- raise Exception("'apply' takes a List as its second argument")
+ raise InterpretPanic(symbol, "requires a :list as its second argument". lst)
new_lst = List([func] + lst.args)
return evaluate(new_lst, env)
@@ -555,7 +560,7 @@ GLOBALS.register("apply", Builtin(interpretApply, 2))
def interpretGlob(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'glob' expects a string")
+ raise InterpretPanic(symbol, "requires a :string", ev)
items = glob(ev.value)
return List([String(item) for item in items], True)
@@ -564,7 +569,7 @@ GLOBALS.register("glob", Builtin(interpretGlob, 1))
def interpretShell(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'$' expects a string")
+ raise InterpretPanic(symbol, "requires a :string", ev)
# TODO either fail or throw exception (?) on error
ret = subprocess.run(shlex.split(ev.value), capture_output=True)
return List([String(r) for r in ret.stdout.decode("utf-8").split("\n")], True)
@@ -574,7 +579,7 @@ GLOBALS.register("$", Builtin(interpretShell, 1))
def interpretEmpty(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'empty?' expects a List")
+ raise InterpretPanic(symbol, "requires a :list", ev)
return Bool(len(ev.args) == 0)
GLOBALS.register("empty?", Builtin(interpretEmpty, 1))
@@ -582,7 +587,7 @@ GLOBALS.register("empty?", Builtin(interpretEmpty, 1))
def interpretShuf(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'shuf' expects a List")
+ raise InterpretPanic(symbol, "expects a :list", ev)
items = ev.args[:]
random.shuffle(items)
return List(items, True)
@@ -605,10 +610,10 @@ GLOBALS.register("block", Builtin(interpretBlock))
def interpretExit(symbol, args, env):
if len(args) > 1:
- raise Exception("'exit' only takes one (optional) argument")
+ raise InterpretPanic(symbol, "expects one (optional) argument")
status = 0 if len(args) == 0 else evaluate(args[0], env).value
if not isinstance(status, int):
- raise Exception("'exit' requires an :int")
+ raise InterpretPanic(symbol, "expects an :int", status)
sys.exit(status)
return List([])
@@ -617,10 +622,10 @@ GLOBALS.register("exit", Builtin(interpretExit, 0, 1))
def interpretUnlink(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'unlink' expects a string")
+ raise InterpretPanic(symbol, "expects a :string", ev)
target_path = Path(ev.value).resolve()
if not target_path.exists():
- raise Exception("'unlink' expects the target file to exist")
+ raise InterpretPanic(symbol, "target file does not exist", target_path)
target_path.unlink()
return List([])
@@ -637,10 +642,10 @@ GLOBALS.register("argv", Builtin(interpretArgv, 0))
def interpretIn(symbol, args, env):
target = evaluate(args[0], env)
if not isinstance(target, Literal):
- raise Exception("'in?' expects a literal as its first argument")
+ raise InterpretPanic(symbol, "expects a :literal as its first argument", target)
lst = evaluate(args[1], env)
if not isinstance(lst, List):
- raise Exception("'in?' expects a list as its second argument")
+ raise InterpretPanic(symbol, "expects a :list as its second argument", lst)
for arg in lst.args:
ev = evaluate(arg, env)
if type(ev) == type(target) and ev.value == target.value:
@@ -652,9 +657,9 @@ GLOBALS.register("in?", Builtin(interpretIn, 2))
def interpretLast(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, List):
- raise Exception("'last' expects a List")
+ raise InterpretPanic("'last' expects a List")
if len(ev.args) == 0:
- raise Exception("List is empty")
+ raise InterpretPanic("List is empty")
return evaluate(ev.args[-1], env)
GLOBALS.register("last", Builtin(interpretLast, 1))
@@ -662,20 +667,20 @@ GLOBALS.register("last", Builtin(interpretLast, 1))
def interpretJoin(symbol, args, env):
lst = evaluate(args[0], env)
if not isinstance(lst, List):
- raise Exception("'join' expects a List as its first argument")
+ raise InterpretPanic(symbol, "expects a :list as its first argument", lst)
target = evaluate(args[1], env)
if not isinstance(target, String):
- raise Exception("'join' expects a :string as its second argument")
+ raise InterpretPanic(symbol, "expects a :string as its second argument", target)
return String(target.value.join(lst.args))
GLOBALS.register("join", Builtin(interpretJoin, 2))
def interpretWithWrite(symbol, args, env):
if len(args) == 0:
- raise Exception("'with-write' expects at least one argument")
+ raise InterpretPanic(symbol, "expects at least one argument")
target_file = evaluate(args[0], env)
if not isinstance(target_file, String):
- raise Exception("'with-write' expects a filename as its first argument")
+ raise InterpretPanic(symbol, "expects a :string as its first argument", target_file)
new_env = Environment(env)
target_path = Path(target_file.value).resolve()
ret = Literal([])
@@ -691,7 +696,7 @@ def interpretWrite(symbol, args, env):
# write :string :filehandle
line = evaluate(args[0], env)
if not isinstance(line, String):
- raise Exception("'write' expects a string as its first argument")
+ raise InterpretPanic(symbol, "expects a :string as its first argument", line)
handle = evaluate(args[1], env)
handle.args[0].write(line.value) # TODO wrong! how do we evaluate a handle?
return Literal([])
@@ -706,7 +711,7 @@ GLOBALS.register("newline", Builtin(interpretNewline, 0))
def interpretExists(symbol, args, env):
file_or_dir = evaluate(args[0], env)
if not isinstance(file_or_dir, String):
- raise Exception("'exists?' expects a string as its first argument")
+ raise InterpretPanic(symbol, "expects a :string", file_or_dir)
return Bool(Path(file_or_dir.value).resolve().exists())
GLOBALS.register("exists?", Builtin(interpretExists, 1))
@@ -714,9 +719,9 @@ GLOBALS.register("exists?", Builtin(interpretExists, 1))
def interpretFirstChar(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'first-char' expects a string")
+ raise InterpretPanic(symbol, "expects a :string", ev)
if len(ev.value) == 0:
- raise Exception("string is empty")
+ raise InterpretPanic(symbol, ":string is empty", ev)
return String(ev.value[0])
GLOBALS.register("first-char", Builtin(interpretFirstChar, 1))
@@ -724,7 +729,7 @@ GLOBALS.register("first-char", Builtin(interpretFirstChar, 1))
def interpretRestChar(symbol, args, env):
ev = evaluate(args[0], env)
if not isinstance(ev, String):
- raise Exception("'rest-char' expects a string")
+ raise InterpretPanic(symbol, "expects a string", ev)
return String(ev.value[1:])
GLOBALS.register("rest-char", Builtin(interpretRestChar, 1))
@@ -732,15 +737,15 @@ GLOBALS.register("rest-char", Builtin(interpretRestChar, 1))
def interpretSlice(symbol, args, env):
lst = evaluate(args[0], env)
if not isinstance(lst, List):
- raise Exception("'slice' expects a list as its first argument")
+ raise InterpretPanic(symbol, "expects a :list as its first argument", lst)
idx = evaluate(args[1], env)
if not isinstance(idx, Int):
- raise Exception("'slice' expects an integer as its second argument")
+ raise InterpretPanic(symbol, "expects an :int as its second argument", idx)
if len(args) == 2:
return List(lst.args[idx.value - 1:])
length = evaluate(args[2], env)
if not isinstance(length, Int):
- raise Exception("'slice' expects an integer as its third argument")
+ raise InterpretPanic(symbol, "expects an :int as its third argument", length)
diff = idx.value - 1 + length.value
return List(lst.args[idx.value - 1:diff])
diff --git a/neb.py b/neb.py
index 3848ea2..5f26233 100644
--- a/neb.py
+++ b/neb.py
@@ -1,6 +1,7 @@
from lexer import lex
from parser import parse
from interpreter import interpret
+from exceptions import NebPanic
import sys
@@ -36,8 +37,10 @@ def repl():
print(f"=> {inter}")
prev_idx = idx
idx += 1
+ except NebPanic as ne:
+ print(f"panic! {ne}")
except Exception as e:
- print(f"panic! {e}")
+ print(f"exception! {type(e)} {e}")
def run_file(filename):
diff --git a/parser.py b/parser.py
index 166fc8d..60bd029 100644
--- a/parser.py
+++ b/parser.py
@@ -25,7 +25,7 @@ def parseExpression(token, prev, tokens):
return List(args), idx + 2 # parens
def parseSymbol(token, prev, tokens):
- return Symbol(token.text), 1
+ return Symbol(token.text, token.line), 1
def parseLiteral(token, prev, tokens):
if token.type_ == TokenType.STRING:
diff --git a/structs.py b/structs.py
index bab9c13..0cd4a1e 100644
--- a/structs.py
+++ b/structs.py
@@ -97,8 +97,9 @@ class Type:
return self.name
class Symbol:
- def __init__(self, name):
+ def __init__(self, name, line):
self.name = name
+ self.line = line
def __str__(self):
return f"'{self.name}"
@@ -107,4 +108,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) + ")"