aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--neb/__init__.py2
-rw-r--r--neb/std/fs.py80
-rw-r--r--neb/std/sys.py10
-rw-r--r--neb/std/types.py7
-rw-r--r--neb/structs.py6
-rw-r--r--neb/typeclass.py1
-rw-r--r--tests/math/addition/bad_arity.neb2
-rw-r--r--tests/math/addition/bad_type_bool.neb2
-rw-r--r--tests/math/addition/bad_type_list.neb2
-rw-r--r--tests/math/addition/bad_type_string.neb2
-rw-r--r--tests/math/addition/bad_type_type.neb2
-rw-r--r--tests/math/addition/happy.neb10
-rw-r--r--tests/math/division/bad_arity_one.neb2
-rw-r--r--tests/math/division/bad_arity_three.neb2
-rw-r--r--tests/math/division/bad_arity_zero.neb2
-rw-r--r--tests/math/division/bad_type_bool.neb2
-rw-r--r--tests/math/division/bad_type_list.neb2
-rw-r--r--tests/math/division/bad_type_string.neb2
-rw-r--r--tests/math/division/bad_type_type.neb2
-rw-r--r--tests/math/division/happy.neb8
-rw-r--r--tests/math/floor/bad_arity_two.neb2
-rw-r--r--tests/math/floor/bad_arity_zero.neb2
-rw-r--r--tests/math/floor/bad_type_bool.neb2
-rw-r--r--tests/math/floor/bad_type_list.neb2
-rw-r--r--tests/math/floor/bad_type_string.neb2
-rw-r--r--tests/math/floor/bad_type_type.neb2
-rw-r--r--tests/math/floor/happy.neb7
-rw-r--r--tests/math/greaterthan/bad_arity_one.neb2
-rw-r--r--tests/math/greaterthan/bad_arity_three.neb2
-rw-r--r--tests/math/greaterthan/bad_arity_zero.neb2
-rw-r--r--tests/math/greaterthan/bad_type_bool.neb2
-rw-r--r--tests/math/greaterthan/bad_type_list.neb2
-rw-r--r--tests/math/greaterthan/bad_type_string.neb2
-rw-r--r--tests/math/greaterthan/bad_type_type.neb2
-rw-r--r--tests/math/greaterthan/happy.neb9
-rw-r--r--tests/math/greaterthanequal/bad_arity_one.neb2
-rw-r--r--tests/math/greaterthanequal/bad_arity_three.neb2
-rw-r--r--tests/math/greaterthanequal/bad_arity_zero.neb2
-rw-r--r--tests/math/greaterthanequal/bad_type_bool.neb2
-rw-r--r--tests/math/greaterthanequal/bad_type_list.neb2
-rw-r--r--tests/math/greaterthanequal/bad_type_string.neb2
-rw-r--r--tests/math/greaterthanequal/bad_type_type.neb2
-rw-r--r--tests/math/greaterthanequal/happy.neb10
-rw-r--r--tests/math/lessthan/bad_arity_one.neb2
-rw-r--r--tests/math/lessthan/bad_arity_three.neb2
-rw-r--r--tests/math/lessthan/bad_arity_zero.neb2
-rw-r--r--tests/math/lessthan/bad_type_bool.neb2
-rw-r--r--tests/math/lessthan/bad_type_list.neb2
-rw-r--r--tests/math/lessthan/bad_type_string.neb2
-rw-r--r--tests/math/lessthan/bad_type_type.neb2
-rw-r--r--tests/math/lessthan/happy.neb9
-rw-r--r--tests/math/lessthanequal/bad_arity_one.neb2
-rw-r--r--tests/math/lessthanequal/bad_arity_three.neb2
-rw-r--r--tests/math/lessthanequal/bad_arity_zero.neb2
-rw-r--r--tests/math/lessthanequal/bad_type_bool.neb2
-rw-r--r--tests/math/lessthanequal/bad_type_list.neb2
-rw-r--r--tests/math/lessthanequal/bad_type_string.neb2
-rw-r--r--tests/math/lessthanequal/bad_type_type.neb2
-rw-r--r--tests/math/lessthanequal/happy.neb10
-rw-r--r--tests/math/multiplication/bad_arity_one.neb2
-rw-r--r--tests/math/multiplication/bad_arity_zero.neb2
-rw-r--r--tests/math/multiplication/bad_type_bool.neb2
-rw-r--r--tests/math/multiplication/bad_type_list.neb2
-rw-r--r--tests/math/multiplication/bad_type_string.neb2
-rw-r--r--tests/math/multiplication/bad_type_type.neb2
-rw-r--r--tests/math/multiplication/happy.neb9
-rw-r--r--tests/math/subtraction/bad_arity.neb2
-rw-r--r--tests/math/subtraction/bad_type_bool.neb2
-rw-r--r--tests/math/subtraction/bad_type_list.neb2
-rw-r--r--tests/math/subtraction/bad_type_string.neb2
-rw-r--r--tests/math/subtraction/bad_type_type.neb2
-rw-r--r--tests/math/subtraction/happy.neb10
-rwxr-xr-xtests/runner.bash22
73 files changed, 318 insertions, 6 deletions
diff --git a/neb/__init__.py b/neb/__init__.py
index 407ff74..0542d0d 100644
--- a/neb/__init__.py
+++ b/neb/__init__.py
@@ -12,7 +12,7 @@ def interpret(exprs, env, ns=None):
return ret
def evaluate(expr, env, ns=None):
- if isinstance(expr, Literal) or isinstance(expr, Function) or isinstance(expr, TypeWrap) or isinstance(expr, List):
+ if isinstance(expr, Literal) or isinstance(expr, Function) or isinstance(expr, TypeWrap) or isinstance(expr, List) or isinstance(expr, Handle):
return expr
elif isinstance(expr, Symbol) or isinstance(expr, Type):
if env.contains(expr.name):
diff --git a/neb/std/fs.py b/neb/std/fs.py
index 495c7ea..d20cde9 100644
--- a/neb/std/fs.py
+++ b/neb/std/fs.py
@@ -2,9 +2,15 @@ from .. import TypeEnum, Environment, Arg, Builtin, evaluate, InterpretPanic
from ..structs import *
from pathlib import Path
from glob import glob
+from io import UnsupportedOperation
+import sys
FS = Environment()
+FS.register("_stdin_", Handle(sys.stdin))
+FS.register("_stdout_", Handle(sys.stdout))
+FS.register("_stderr_", Handle(sys.stderr))
+
def interpretExists(symbol, args, env, ns):
return Bool(Path(args[0].value).resolve().exists())
@@ -39,13 +45,77 @@ def interpretWithWrite(symbol, args, env, ns):
FS.register("with-write", Builtin("with-write", interpretWithWrite, [Arg("filename", TypeEnum.STRING)], Arg("exprs", TypeEnum.ANY, lazy=True)))
def interpretWrite(symbol, args, env, ns):
- # write :string :filehandle
- line = args[0]
+ string = args[0]
handle = args[1]
- handle.args[0].write(line.value) # TODO wrong! how do we evaluate a handle?
- return Literal([])
+ try:
+ handle.file.write(string.value)
+ except UnsupportedOperation:
+ raise InterpretPanic(symbol, f"{handle} is not writable!")
+ except ValueError:
+ raise InterpretPanic(symbol, f"{handle} is closed")
+ return List([])
+
+FS.register("write", Builtin("write", interpretWrite, [Arg("string", TypeEnum.STRING), Arg("handle", TypeEnum.HANDLE)], return_type=TypeEnum.LIST))
+
+def interpretOpenRead(symbol, args, env, ns):
+ name = args[0].value
+ fil = Path(name)
+ if not fil.exists():
+ raise InterpretPanic(symbol, "file does not exist", fil)
+ try:
+ f = open(str(fil), "r")
+ except:
+ raise InterpretPanic(symbol, "cannot open {fil} for reading")
+ return Handle(f)
+
+FS.register("open-read", Builtin("open-read", interpretOpenRead, [Arg("filename", TypeEnum.STRING)], return_type=TypeEnum.HANDLE))
+
+def interpretOpenWrite(symbol, args, env, ns):
+ name = args[0].value
+ fil = Path(name)
+ try:
+ f = open(str(fil), "w")
+ except:
+ raise InterpretPanic(symbol, "cannot open {fil} for writing")
+ return Handle(f)
+
+FS.register("open-write", Builtin("open-write", interpretOpenWrite, [Arg("filename", TypeEnum.STRING)], return_type=TypeEnum.HANDLE))
+
+def interpretOpenAppend(symbol, args, env, ns):
+ name = args[0].value
+ fil = Path(name)
+ try:
+ f = open(str(fil), "a")
+ except:
+ raise InterpretPanic(symbol, "cannot open {fil} for appending")
+ return Handle(f)
+
+FS.register("open-append", Builtin("open-append", interpretOpenAppend, [Arg("filename", TypeEnum.STRING)], return_type=TypeEnum.HANDLE))
+
+def interpretClose(symbol, args, env, ns):
+ try:
+ args[0].file.close()
+ # TODO ideally we'd be able to remove it from the env
+ # but by this point, we don't know its symbol
+ # though it may not be in the env, e.g.
+ # (close (print (read (open-read "fil.txt"))))
+ except:
+ raise InterpretPanic(symbol, "cannot close {args[0]}")
+ return List([])
+
+FS.register("close", Builtin("close", interpretClose, [Arg("handle", TypeEnum.HANDLE)], return_type=TypeEnum.LIST))
+
+def interpretRead(symbol, args, env, ns):
+ handle = args[0]
+ try:
+ inp = args[0].file.read()
+ except UnsupportedOperation:
+ raise InterpretPanic(symbol, f"{handle} is not writable!")
+ except ValueError:
+ raise InterpretPanic(symbol, f"{handle} is closed")
+ return String(inp)
-FS.register("write", Builtin("write", interpretWrite, [Arg("string", TypeEnum.STRING), Arg("filename", TypeEnum.LIST)]))
+FS.register("read", Builtin("read", interpretRead, [Arg("handle", TypeEnum.HANDLE)], return_type=TypeEnum.STRING))
def interpretReadLines(symbol, args, env, ns):
target_file_name = args[0].value
diff --git a/neb/std/sys.py b/neb/std/sys.py
index 61e83c2..3dc87e9 100644
--- a/neb/std/sys.py
+++ b/neb/std/sys.py
@@ -3,6 +3,7 @@ from ..structs import *
import shlex
import subprocess
import sys
+from datetime import datetime
SYS = Environment()
@@ -33,3 +34,12 @@ def interpretPrint(symbol, args, env, ns):
return List([]) # print returns nothing
SYS.register("print", Builtin("print", interpretPrint, [Arg("arg", TypeEnum.STRING)], return_type=Type(":list")))
+
+def interpretBench(symbol, args, env, ns):
+ before = datetime.now()
+ ret = evaluate(args[0], env, ns)
+ after = datetime.now()
+ print(f"bench [{symbol.line}]: {args[0]} => {after - before}")
+ return ret
+
+SYS.register("bench", Builtin("bench", interpretBench, [Arg("command", TypeEnum.ANY, lazy=True)], return_type=Type(":any")))
diff --git a/neb/std/types.py b/neb/std/types.py
index 01e4fb3..9cd8e6d 100644
--- a/neb/std/types.py
+++ b/neb/std/types.py
@@ -88,6 +88,11 @@ def interpretIsLiteral(symbol, args, env, ns):
TYPES.register("literal?", Builtin("literal?", interpretIsLiteral, [Arg("arg", TypeEnum.ANY)], return_type=Type(":bool")))
+def interpretIsHandle(symbol, args, env, ns):
+ return Bool(isinstance(args[0], Handle))
+
+TYPES.register("handle?", Builtin("handle?", interpretIsHandle, [Arg("arg", TypeEnum.ANY)], return_type=Type(":bool")))
+
# add types to env
any_type = NebType(":any", None, interpretIsAny)
literal_type = NebType(":literal", any_type, interpretIsLiteral)
@@ -98,6 +103,7 @@ bool_type = NebType(":bool", literal_type, interpretIsBool)
number_type = NebType(":number", literal_type, interpretIsNumber)
int_type = NebType(":int", number_type, interpretIsInt)
float_type = NebType(":float", number_type, interpretIsFloat)
+handle_type = NebType(":handle", any_type, interpretIsHandle)
TYPES.register(":any", any_type)
TYPES.register(":literal", literal_type)
@@ -108,3 +114,4 @@ TYPES.register(":bool", bool_type)
TYPES.register(":number", number_type)
TYPES.register(":int", int_type)
TYPES.register(":float", float_type)
+TYPES.register(":handle", handle_type)
diff --git a/neb/structs.py b/neb/structs.py
index 0ba2aaa..1c5248c 100644
--- a/neb/structs.py
+++ b/neb/structs.py
@@ -92,6 +92,12 @@ class String(Literal):
def __str__(self):
return f'"{repr(self.value)[1:-1]}"'
+class Handle:
+ def __init__(self, file):
+ self.file = file
+ def __str__(self):
+ return f"{self.file.name} :handle"
+
class Type:
def __init__(self, name, inner=None):
self.name = name
diff --git a/neb/typeclass.py b/neb/typeclass.py
index 9e37a2d..a6daaf9 100644
--- a/neb/typeclass.py
+++ b/neb/typeclass.py
@@ -9,6 +9,7 @@ class TypeEnum(Enum):
LIST = auto()
LITERAL = auto()
BOOL = auto()
+ HANDLE = auto()
USER = auto()
def __str__(self):
diff --git a/tests/math/addition/bad_arity.neb b/tests/math/addition/bad_arity.neb
new file mode 100644
index 0000000..fd4763d
--- /dev/null
+++ b/tests/math/addition/bad_arity.neb
@@ -0,0 +1,2 @@
+(+)
+; panic! [1] '+': expected [1+] arguments, received 0
diff --git a/tests/math/addition/bad_type_bool.neb b/tests/math/addition/bad_type_bool.neb
new file mode 100644
index 0000000..a4bfecc
--- /dev/null
+++ b/tests/math/addition/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(+ 7.34 #true)
+; panic! [1] '+': received :bool, expected :number (got #true)
diff --git a/tests/math/addition/bad_type_list.neb b/tests/math/addition/bad_type_list.neb
new file mode 100644
index 0000000..cb8a999
--- /dev/null
+++ b/tests/math/addition/bad_type_list.neb
@@ -0,0 +1,2 @@
+(+ 4 (list "hello"))
+; panic! [1] '+': received :list, expected :number (got ("hello"))
diff --git a/tests/math/addition/bad_type_string.neb b/tests/math/addition/bad_type_string.neb
new file mode 100644
index 0000000..23a161c
--- /dev/null
+++ b/tests/math/addition/bad_type_string.neb
@@ -0,0 +1,2 @@
+(+ 3 "hello")
+; panic! [1] '+': received :string, expected :number (got "hello")
diff --git a/tests/math/addition/bad_type_type.neb b/tests/math/addition/bad_type_type.neb
new file mode 100644
index 0000000..088fefb
--- /dev/null
+++ b/tests/math/addition/bad_type_type.neb
@@ -0,0 +1,2 @@
+(+ .4 :string)
+; panic! [1] '+': received :type, expected :number (got :string)
diff --git a/tests/math/addition/happy.neb b/tests/math/addition/happy.neb
new file mode 100644
index 0000000..4c037b2
--- /dev/null
+++ b/tests/math/addition/happy.neb
@@ -0,0 +1,10 @@
+(print
+ (concat
+ (->string (+ 2)) " "
+ (->string (+ 4 5)) " "
+ (->string (+ .9)) " "
+ (->string (+ 2.9 4)) " "
+ (->string (+ 2 4.5)) " "
+ (->string (+ 6 7 8 15))))
+
+; 2 9 0.9 6.9 6.5 36
diff --git a/tests/math/division/bad_arity_one.neb b/tests/math/division/bad_arity_one.neb
new file mode 100644
index 0000000..eb243d1
--- /dev/null
+++ b/tests/math/division/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(/ 7)
+; panic! [1] '/': expected [2] arguments, received 1
diff --git a/tests/math/division/bad_arity_three.neb b/tests/math/division/bad_arity_three.neb
new file mode 100644
index 0000000..34f7313
--- /dev/null
+++ b/tests/math/division/bad_arity_three.neb
@@ -0,0 +1,2 @@
+(/ 20 4 5)
+; panic! [1] '/': expected [2] arguments, received 3
diff --git a/tests/math/division/bad_arity_zero.neb b/tests/math/division/bad_arity_zero.neb
new file mode 100644
index 0000000..c5c47c3
--- /dev/null
+++ b/tests/math/division/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(/)
+; panic! [1] '/': expected [2] arguments, received 0
diff --git a/tests/math/division/bad_type_bool.neb b/tests/math/division/bad_type_bool.neb
new file mode 100644
index 0000000..ad15afe
--- /dev/null
+++ b/tests/math/division/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(/ 7.34 #true)
+; panic! [1] '/': received :bool, expected :number (got #true)
diff --git a/tests/math/division/bad_type_list.neb b/tests/math/division/bad_type_list.neb
new file mode 100644
index 0000000..e394b7b
--- /dev/null
+++ b/tests/math/division/bad_type_list.neb
@@ -0,0 +1,2 @@
+(/ 4 (list "hello"))
+; panic! [1] '/': received :list, expected :number (got ("hello"))
diff --git a/tests/math/division/bad_type_string.neb b/tests/math/division/bad_type_string.neb
new file mode 100644
index 0000000..11f236e
--- /dev/null
+++ b/tests/math/division/bad_type_string.neb
@@ -0,0 +1,2 @@
+(/ 3 "hello")
+; panic! [1] '/': received :string, expected :number (got "hello")
diff --git a/tests/math/division/bad_type_type.neb b/tests/math/division/bad_type_type.neb
new file mode 100644
index 0000000..c008850
--- /dev/null
+++ b/tests/math/division/bad_type_type.neb
@@ -0,0 +1,2 @@
+(/ .4 :string)
+; panic! [1] '/': received :type, expected :number (got :string)
diff --git a/tests/math/division/happy.neb b/tests/math/division/happy.neb
new file mode 100644
index 0000000..dc0692d
--- /dev/null
+++ b/tests/math/division/happy.neb
@@ -0,0 +1,8 @@
+(print
+ (concat
+ (->string (/ 20 5)) " "
+ (->string (/ 3.6 2)) " "
+ (->string (/ 4 2.5)) " "
+ (->string (/ 1.1 4.4))))
+
+; 4 1.8 1.6 0.25
diff --git a/tests/math/floor/bad_arity_two.neb b/tests/math/floor/bad_arity_two.neb
new file mode 100644
index 0000000..2968901
--- /dev/null
+++ b/tests/math/floor/bad_arity_two.neb
@@ -0,0 +1,2 @@
+(floor 20 4)
+; panic! [1] 'floor': expected [1] arguments, received 2
diff --git a/tests/math/floor/bad_arity_zero.neb b/tests/math/floor/bad_arity_zero.neb
new file mode 100644
index 0000000..cc21ccc
--- /dev/null
+++ b/tests/math/floor/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(floor)
+; panic! [1] 'floor': expected [1] arguments, received 0
diff --git a/tests/math/floor/bad_type_bool.neb b/tests/math/floor/bad_type_bool.neb
new file mode 100644
index 0000000..4404bfc
--- /dev/null
+++ b/tests/math/floor/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(floor #true)
+; panic! [1] 'floor': received :bool, expected :number (got #true)
diff --git a/tests/math/floor/bad_type_list.neb b/tests/math/floor/bad_type_list.neb
new file mode 100644
index 0000000..a0d3b3b
--- /dev/null
+++ b/tests/math/floor/bad_type_list.neb
@@ -0,0 +1,2 @@
+(floor (list "hello"))
+; panic! [1] 'floor': received :list, expected :number (got ("hello"))
diff --git a/tests/math/floor/bad_type_string.neb b/tests/math/floor/bad_type_string.neb
new file mode 100644
index 0000000..c1abd33
--- /dev/null
+++ b/tests/math/floor/bad_type_string.neb
@@ -0,0 +1,2 @@
+(floor "hello")
+; panic! [1] 'floor': received :string, expected :number (got "hello")
diff --git a/tests/math/floor/bad_type_type.neb b/tests/math/floor/bad_type_type.neb
new file mode 100644
index 0000000..d5383cf
--- /dev/null
+++ b/tests/math/floor/bad_type_type.neb
@@ -0,0 +1,2 @@
+(floor :string)
+; panic! [1] 'floor': received :type, expected :number (got :string)
diff --git a/tests/math/floor/happy.neb b/tests/math/floor/happy.neb
new file mode 100644
index 0000000..7fda23e
--- /dev/null
+++ b/tests/math/floor/happy.neb
@@ -0,0 +1,7 @@
+(print
+ (concat
+ (->string (floor 5)) " "
+ (->string (floor 3.6)) " "
+ (->string (floor 2.5))))
+
+; 5 3 2
diff --git a/tests/math/greaterthan/bad_arity_one.neb b/tests/math/greaterthan/bad_arity_one.neb
new file mode 100644
index 0000000..08c456a
--- /dev/null
+++ b/tests/math/greaterthan/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(> 7)
+; panic! [1] '>': expected [2] arguments, received 1
diff --git a/tests/math/greaterthan/bad_arity_three.neb b/tests/math/greaterthan/bad_arity_three.neb
new file mode 100644
index 0000000..039cf79
--- /dev/null
+++ b/tests/math/greaterthan/bad_arity_three.neb
@@ -0,0 +1,2 @@
+(> 20 4 5)
+; panic! [1] '>': expected [2] arguments, received 3
diff --git a/tests/math/greaterthan/bad_arity_zero.neb b/tests/math/greaterthan/bad_arity_zero.neb
new file mode 100644
index 0000000..8ca1597
--- /dev/null
+++ b/tests/math/greaterthan/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(>)
+; panic! [1] '>': expected [2] arguments, received 0
diff --git a/tests/math/greaterthan/bad_type_bool.neb b/tests/math/greaterthan/bad_type_bool.neb
new file mode 100644
index 0000000..2907546
--- /dev/null
+++ b/tests/math/greaterthan/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(> 7.34 #true)
+; panic! [1] '>': received :bool, expected :number (got #true)
diff --git a/tests/math/greaterthan/bad_type_list.neb b/tests/math/greaterthan/bad_type_list.neb
new file mode 100644
index 0000000..f24a46c
--- /dev/null
+++ b/tests/math/greaterthan/bad_type_list.neb
@@ -0,0 +1,2 @@
+(> 4 (list "hello"))
+; panic! [1] '>': received :list, expected :number (got ("hello"))
diff --git a/tests/math/greaterthan/bad_type_string.neb b/tests/math/greaterthan/bad_type_string.neb
new file mode 100644
index 0000000..e1f8e15
--- /dev/null
+++ b/tests/math/greaterthan/bad_type_string.neb
@@ -0,0 +1,2 @@
+(> 3 "hello")
+; panic! [1] '>': received :string, expected :number (got "hello")
diff --git a/tests/math/greaterthan/bad_type_type.neb b/tests/math/greaterthan/bad_type_type.neb
new file mode 100644
index 0000000..3fe2e5b
--- /dev/null
+++ b/tests/math/greaterthan/bad_type_type.neb
@@ -0,0 +1,2 @@
+(> .4 :string)
+; panic! [1] '>': received :type, expected :number (got :string)
diff --git a/tests/math/greaterthan/happy.neb b/tests/math/greaterthan/happy.neb
new file mode 100644
index 0000000..e195b7e
--- /dev/null
+++ b/tests/math/greaterthan/happy.neb
@@ -0,0 +1,9 @@
+(print
+ (concat
+ (->string (> 20 5)) " "
+ (->string (> 3.6 2)) " "
+ (->string (> 1 2.5)) " "
+ (->string (> 5 5)) " "
+ (->string (> 1.1 4.4))))
+
+; #true #true #false #false #false
diff --git a/tests/math/greaterthanequal/bad_arity_one.neb b/tests/math/greaterthanequal/bad_arity_one.neb
new file mode 100644
index 0000000..4de4de8
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(>= 7)
+; panic! [1] '>=': expected [2] arguments, received 1
diff --git a/tests/math/greaterthanequal/bad_arity_three.neb b/tests/math/greaterthanequal/bad_arity_three.neb
new file mode 100644
index 0000000..20861f1
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_arity_three.neb
@@ -0,0 +1,2 @@
+(>= 20 4 5)
+; panic! [1] '>=': expected [2] arguments, received 3
diff --git a/tests/math/greaterthanequal/bad_arity_zero.neb b/tests/math/greaterthanequal/bad_arity_zero.neb
new file mode 100644
index 0000000..56c08ac
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(>=)
+; panic! [1] '>=': expected [2] arguments, received 0
diff --git a/tests/math/greaterthanequal/bad_type_bool.neb b/tests/math/greaterthanequal/bad_type_bool.neb
new file mode 100644
index 0000000..3fd50e2
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(>= 7.34 #true)
+; panic! [1] '>=': received :bool, expected :number (got #true)
diff --git a/tests/math/greaterthanequal/bad_type_list.neb b/tests/math/greaterthanequal/bad_type_list.neb
new file mode 100644
index 0000000..b7b9cf8
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_type_list.neb
@@ -0,0 +1,2 @@
+(>= 4 (list "hello"))
+; panic! [1] '>=': received :list, expected :number (got ("hello"))
diff --git a/tests/math/greaterthanequal/bad_type_string.neb b/tests/math/greaterthanequal/bad_type_string.neb
new file mode 100644
index 0000000..10fdaea
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_type_string.neb
@@ -0,0 +1,2 @@
+(>= 3 "hello")
+; panic! [1] '>=': received :string, expected :number (got "hello")
diff --git a/tests/math/greaterthanequal/bad_type_type.neb b/tests/math/greaterthanequal/bad_type_type.neb
new file mode 100644
index 0000000..ae5b166
--- /dev/null
+++ b/tests/math/greaterthanequal/bad_type_type.neb
@@ -0,0 +1,2 @@
+(>= .4 :string)
+; panic! [1] '>=': received :type, expected :number (got :string)
diff --git a/tests/math/greaterthanequal/happy.neb b/tests/math/greaterthanequal/happy.neb
new file mode 100644
index 0000000..16d3c66
--- /dev/null
+++ b/tests/math/greaterthanequal/happy.neb
@@ -0,0 +1,10 @@
+(print
+ (concat
+ (->string (>= 20 5)) " "
+ (->string (>= 3.6 2)) " "
+ (->string (>= 1 2.5)) " "
+ (->string (>= 5 5)) " "
+ (->string (>= 45 45.0)) " "
+ (->string (>= 1.1 4.4))))
+
+; #true #true #false #true #true #false
diff --git a/tests/math/lessthan/bad_arity_one.neb b/tests/math/lessthan/bad_arity_one.neb
new file mode 100644
index 0000000..9e33b3e
--- /dev/null
+++ b/tests/math/lessthan/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(< 7)
+; panic! [1] '<': expected [2] arguments, received 1
diff --git a/tests/math/lessthan/bad_arity_three.neb b/tests/math/lessthan/bad_arity_three.neb
new file mode 100644
index 0000000..fedcbd2
--- /dev/null
+++ b/tests/math/lessthan/bad_arity_three.neb
@@ -0,0 +1,2 @@
+(< 20 4 5)
+; panic! [1] '<': expected [2] arguments, received 3
diff --git a/tests/math/lessthan/bad_arity_zero.neb b/tests/math/lessthan/bad_arity_zero.neb
new file mode 100644
index 0000000..64217c6
--- /dev/null
+++ b/tests/math/lessthan/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(<)
+; panic! [1] '<': expected [2] arguments, received 0
diff --git a/tests/math/lessthan/bad_type_bool.neb b/tests/math/lessthan/bad_type_bool.neb
new file mode 100644
index 0000000..b68fb9f
--- /dev/null
+++ b/tests/math/lessthan/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(< 7.34 #true)
+; panic! [1] '<': received :bool, expected :number (got #true)
diff --git a/tests/math/lessthan/bad_type_list.neb b/tests/math/lessthan/bad_type_list.neb
new file mode 100644
index 0000000..7b1153c
--- /dev/null
+++ b/tests/math/lessthan/bad_type_list.neb
@@ -0,0 +1,2 @@
+(< 4 (list "hello"))
+; panic! [1] '<': received :list, expected :number (got ("hello"))
diff --git a/tests/math/lessthan/bad_type_string.neb b/tests/math/lessthan/bad_type_string.neb
new file mode 100644
index 0000000..b98f088
--- /dev/null
+++ b/tests/math/lessthan/bad_type_string.neb
@@ -0,0 +1,2 @@
+(< 3 "hello")
+; panic! [1] '<': received :string, expected :number (got "hello")
diff --git a/tests/math/lessthan/bad_type_type.neb b/tests/math/lessthan/bad_type_type.neb
new file mode 100644
index 0000000..339d283
--- /dev/null
+++ b/tests/math/lessthan/bad_type_type.neb
@@ -0,0 +1,2 @@
+(< .4 :string)
+; panic! [1] '<': received :type, expected :number (got :string)
diff --git a/tests/math/lessthan/happy.neb b/tests/math/lessthan/happy.neb
new file mode 100644
index 0000000..796a8f1
--- /dev/null
+++ b/tests/math/lessthan/happy.neb
@@ -0,0 +1,9 @@
+(print
+ (concat
+ (->string (< 20 5)) " "
+ (->string (< 3.6 2)) " "
+ (->string (< 1 2.5)) " "
+ (->string (< 5 5)) " "
+ (->string (< 1.1 4.4))))
+
+; #false #false #true #false #true
diff --git a/tests/math/lessthanequal/bad_arity_one.neb b/tests/math/lessthanequal/bad_arity_one.neb
new file mode 100644
index 0000000..32bf4c1
--- /dev/null
+++ b/tests/math/lessthanequal/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(<= 7)
+; panic! [1] '<=': expected [2] arguments, received 1
diff --git a/tests/math/lessthanequal/bad_arity_three.neb b/tests/math/lessthanequal/bad_arity_three.neb
new file mode 100644
index 0000000..2430b0f
--- /dev/null
+++ b/tests/math/lessthanequal/bad_arity_three.neb
@@ -0,0 +1,2 @@
+(<= 20 4 5)
+; panic! [1] '<=': expected [2] arguments, received 3
diff --git a/tests/math/lessthanequal/bad_arity_zero.neb b/tests/math/lessthanequal/bad_arity_zero.neb
new file mode 100644
index 0000000..9130333
--- /dev/null
+++ b/tests/math/lessthanequal/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(<=)
+; panic! [1] '<=': expected [2] arguments, received 0
diff --git a/tests/math/lessthanequal/bad_type_bool.neb b/tests/math/lessthanequal/bad_type_bool.neb
new file mode 100644
index 0000000..6c68ead
--- /dev/null
+++ b/tests/math/lessthanequal/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(<= 7.34 #true)
+; panic! [1] '<=': received :bool, expected :number (got #true)
diff --git a/tests/math/lessthanequal/bad_type_list.neb b/tests/math/lessthanequal/bad_type_list.neb
new file mode 100644
index 0000000..530c8cc
--- /dev/null
+++ b/tests/math/lessthanequal/bad_type_list.neb
@@ -0,0 +1,2 @@
+(<= 4 (list "hello"))
+; panic! [1] '<=': received :list, expected :number (got ("hello"))
diff --git a/tests/math/lessthanequal/bad_type_string.neb b/tests/math/lessthanequal/bad_type_string.neb
new file mode 100644
index 0000000..22b0360
--- /dev/null
+++ b/tests/math/lessthanequal/bad_type_string.neb
@@ -0,0 +1,2 @@
+(<= 3 "hello")
+; panic! [1] '<=': received :string, expected :number (got "hello")
diff --git a/tests/math/lessthanequal/bad_type_type.neb b/tests/math/lessthanequal/bad_type_type.neb
new file mode 100644
index 0000000..7ebbe12
--- /dev/null
+++ b/tests/math/lessthanequal/bad_type_type.neb
@@ -0,0 +1,2 @@
+(<= .4 :string)
+; panic! [1] '<=': received :type, expected :number (got :string)
diff --git a/tests/math/lessthanequal/happy.neb b/tests/math/lessthanequal/happy.neb
new file mode 100644
index 0000000..246f069
--- /dev/null
+++ b/tests/math/lessthanequal/happy.neb
@@ -0,0 +1,10 @@
+(print
+ (concat
+ (->string (<= 20 5)) " "
+ (->string (<= 3.6 2)) " "
+ (->string (<= 1 2.5)) " "
+ (->string (<= 5 5)) " "
+ (->string (<= 45 45.0)) " "
+ (->string (<= 1.1 4.4))))
+
+; #false #false #true #true #true #true
diff --git a/tests/math/multiplication/bad_arity_one.neb b/tests/math/multiplication/bad_arity_one.neb
new file mode 100644
index 0000000..740725a
--- /dev/null
+++ b/tests/math/multiplication/bad_arity_one.neb
@@ -0,0 +1,2 @@
+(* 7)
+; panic! [1] '*': expected [2+] arguments, received 1
diff --git a/tests/math/multiplication/bad_arity_zero.neb b/tests/math/multiplication/bad_arity_zero.neb
new file mode 100644
index 0000000..f1301ae
--- /dev/null
+++ b/tests/math/multiplication/bad_arity_zero.neb
@@ -0,0 +1,2 @@
+(*)
+; panic! [1] '*': expected [2+] arguments, received 0
diff --git a/tests/math/multiplication/bad_type_bool.neb b/tests/math/multiplication/bad_type_bool.neb
new file mode 100644
index 0000000..5344e1a
--- /dev/null
+++ b/tests/math/multiplication/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(* 7.34 #true)
+; panic! [1] '*': received :bool, expected :number (got #true)
diff --git a/tests/math/multiplication/bad_type_list.neb b/tests/math/multiplication/bad_type_list.neb
new file mode 100644
index 0000000..9bc8418
--- /dev/null
+++ b/tests/math/multiplication/bad_type_list.neb
@@ -0,0 +1,2 @@
+(* 4 (list "hello"))
+; panic! [1] '*': received :list, expected :number (got ("hello"))
diff --git a/tests/math/multiplication/bad_type_string.neb b/tests/math/multiplication/bad_type_string.neb
new file mode 100644
index 0000000..ca9b70d
--- /dev/null
+++ b/tests/math/multiplication/bad_type_string.neb
@@ -0,0 +1,2 @@
+(* 3 "hello")
+; panic! [1] '*': received :string, expected :number (got "hello")
diff --git a/tests/math/multiplication/bad_type_type.neb b/tests/math/multiplication/bad_type_type.neb
new file mode 100644
index 0000000..4d0d2d1
--- /dev/null
+++ b/tests/math/multiplication/bad_type_type.neb
@@ -0,0 +1,2 @@
+(* .4 :string)
+; panic! [1] '*': received :type, expected :number (got :string)
diff --git a/tests/math/multiplication/happy.neb b/tests/math/multiplication/happy.neb
new file mode 100644
index 0000000..8233975
--- /dev/null
+++ b/tests/math/multiplication/happy.neb
@@ -0,0 +1,9 @@
+(print
+ (concat
+ (->string (* 4 5)) " "
+ (->string (* 2.9 4)) " "
+ (->string (* 2 4.5)) " "
+ (->string (* 1.1 4.5)) " "
+ (->string (* 6 7 8 15))))
+
+; 20 11.6 9.0 4.95 5040
diff --git a/tests/math/subtraction/bad_arity.neb b/tests/math/subtraction/bad_arity.neb
new file mode 100644
index 0000000..4fd1a01
--- /dev/null
+++ b/tests/math/subtraction/bad_arity.neb
@@ -0,0 +1,2 @@
+(-)
+; panic! [1] '-': expected [1+] arguments, received 0
diff --git a/tests/math/subtraction/bad_type_bool.neb b/tests/math/subtraction/bad_type_bool.neb
new file mode 100644
index 0000000..64a8a7b
--- /dev/null
+++ b/tests/math/subtraction/bad_type_bool.neb
@@ -0,0 +1,2 @@
+(- 7.34 #true)
+; panic! [1] '-': received :bool, expected :number (got #true)
diff --git a/tests/math/subtraction/bad_type_list.neb b/tests/math/subtraction/bad_type_list.neb
new file mode 100644
index 0000000..e06685e
--- /dev/null
+++ b/tests/math/subtraction/bad_type_list.neb
@@ -0,0 +1,2 @@
+(- 4 (list "hello"))
+; panic! [1] '-': received :list, expected :number (got ("hello"))
diff --git a/tests/math/subtraction/bad_type_string.neb b/tests/math/subtraction/bad_type_string.neb
new file mode 100644
index 0000000..5685b59
--- /dev/null
+++ b/tests/math/subtraction/bad_type_string.neb
@@ -0,0 +1,2 @@
+(- 3 "hello")
+; panic! [1] '-': received :string, expected :number (got "hello")
diff --git a/tests/math/subtraction/bad_type_type.neb b/tests/math/subtraction/bad_type_type.neb
new file mode 100644
index 0000000..7bb72aa
--- /dev/null
+++ b/tests/math/subtraction/bad_type_type.neb
@@ -0,0 +1,2 @@
+(- .4 :string)
+; panic! [1] '-': received :type, expected :number (got :string)
diff --git a/tests/math/subtraction/happy.neb b/tests/math/subtraction/happy.neb
new file mode 100644
index 0000000..035b83c
--- /dev/null
+++ b/tests/math/subtraction/happy.neb
@@ -0,0 +1,10 @@
+(print
+ (concat
+ (->string (- 2)) " "
+ (->string (- 4 5)) " "
+ (->string (- .9)) " "
+ (->string (- 2.9 4)) " "
+ (->string (- 2 4.5)) " "
+ (->string (- 6 7 8 15))))
+
+; -2 -1 -0.9 -1.1 -2.5 -24
diff --git a/tests/runner.bash b/tests/runner.bash
new file mode 100755
index 0000000..32af554
--- /dev/null
+++ b/tests/runner.bash
@@ -0,0 +1,22 @@
+#!/bin/bash
+passed=0
+total=0
+for item in $(find . -name *.neb)
+do
+ expected=$(tail -1 $item | sed 's/; //')
+ actual=$(python3 ../neb.py $item)
+ if [[ "$expected" == "$actual" ]]; then
+ ((passed=passed+1))
+ else
+ echo "$item... FAILED"
+ echo " Expected: $expected"
+ echo " Actual: $actual"
+ echo ""
+ fi
+ ((total=total+1))
+done
+
+pct=$(python -c "print('{:.2f}'.format($passed / float($total) * 100))")
+
+echo "Total: $total"
+echo "Passed: $passed ($pct%)"