Phase 9: Agents (send/await/deref) + core.async channels (chan, >!, <!, close!, go). All phases complete.

This commit is contained in:
2026-05-08 20:28:51 +03:00
parent 1f9d662ed9
commit 30fde68fed
6 changed files with 222 additions and 10 deletions
+86 -1
View File
@@ -1,8 +1,19 @@
# Tree-walking interpreter for fast REPL evaluation
# Handles common cases without spawning nim c
import strutils, sequtils, tables, algorithm, times
import strutils, sequtils, tables, algorithm, times, deques
import types, reader
var agentRegistry* = initTable[string, CljVal]()
var agentCounter*: int64 = 0
type
Channel* = ref object
buf: Deque[CljVal]
capacity: int # 0 = unbuffered
var channelRegistry* = initTable[string, Channel]()
var channelCounter*: int64 = 0
type
EvalError* = object of CatchableError
@@ -1006,12 +1017,86 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
of "deref":
numArgs(1)
if args[0].kind == ckString and agentRegistry.hasKey(args[0].strVal):
return EvalResult(ok: true, value: agentRegistry[args[0].strVal])
return EvalResult(ok: true, value: args[0])
of "atom":
numArgs(1)
return EvalResult(ok: true, value: args[0])
of "agent":
numArgs(1)
agentCounter += 1
let id = "agent_" & $agentCounter
agentRegistry[id] = args[0]
return EvalResult(ok: true, value: cljString(id))
of "send":
atLeast(2)
let agentId = args[0]
if agentId.kind != ckString or not agentRegistry.hasKey(agentId.strVal):
return EvalResult(ok: false, error: "send requires an agent")
let fn = args[1]
let fnArgs = if args.len > 2: args[2..^1] else: @[]
let currentVal = agentRegistry[agentId.strVal]
var callItems = @[fn, currentVal]
callItems.add(fnArgs)
let callRes = evalList(callItems, env)
if not callRes.ok: return callRes
agentRegistry[agentId.strVal] = callRes.value
return EvalResult(ok: true, value: callRes.value)
of "await":
numArgs(1)
return EvalResult(ok: true, value: cljNil())
of "shutdown-agents":
return EvalResult(ok: true, value: cljNil())
of "chan":
channelCounter += 1
let id = "chan_" & $channelCounter
let cap = if args.len > 0 and args[0].kind == ckInt: args[0].intVal.int else: 0
let ch = Channel(buf: initDeque[CljVal](), capacity: cap)
channelRegistry[id] = ch
return EvalResult(ok: true, value: cljString(id))
of "put!", ">!":
numArgs(2)
if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "put!/<! requires a channel")
let ch = channelRegistry[args[0].strVal]
if ch.capacity > 0 and ch.buf.len >= ch.capacity:
return EvalResult(ok: false, error: "Channel buffer full")
ch.buf.addLast(args[1])
return EvalResult(ok: true, value: cljBool(true))
of "take!", "<!":
numArgs(1)
if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "take!/<! requires a channel")
let ch = channelRegistry[args[0].strVal]
if ch.buf.len == 0:
return EvalResult(ok: true, value: cljNil())
return EvalResult(ok: true, value: ch.buf.popFirst())
of "close!":
numArgs(1)
if args[0].kind != ckString or not channelRegistry.hasKey(args[0].strVal):
return EvalResult(ok: false, error: "close! requires a channel")
channelRegistry.del(args[0].strVal)
return EvalResult(ok: true, value: cljNil())
of "go":
# In synchronous interpreter, go just evaluates the body
var lastVal: CljVal = cljNil()
for i in 1..<items.len:
let res = evalAst(items[i], env)
if not res.ok: return res
lastVal = res.value
return EvalResult(ok: true, value: lastVal)
else:
return EvalResult(ok: false, error: "Unknown function: " & name & " (use compile mode for full runtime)")