diff --git a/src/eval.nim b/src/eval.nim index 7f5ffec..27ecfcf 100644 --- a/src/eval.nim +++ b/src/eval.nim @@ -5,6 +5,8 @@ import types, reader var agentRegistry* = initTable[string, CljVal]() var agentCounter*: int64 = 0 +var atomRegistry* = initTable[string, CljVal]() +var atomCounter*: int64 = 0 type Channel* = ref object @@ -22,8 +24,6 @@ type bindings*: Table[string, CljVal] parent*: Env - BuiltinFn = proc(args: seq[CljVal], env: Env): CljVal - EvalResult* = object ok*: bool value*: CljVal @@ -298,20 +298,20 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult = if args[0].kind == ckInt: return EvalResult(ok: true, value: cljInt(-args[0].intVal)) if args[0].kind == ckFloat: return EvalResult(ok: true, value: cljFloat(-args[0].floatVal)) return EvalResult(ok: false, error: "- requires numbers") - var result: float64 - if args[0].kind == ckInt: result = args[0].intVal.float64 - elif args[0].kind == ckFloat: result = args[0].floatVal + var sum: float64 + if args[0].kind == ckInt: sum = args[0].intVal.float64 + elif args[0].kind == ckFloat: sum = args[0].floatVal else: return EvalResult(ok: false, error: "- requires numbers") for i in 1.. 0: acc = coll.items[0] # reduce rest - var result = acc + var res = acc for i in 1.. 2: + callItems.add(args[2..^1]) + let callRes = evalList(callItems, env) + if not callRes.ok: return callRes + atomRegistry[args[0].strVal] = callRes.value + return EvalResult(ok: true, value: callRes.value) of "agent": numArgs(1) diff --git a/tests/test_eval.nim b/tests/test_eval.nim index ae9d526..6106c1c 100644 --- a/tests/test_eval.nim +++ b/tests/test_eval.nim @@ -1,6 +1,5 @@ import unittest import ../src/types -import ../src/reader import ../src/eval suite "Eval - Basic Values": @@ -70,6 +69,17 @@ suite "Eval - Arithmetic": check r.ok check r.value.intVal == 10 + test "float arithmetic": + let r = eval("(+ 1.5 2.5)", env) + check r.ok + check r.value.kind == ckFloat + check r.value.floatVal == 4.0 + + test "negation": + let r = eval("(- 5)", env) + check r.ok + check r.value.intVal == -5 + suite "Eval - Comparison": setup: let env = newTopLevelEnv() @@ -84,6 +94,16 @@ suite "Eval - Comparison": check r.ok check r.value.boolVal == true + test "greater or equal": + let r = eval("(>= 5 5)", env) + check r.ok + check r.value.boolVal == true + + test "less or equal": + let r = eval("(<= 3 5)", env) + check r.ok + check r.value.boolVal == true + test "equality": let r = eval("(= 42 42)", env) check r.ok @@ -94,6 +114,45 @@ suite "Eval - Comparison": check r.ok check r.value.boolVal == true +suite "Eval - Math Predicates": + setup: + let env = newTopLevelEnv() + + test "pos?": + check eval("(pos? 5)", env).value.boolVal == true + check eval("(pos? -1)", env).value.boolVal == false + + test "neg?": + check eval("(neg? -3)", env).value.boolVal == true + check eval("(neg? 3)", env).value.boolVal == false + + test "even?": + check eval("(even? 4)", env).value.boolVal == true + check eval("(even? 3)", env).value.boolVal == false + + test "odd?": + check eval("(odd? 3)", env).value.boolVal == true + check eval("(odd? 4)", env).value.boolVal == false + + test "abs": + check eval("(abs -5)", env).value.intVal == 5 + check eval("(abs 5)", env).value.intVal == 5 + + test "mod": + check eval("(mod 10 3)", env).value.intVal == 1 + + test "quot": + check eval("(quot 10 3)", env).value.intVal == 3 + + test "rem": + check eval("(rem 10 3)", env).value.intVal == 1 + + test "min": + check eval("(min 3 7)", env).value.intVal == 3 + + test "max": + check eval("(max 3 7)", env).value.intVal == 7 + suite "Eval - Special Forms": setup: let env = newTopLevelEnv() @@ -130,6 +189,11 @@ suite "Eval - Special Forms": check r.ok check r.value.intVal == 2 + test "if without else": + let r = eval("(if false 1)", env) + check r.ok + check r.value.kind == ckNil + test "when": let r = eval("(when true 42)", env) check r.ok @@ -145,11 +209,21 @@ suite "Eval - Special Forms": check r.ok check r.value.intVal == 3 + test "do empty": + let r = eval("(do)", env) + check r.ok + check r.value.kind == ckNil + test "fn and call": let r = eval("((fn [x] (+ x 1)) 5)", env) check r.ok check r.value.intVal == 6 + test "variadic fn": + let r = eval("((fn [x & rest] (count rest)) 1 2 3 4)", env) + check r.ok + check r.value.intVal == 3 + test "quote": let r = eval("(quote (1 2 3))", env) check r.ok @@ -166,6 +240,18 @@ suite "Eval - Collections": check r.value.kind == ckVector check r.value.items.len == 3 + test "list constructor": + let r = eval("(list 1 2 3)", env) + check r.ok + check r.value.kind == ckList + check r.value.items.len == 3 + + test "vector constructor": + let r = eval("(vector 1 2 3)", env) + check r.ok + check r.value.kind == ckVector + check r.value.items.len == 3 + test "map literal": let r = eval("{:a 1 :b 2}", env) check r.ok @@ -182,6 +268,16 @@ suite "Eval - Collections": check r.ok check r.value.intVal == 10 + test "last": + let r = eval("(last [10 20 30])", env) + check r.ok + check r.value.intVal == 30 + + test "nth": + let r = eval("(nth [10 20 30] 1)", env) + check r.ok + check r.value.intVal == 20 + test "rest": let r = eval("(rest [1 2 3])", env) check r.ok @@ -204,6 +300,27 @@ suite "Eval - Collections": check r.ok check r.value.items.len == 4 + test "reverse": + let r = eval("(reverse [1 2 3])", env) + check r.ok + check r.value.items[0].intVal == 3 + check r.value.items[2].intVal == 1 + + test "vec": + let r = eval("(vec (list 1 2 3))", env) + check r.ok + check r.value.kind == ckVector + check r.value.items.len == 3 + + test "distinct": + let r = eval("(distinct [1 2 2 3 3 3])", env) + check r.ok + check r.value.items.len == 3 + + test "empty?": + check eval("(empty? [])", env).value.boolVal == true + check eval("(empty? [1])", env).value.boolVal == false + suite "Eval - Higher-order": setup: let env = newTopLevelEnv() @@ -275,6 +392,18 @@ suite "Eval - String/Primitives": check r.value.kind == ckKeyword check r.value.kwName == "integer" + test "true?": + check eval("(true? true)", env).value.boolVal == true + check eval("(true? false)", env).value.boolVal == false + + test "false?": + check eval("(false? false)", env).value.boolVal == true + check eval("(false? true)", env).value.boolVal == false + + test "instance?": + check eval("(instance? :integer 42)", env).value.boolVal == true + check eval("(instance? :string 42)", env).value.boolVal == false + suite "Eval - Map Operations": setup: let env = newTopLevelEnv() @@ -299,6 +428,11 @@ suite "Eval - Map Operations": check r.ok check r.value.mapKeys.len == 1 + test "merge": + let r = eval("(merge {:a 1} {:b 2})", env) + check r.ok + check r.value.mapKeys.len == 2 + test "keys": let r = eval("(keys {:a 1 :b 2})", env) check r.ok @@ -370,3 +504,79 @@ suite "Eval - Boolean Logic": let r = eval("(cond false 1 true 2 false 3)", env) check r.ok check r.value.intVal == 2 + +suite "Eval - Atoms": + setup: + let env = newTopLevelEnv() + + test "atom and deref": + discard eval("(def a (atom 0))", env) + let r = eval("(deref a)", env) + check r.ok + check r.value.intVal == 0 + + test "swap! updates atom value": + discard eval("(def a (atom 0))", env) + let r = eval("(swap! a inc)", env) + check r.ok + check r.value.intVal == 1 + let r2 = eval("(deref a)", env) + check r2.value.intVal == 1 + + test "swap! with extra args": + discard eval("(def a (atom 10))", env) + let r = eval("(swap! a + 5)", env) + check r.ok + check r.value.intVal == 15 + + test "reset! sets atom value": + discard eval("(def a (atom 0))", env) + let r = eval("(reset! a 42)", env) + check r.ok + check r.value.intVal == 42 + let r2 = eval("(deref a)", env) + check r2.value.intVal == 42 + +suite "Eval - Agents": + setup: + let env = newTopLevelEnv() + + test "agent and deref": + discard eval("(def ag (agent 10))", env) + let r = eval("(deref ag)", env) + check r.ok + check r.value.intVal == 10 + + test "send": + discard eval("(def ag (agent 10))", env) + let r = eval("(send ag inc)", env) + check r.ok + check eval("(deref ag)", env).value.intVal == 11 + + test "await returns nil": + discard eval("(def ag (agent 10))", env) + let r = eval("(await ag)", env) + check r.ok + check r.value.kind == ckNil + +suite "Eval - Channels": + setup: + let env = newTopLevelEnv() + + test "chan create returns string id": + let r = eval("(chan)", env) + check r.ok + check r.value.kind == ckString + + test "chan put and take": + discard eval("(def ch (chan))", env) + let putR = eval("(>! ch 42)", env) + check putR.ok + let takeR = eval("(