feat: swap! and reset! in interpreter

- atoms now work in REPL interpreter via string registry
- swap! supports extra arguments: (swap! a + 5)
- reset! sets atom value directly
- Updated tests: 3 new atom tests (swap!, reset!, swap! with args)
- 270+ tests pass across all suites
This commit is contained in:
2026-05-08 22:30:35 +03:00
parent 6249d8a251
commit 2b32640eeb
2 changed files with 269 additions and 33 deletions
+58 -32
View File
@@ -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..<args.len:
if args[i].kind == ckInt: result -= args[i].intVal.float64
elif args[i].kind == ckFloat: result -= args[i].floatVal
if args[i].kind == ckInt: sum -= args[i].intVal.float64
elif args[i].kind == ckFloat: sum -= args[i].floatVal
else: return EvalResult(ok: false, error: "- requires numbers")
if result == result.int64.float64 and args[0].kind == ckInt:
if sum == sum.int64.float64 and args[0].kind == ckInt:
var allInt = true
for i in 1..<args.len:
if args[i].kind != ckInt: allInt = false
if allInt: return EvalResult(ok: true, value: cljInt(result.int64))
return EvalResult(ok: true, value: cljFloat(result))
if allInt: return EvalResult(ok: true, value: cljInt(sum.int64))
return EvalResult(ok: true, value: cljFloat(sum))
of "*":
atLeast(1)
@@ -594,11 +594,11 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
return EvalResult(ok: false, error: "cons requires a collection")
of "concat":
var result: seq[CljVal] = @[]
var res: seq[CljVal] = @[]
for a in args:
if a.kind in {ckList, ckVector}:
result.add(a.items)
return EvalResult(ok: true, value: cljList(result))
res.add(a.items)
return EvalResult(ok: true, value: cljList(res))
of "reverse":
numArgs(1)
@@ -629,13 +629,13 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
let fn = args[0]
let coll = args[1]
if coll.kind in {ckList, ckVector}:
var result: seq[CljVal] = @[]
var res: seq[CljVal] = @[]
for item in coll.items:
let callItems = @[fn, item]
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
result.add(callRes.value)
return EvalResult(ok: true, value: cljList(result))
res.add(callRes.value)
return EvalResult(ok: true, value: cljList(res))
return EvalResult(ok: false, error: "map requires a function and collection")
of "filter":
@@ -643,7 +643,7 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
let fn = args[0]
let coll = args[1]
if coll.kind in {ckList, ckVector}:
var result: seq[CljVal] = @[]
var res: seq[CljVal] = @[]
for item in coll.items:
let callItems = @[fn, item]
let callRes = evalList(callItems, env)
@@ -651,8 +651,8 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
let isTruthy = not (callRes.value.kind == ckNil or
(callRes.value.kind == ckBool and not callRes.value.boolVal))
if isTruthy:
result.add(item)
return EvalResult(ok: true, value: cljList(result))
res.add(item)
return EvalResult(ok: true, value: cljList(res))
return EvalResult(ok: false, error: "filter requires a function and collection")
of "reduce":
@@ -668,22 +668,22 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
if coll.kind in {ckList, ckVector} and coll.items.len > 0:
acc = coll.items[0]
# reduce rest
var result = acc
var res = acc
for i in 1..<coll.items.len:
let callItems = @[fn, result, coll.items[i]]
let callItems = @[fn, res, coll.items[i]]
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
result = callRes.value
return EvalResult(ok: true, value: result)
res = callRes.value
return EvalResult(ok: true, value: res)
return EvalResult(ok: true, value: cljNil())
if coll.kind in {ckList, ckVector}:
var result = acc
var res = acc
for item in coll.items:
let callItems = @[fn, result, item]
let callItems = @[fn, res, item]
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
result = callRes.value
return EvalResult(ok: true, value: result)
res = callRes.value
return EvalResult(ok: true, value: res)
return EvalResult(ok: false, error: "reduce requires a function and collection")
of "apply":
@@ -957,17 +957,17 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
of "distinct":
numArgs(1)
if args[0].kind in {ckList, ckVector}:
var result: seq[CljVal] = @[]
var res: seq[CljVal] = @[]
for item in args[0].items:
var isDup = false
for existing in result:
for existing in res:
let eqRes = evalBuiltin("=", @[existing, item])
if eqRes.ok and eqRes.value.kind == ckBool and eqRes.value.boolVal:
isDup = true
break
if not isDup:
result.add(item)
return EvalResult(ok: true, value: cljList(result))
res.add(item)
return EvalResult(ok: true, value: cljList(res))
return EvalResult(ok: false, error: "distinct requires a collection")
of "slurp":
@@ -1036,11 +1036,37 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
numArgs(1)
if args[0].kind == ckString and agentRegistry.hasKey(args[0].strVal):
return EvalResult(ok: true, value: agentRegistry[args[0].strVal])
if args[0].kind == ckString and atomRegistry.hasKey(args[0].strVal):
return EvalResult(ok: true, value: atomRegistry[args[0].strVal])
return EvalResult(ok: true, value: args[0])
of "atom":
numArgs(1)
return EvalResult(ok: true, value: args[0])
atomCounter += 1
let id = "atom_" & $atomCounter
atomRegistry[id] = args[0]
return EvalResult(ok: true, value: cljString(id))
of "reset!":
numArgs(2)
if args[0].kind != ckString or not atomRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "reset! requires an atom")
atomRegistry[args[0].strVal] = args[1]
return EvalResult(ok: true, value: args[1])
of "swap!":
atLeast(2)
if args[0].kind != ckString or not atomRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "swap! requires an atom")
let fn = args[1]
let currentVal = atomRegistry[args[0].strVal]
var callItems = @[fn, currentVal]
if args.len > 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)