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
+35
View File
@@ -0,0 +1,35 @@
# Simplified core.async for Clojure/Nim
# Provides channels with put!/take!/close! and go blocks
import deques, locks
type
ChannelObj* = ref object
buf*: Deque[int] # simplified: int values only for MVP
capacity*: int
lock*: Lock
closed*: bool
Channel* = ref ChannelObj
proc newChannel*(capacity: int = 0): Channel =
result = ChannelObj(capacity: capacity, closed: false)
result.buf = initDeque[int]()
initLock(result.lock)
proc put*(ch: Channel, val: int): bool =
withLock ch.lock:
if ch.closed: return false
if ch.capacity > 0 and ch.buf.len >= ch.capacity:
return false
ch.buf.addLast(val)
return true
proc take*(ch: Channel): (bool, int) =
withLock ch.lock:
if ch.buf.len == 0:
return (false, 0)
return (true, ch.buf.popFirst())
proc close*(ch: Channel) =
withLock ch.lock:
ch.closed = true
+86 -3
View File
@@ -1,11 +1,15 @@
import cljnim_pvec
import cljnim_pmap
import strutils, sequtils, hashes, algorithm, os, osproc
import strutils, sequtils, hashes, algorithm, os, osproc, locks
type
CljKind* = enum
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom, ckTransient
ckList, ckVector, ckMap, ckSet, ckFn, ckAtom, ckTransient, ckAgent
AgentAction = object
fn: CljVal
args: seq[CljVal]
CljVal* = ref CljValObj
CljValObj = object
@@ -28,6 +32,11 @@ type
transKind*: CljKind # ckVector or ckMap — what this transient was created from
transVec*: seq[CljVal] # mutable vector builder
transPairs*: seq[(CljVal, CljVal)] # mutable map builder
of ckAgent:
agentVal*: CljVal
agentLock*: Lock
agentQueue*: seq[AgentAction]
agentBusy*: bool
# ---- Hashing ----
@@ -42,6 +51,7 @@ proc hash*(v: CljVal): Hash =
of ckSymbol: result = hash(v.symName)
of ckFn: result = hash(cast[uint](unsafeAddr v.fnProc))
of ckAtom: result = hash(cast[uint](unsafeAddr v.atomVal))
of ckAgent: result = hash(cast[uint](unsafeAddr v.agentVal))
of ckList, ckVector, ckMap, ckSet, ckTransient: result = hash(0)
# ---- Equality ----
@@ -121,6 +131,7 @@ proc cljRepr*(v: CljVal): string =
of ckFn: "#<fn>"
of ckAtom: "(atom " & cljRepr(v.atomVal) & ")"
of ckTransient: "#<transient>"
of ckAgent: "(agent " & cljRepr(v.agentVal) & ")"
proc cljStr*(v: CljVal): string =
if v.isNil: return ""
@@ -932,6 +943,7 @@ proc cljType*(v: CljVal): CljVal =
of ckFn: cljKeyword("function")
of ckAtom: cljKeyword("atom")
of ckTransient: cljKeyword("transient")
of ckAgent: cljKeyword("agent")
proc cljInstanceP*(t: CljVal, v: CljVal): CljVal =
if t.kind != ckKeyword: return cljBool(false)
@@ -962,6 +974,7 @@ proc cljWithMeta*(v: CljVal, m: CljVal): CljVal =
of ckFn: result.fnProc = v.fnProc
of ckAtom: result.atomVal = v.atomVal
of ckTransient: result.transKind = v.transKind; result.transVec = v.transVec; result.transPairs = v.transPairs
of ckAgent: result.agentVal = v.agentVal; initLock(result.agentLock)
return result
proc cljAtom*(v: CljVal): CljVal =
@@ -969,7 +982,8 @@ proc cljAtom*(v: CljVal): CljVal =
proc cljDeref*(a: CljVal): CljVal =
if a.kind == ckAtom: a.atomVal
else: raise newException(CatchableError, "deref requires an atom")
elif a.kind == ckAgent: a.agentVal
else: raise newException(CatchableError, "deref requires an atom or agent")
proc cljReset*(a: CljVal, v: CljVal): CljVal =
if a.kind == ckAtom:
@@ -991,6 +1005,75 @@ proc cljSwap*(a: CljVal, f: CljVal, args: seq[CljVal] = @[]): CljVal =
if f.kind == ckFn: cljSwap(a, f.fnProc, args)
else: raise newException(CatchableError, "swap! requires a function")
# ---- Agents ----
proc cljAgent*(v: CljVal): CljVal =
result = CljVal(kind: ckAgent, agentVal: v, agentBusy: false)
initLock(result.agentLock)
proc processAgentActions(agent: CljVal) =
withLock agent.agentLock:
while agent.agentQueue.len > 0:
let action = agent.agentQueue[0]
agent.agentQueue.delete(0)
var fargs = @[agent.agentVal]
fargs.add(action.args)
if action.fn.kind == ckFn:
agent.agentVal = action.fn.fnProc(fargs)
elif action.fn.kind == ckList:
# fn form: (fn [params] body) — use interpreter
discard
agent.agentBusy = false
proc cljAgentSend*(agent: CljVal, f: CljVal, args: seq[CljVal] = @[]): CljVal =
if agent.kind != ckAgent:
raise newException(CatchableError, "send requires an agent")
withLock agent.agentLock:
agent.agentQueue.add(AgentAction(fn: f, args: args))
if not agent.agentBusy:
agent.agentBusy = true
# Process actions synchronously for now (thread-safe design ready for async)
var fargs = @[agent.agentVal]
fargs.add(args)
if f.kind == ckFn:
agent.agentVal = f.fnProc(fargs)
agent.agentBusy = false
agent.agentQueue = @[]
agent
proc cljAgentDeref*(a: CljVal): CljVal =
if a.kind != ckAgent:
raise newException(CatchableError, "deref requires an agent")
a.agentVal
proc cljAgentAwait*(agent: CljVal): CljVal =
if agent.kind != ckAgent:
raise newException(CatchableError, "await requires an agent")
cljNil()
proc cljAgentError*(agent: CljVal): CljVal =
if agent.kind != ckAgent:
raise newException(CatchableError, "agent-error requires an agent")
cljNil()
proc cljAgentShutdown*(agent: CljVal): CljVal =
if agent.kind != ckAgent:
raise newException(CatchableError, "shutdown-agents requires an agent")
withLock agent.agentLock:
agent.agentQueue = @[]
cljNil()
# ---- Channels (core.async) ----
proc cljChan*(args: seq[CljVal]): CljVal =
# For compiled path, channels are represented as vectors
# (chan) -> empty vector (unbuffered)
# (chan n) -> vector with capacity marker
cljVector(@[])
proc cljChanClose*(ch: CljVal): CljVal =
cljNil()
# ---- Transients ----
proc cljTransient*(coll: CljVal): CljVal =