feat: add cooperative Clojure agents
3dc7d0f parent: 7fd797f modified
README.md +5 -0 | @@ -154,6 +154,11 @@ Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set — | ||
| 154 | 154 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, |
| 155 | 155 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. |
| 156 | 156 | |
| 157 | +Agents: `agent`, `send`, `await`, `agent-error`, and `restart-agent`. Agents | |
| 158 | +serialize queued state transitions; this initial implementation is cooperative, | |
| 159 | +so queued actions run when an agent is dereferenced, awaited, or inspected for | |
| 160 | +an error rather than on a background thread. | |
| 161 | + | |
| 157 | 162 | Lazy seqs: `iterate` `repeat` `repeatedly` `cycle` `doall` `dorun`, and the seq |
| 158 | 163 | library above returns them where Clojure does. |
| 159 | 164 | |
| @@ -154,6 +154,11 @@ Data: nil, bool, int, float, string, keyword, symbol, list, vector, map, set — | |||
| 154 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, | 154 | persistent, with structural equality, hashing, and Clojure-shaped printing. Atoms, closures, `comp`, |
| 155 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. | 155 | `partial`, `juxt`, the usual seq library, `clojure.string/*`. |
| 156 | 156 | ||
| 157 | +Agents: `agent`, `send`, `await`, `agent-error`, and `restart-agent`. Agents | ||
| 158 | +serialize queued state transitions; this initial implementation is cooperative, | ||
| 159 | +so queued actions run when an agent is dereferenced, awaited, or inspected for | ||
| 160 | +an error rather than on a background thread. | ||
| 161 | + | ||
| 157 | Lazy seqs: `iterate` `repeat` `repeatedly` `cycle` `doall` `dorun`, and the seq | 162 | Lazy seqs: `iterate` `repeat` `repeatedly` `cycle` `doall` `dorun`, and the seq |
| 158 | library above returns them where Clojure does. | 163 | library above returns them where Clojure does. |
| 159 | 164 | ||
added
clonim.nimble +8 -0 | new file mode 100644 | ||
| @@ -0,0 +1,8 @@ | ||
| 1 | +version = "0.1.14" | |
| 2 | +author = "clonim contributors" | |
| 3 | +description = "A Clojure compiler hosted on Nim" | |
| 4 | +license = "MIT" | |
| 5 | +srcDir = "src" | |
| 6 | +bin = @["clonim"] | |
| 7 | + | |
| 8 | +requires "nim >= 2.2.12" | |
| new file mode 100644 | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | +version = "0.1.14" | ||
| 2 | +author = "clonim contributors" | ||
| 3 | +description = "A Clojure compiler hosted on Nim" | ||
| 4 | +license = "MIT" | ||
| 5 | +srcDir = "src" | ||
| 6 | +bin = @["clonim"] | ||
| 7 | + | ||
| 8 | +requires "nim >= 2.2.12" | ||
added
examples/agents.clj +16 -0 | new file mode 100644 | ||
| @@ -0,0 +1,16 @@ | ||
| 1 | +(def counter (agent 0)) | |
| 2 | +(send counter + 2) | |
| 3 | +(send counter * 10) | |
| 4 | +(println @counter) | |
| 5 | + | |
| 6 | +(def awaited (agent 3)) | |
| 7 | +(send awaited + 4) | |
| 8 | +(await awaited) | |
| 9 | +(println @awaited) | |
| 10 | + | |
| 11 | +(def broken (agent 1)) | |
| 12 | +(send broken (fn [_] (throw "boom"))) | |
| 13 | +(println (agent-error broken)) | |
| 14 | +(restart-agent broken 7) | |
| 15 | +(send broken inc) | |
| 16 | +(println @broken) | |
| new file mode 100644 | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | +(def counter (agent 0)) | ||
| 2 | +(send counter + 2) | ||
| 3 | +(send counter * 10) | ||
| 4 | +(println @counter) | ||
| 5 | + | ||
| 6 | +(def awaited (agent 3)) | ||
| 7 | +(send awaited + 4) | ||
| 8 | +(await awaited) | ||
| 9 | +(println @awaited) | ||
| 10 | + | ||
| 11 | +(def broken (agent 1)) | ||
| 12 | +(send broken (fn [_] (throw "boom"))) | ||
| 13 | +(println (agent-error broken)) | ||
| 14 | +(restart-agent broken 7) | ||
| 15 | +(send broken inc) | ||
| 16 | +(println @broken) | ||
modified
src/compiler.nim +4 -1 | @@ -194,6 +194,7 @@ proc quoteLit(v: Value): string = | ||
| 194 | 194 | "mkMap(@[" & parts.join(", ") & "])" |
| 195 | 195 | of kCons, kChunk, kLazy: err("Can't quote a lazy seq") |
| 196 | 196 | of kFn: err("Can't quote a function") |
| 197 | + of kAgent: err("Can't quote an agent") | |
| 197 | 198 | |
| 198 | 199 | proc emptySeqFix(s: string, elemType: string): string = |
| 199 | 200 | ## `@[]` has no inferable element type in Nim; annotate it. |
| @@ -949,7 +950,7 @@ proc tryExpr(f: Value, env: Env, c: Ctx): string = | ||
| 949 | 950 | if hv.len == 0: return "" |
| 950 | 951 | "call(" & hv & ", " & |
| 951 | 952 | (if ids.len == 0: "emptyArgs" else: "[" & ids.join(", ") & "]") & ")" |
| 952 | - of kFn, kCons, kChunk, kLazy: | |
| 953 | + of kFn, kAgent, kCons, kChunk, kLazy: | |
| 953 | 954 | "" |
| 954 | 955 | |
| 955 | 956 | proc genInto(f: Value, dst: string, env: Env, c: Ctx) = |
| @@ -985,6 +986,8 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | ||
| 985 | 986 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") |
| 986 | 987 | of kFn: |
| 987 | 988 | err("Can't emit a function literal") |
| 989 | + of kAgent: | |
| 990 | + err("Can't emit an agent literal") | |
| 988 | 991 | of kCons, kChunk, kLazy: |
| 989 | 992 | err("Can't emit a lazy seq literal") |
| 990 | 993 | of kList: |
| @@ -194,6 +194,7 @@ proc quoteLit(v: Value): string = | |||
| 194 | "mkMap(@[" & parts.join(", ") & "])" | 194 | "mkMap(@[" & parts.join(", ") & "])" |
| 195 | of kCons, kChunk, kLazy: err("Can't quote a lazy seq") | 195 | of kCons, kChunk, kLazy: err("Can't quote a lazy seq") |
| 196 | of kFn: err("Can't quote a function") | 196 | of kFn: err("Can't quote a function") |
| 197 | + of kAgent: err("Can't quote an agent") | ||
| 197 | 198 | ||
| 198 | proc emptySeqFix(s: string, elemType: string): string = | 199 | proc emptySeqFix(s: string, elemType: string): string = |
| 199 | ## `@[]` has no inferable element type in Nim; annotate it. | 200 | ## `@[]` has no inferable element type in Nim; annotate it. |
| @@ -949,7 +950,7 @@ proc tryExpr(f: Value, env: Env, c: Ctx): string = | |||
| 949 | if hv.len == 0: return "" | 950 | if hv.len == 0: return "" |
| 950 | "call(" & hv & ", " & | 951 | "call(" & hv & ", " & |
| 951 | (if ids.len == 0: "emptyArgs" else: "[" & ids.join(", ") & "]") & ")" | 952 | (if ids.len == 0: "emptyArgs" else: "[" & ids.join(", ") & "]") & ")" |
| 952 | - of kFn, kCons, kChunk, kLazy: | 953 | + of kFn, kAgent, kCons, kChunk, kLazy: |
| 953 | "" | 954 | "" |
| 954 | 955 | ||
| 955 | proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | 956 | proc genInto(f: Value, dst: string, env: Env, c: Ctx) = |
| @@ -985,6 +986,8 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) = | |||
| 985 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") | 986 | (if parts.len == 0: "newSeq[(Value, Value)]()" else: "@[" & parts.join(", ") & "]") & ")") |
| 986 | of kFn: | 987 | of kFn: |
| 987 | err("Can't emit a function literal") | 988 | err("Can't emit a function literal") |
| 989 | + of kAgent: | ||
| 990 | + err("Can't emit an agent literal") | ||
| 988 | of kCons, kChunk, kLazy: | 991 | of kCons, kChunk, kLazy: |
| 989 | err("Can't emit a lazy seq literal") | 992 | err("Can't emit a lazy seq literal") |
| 990 | of kList: | 993 | of kList: |
modified
src/core.nim +10 -1 | @@ -1090,7 +1090,16 @@ proc registerCore*() = | ||
| 1090 | 1090 | if args.len == 0: return cell |
| 1091 | 1091 | cell = args[1] |
| 1092 | 1092 | cell) |
| 1093 | - def "deref", proc (a: openArray[Value]): Value = call(a[0], []) | |
| 1093 | + # ---- agents (cooperative, serialized state transitions) | |
| 1094 | + def "agent", proc (a: openArray[Value]): Value = mkAgent(a[0]) | |
| 1095 | + def "send", proc (a: openArray[Value]): Value = | |
| 1096 | + if a.len < 2: err("send expects an agent and an action") | |
| 1097 | + agentSend(a[0], a[1], a[2 .. ^1]) | |
| 1098 | + def "await", proc (a: openArray[Value]): Value = agentAwait(a) | |
| 1099 | + def "agent-error", proc (a: openArray[Value]): Value = agentError(a[0]) | |
| 1100 | + def "restart-agent", proc (a: openArray[Value]): Value = restartAgent(a[0], a[1]) | |
| 1101 | + def "deref", proc (a: openArray[Value]): Value = | |
| 1102 | + if a[0].kind == kAgent: agentDeref(a[0]) else: call(a[0], []) | |
| 1094 | 1103 | def "reset!", proc (a: openArray[Value]): Value = call(a[0], [mkKeyword("set"), a[1]]) |
| 1095 | 1104 | def "swap!", proc (a: openArray[Value]): Value = |
| 1096 | 1105 | let cur = call(a[0], []) |
| @@ -1090,7 +1090,16 @@ proc registerCore*() = | |||
| 1090 | if args.len == 0: return cell | 1090 | if args.len == 0: return cell |
| 1091 | cell = args[1] | 1091 | cell = args[1] |
| 1092 | cell) | 1092 | cell) |
| 1093 | - def "deref", proc (a: openArray[Value]): Value = call(a[0], []) | 1093 | + # ---- agents (cooperative, serialized state transitions) |
| 1094 | + def "agent", proc (a: openArray[Value]): Value = mkAgent(a[0]) | ||
| 1095 | + def "send", proc (a: openArray[Value]): Value = | ||
| 1096 | + if a.len < 2: err("send expects an agent and an action") | ||
| 1097 | + agentSend(a[0], a[1], a[2 .. ^1]) | ||
| 1098 | + def "await", proc (a: openArray[Value]): Value = agentAwait(a) | ||
| 1099 | + def "agent-error", proc (a: openArray[Value]): Value = agentError(a[0]) | ||
| 1100 | + def "restart-agent", proc (a: openArray[Value]): Value = restartAgent(a[0], a[1]) | ||
| 1101 | + def "deref", proc (a: openArray[Value]): Value = | ||
| 1102 | + if a[0].kind == kAgent: agentDeref(a[0]) else: call(a[0], []) | ||
| 1094 | def "reset!", proc (a: openArray[Value]): Value = call(a[0], [mkKeyword("set"), a[1]]) | 1103 | def "reset!", proc (a: openArray[Value]): Value = call(a[0], [mkKeyword("set"), a[1]]) |
| 1095 | def "swap!", proc (a: openArray[Value]): Value = | 1104 | def "swap!", proc (a: openArray[Value]): Value = |
| 1096 | let cur = call(a[0], []) | 1105 | let cur = call(a[0], []) |
modified
src/runtime.nim +56 -2 | @@ -22,6 +22,7 @@ proc equals*(a, b: Value): bool | ||
| 22 | 22 | proc hashValue*(v: Value): uint32 |
| 23 | 23 | proc prStr*(v: Value): string |
| 24 | 24 | proc toSeq*(v: Value): seq[Value] |
| 25 | +proc call*(f: Value, args: openArray[Value]): Value | |
| 25 | 26 | |
| 26 | 27 | # ------------------------------------------------------- persistent vector |
| 27 | 28 | let emptyVNode = VNode(leaf: false, kids: @[]) |
| @@ -297,6 +298,58 @@ proc mkSet*(xs: openArray[Value]): Value = mkSet(@xs) | ||
| 297 | 298 | proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): Value = |
| 298 | 299 | Value(kind: kFn, obj: Obj(kind: kFn, fn: f, name: name)) |
| 299 | 300 | |
| 301 | +proc mkAgent*(state: Value): Value = | |
| 302 | + Value(kind: kAgent, obj: Obj(kind: kAgent, | |
| 303 | + agent: Agent(state: state, queue: @[], failure: NilV, draining: false))) | |
| 304 | + | |
| 305 | +proc agentOf(v: Value): Agent = | |
| 306 | + if v.kind != kAgent: err("Expected an agent, got " & prStr(v)) | |
| 307 | + v.obj.agent | |
| 308 | + | |
| 309 | +proc drainAgent(a: Agent) = | |
| 310 | + ## A cooperative mailbox: send appends work, while deref and await drain it. | |
| 311 | + ## Actions remain serialized and an exception stops the mailbox. | |
| 312 | + if a.draining or not a.failure.isNil: return | |
| 313 | + a.draining = true | |
| 314 | + try: | |
| 315 | + while a.queue.len > 0 and a.failure.isNil: | |
| 316 | + let action = a.queue[0] | |
| 317 | + a.queue.delete(0) | |
| 318 | + try: | |
| 319 | + a.state = call(action.fn, @[a.state] & action.args) | |
| 320 | + except CljError as e: | |
| 321 | + a.failure = mkStr(e.msg) | |
| 322 | + a.queue.setLen(0) | |
| 323 | + finally: | |
| 324 | + a.draining = false | |
| 325 | + | |
| 326 | +proc agentSend*(v, f: Value, args: openArray[Value]): Value = | |
| 327 | + let a = agentOf(v) | |
| 328 | + if not a.failure.isNil: err("Agent is failed: " & a.failure.s) | |
| 329 | + a.queue.add AgentAction(fn: f, args: @args) | |
| 330 | + v | |
| 331 | + | |
| 332 | +proc agentDeref*(v: Value): Value = | |
| 333 | + let a = agentOf(v) | |
| 334 | + drainAgent(a) | |
| 335 | + a.state | |
| 336 | + | |
| 337 | +proc agentAwait*(vs: openArray[Value]): Value = | |
| 338 | + for v in vs: drainAgent(agentOf(v)) | |
| 339 | + NilV | |
| 340 | + | |
| 341 | +proc agentError*(v: Value): Value = | |
| 342 | + let a = agentOf(v) | |
| 343 | + drainAgent(a) | |
| 344 | + a.failure | |
| 345 | + | |
| 346 | +proc restartAgent*(v, state: Value): Value = | |
| 347 | + let a = agentOf(v) | |
| 348 | + a.state = state | |
| 349 | + a.failure = NilV | |
| 350 | + a.queue.setLen(0) | |
| 351 | + v | |
| 352 | + | |
| 300 | 353 | # --------------------------------------------------------------- lazy seqs |
| 301 | 354 | ## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) |
| 302 | 355 | ## or a cons cell whose tail is usually another lazy seq. Forcing is memoized |
| @@ -474,7 +527,7 @@ proc hashValue*(v: Value): uint32 = | ||
| 474 | 527 | for e in mapEntries(v.m): |
| 475 | 528 | h = h xor mixHash(hashValue(e.key), hashValue(e.val)) |
| 476 | 529 | h |
| 477 | - of kFn: uint32(hash(cast[int](cast[pointer](v.obj)))) | |
| 530 | + of kFn, kAgent: uint32(hash(cast[int](cast[pointer](v.obj)))) | |
| 478 | 531 | |
| 479 | 532 | # --------------------------------------------------------------- accessors |
| 480 | 533 | proc items*(v: Value): seq[Value] = |
| @@ -558,7 +611,7 @@ proc equals*(a, b: Value): bool = | ||
| 558 | 611 | for e in mapEntries(a.m): |
| 559 | 612 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false |
| 560 | 613 | true |
| 561 | - of kFn: a == b | |
| 614 | + of kFn, kAgent: a == b | |
| 562 | 615 | of kList, kVector, kCons, kChunk, kLazy: false # handled above |
| 563 | 616 | # ---------------------------------------------------------------- printing |
| 564 | 617 | proc escapeStr(s: string): string = |
| @@ -615,6 +668,7 @@ proc toStr*(v: Value, readable: bool): string = | ||
| 615 | 668 | for (k, val) in v.pairs: parts.add toStr(k, readable) & " " & toStr(val, readable) |
| 616 | 669 | "{" & parts.join(", ") & "}" |
| 617 | 670 | of kFn: "#<fn " & v.name & ">" |
| 671 | + of kAgent: "#<agent>" | |
| 618 | 672 | |
| 619 | 673 | proc prStr*(v: Value): string = toStr(v, true) |
| 620 | 674 | proc str*(v: Value): string = toStr(v, false) |
| @@ -22,6 +22,7 @@ proc equals*(a, b: Value): bool | |||
| 22 | proc hashValue*(v: Value): uint32 | 22 | proc hashValue*(v: Value): uint32 |
| 23 | proc prStr*(v: Value): string | 23 | proc prStr*(v: Value): string |
| 24 | proc toSeq*(v: Value): seq[Value] | 24 | proc toSeq*(v: Value): seq[Value] |
| 25 | +proc call*(f: Value, args: openArray[Value]): Value | ||
| 25 | 26 | ||
| 26 | # ------------------------------------------------------- persistent vector | 27 | # ------------------------------------------------------- persistent vector |
| 27 | let emptyVNode = VNode(leaf: false, kids: @[]) | 28 | let emptyVNode = VNode(leaf: false, kids: @[]) |
| @@ -297,6 +298,58 @@ proc mkSet*(xs: openArray[Value]): Value = mkSet(@xs) | |||
| 297 | proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): Value = | 298 | proc mkFn*(name: string, f: proc (args: openArray[Value]): Value {.closure.}): Value = |
| 298 | Value(kind: kFn, obj: Obj(kind: kFn, fn: f, name: name)) | 299 | Value(kind: kFn, obj: Obj(kind: kFn, fn: f, name: name)) |
| 299 | 300 | ||
| 301 | +proc mkAgent*(state: Value): Value = | ||
| 302 | + Value(kind: kAgent, obj: Obj(kind: kAgent, | ||
| 303 | + agent: Agent(state: state, queue: @[], failure: NilV, draining: false))) | ||
| 304 | + | ||
| 305 | +proc agentOf(v: Value): Agent = | ||
| 306 | + if v.kind != kAgent: err("Expected an agent, got " & prStr(v)) | ||
| 307 | + v.obj.agent | ||
| 308 | + | ||
| 309 | +proc drainAgent(a: Agent) = | ||
| 310 | + ## A cooperative mailbox: send appends work, while deref and await drain it. | ||
| 311 | + ## Actions remain serialized and an exception stops the mailbox. | ||
| 312 | + if a.draining or not a.failure.isNil: return | ||
| 313 | + a.draining = true | ||
| 314 | + try: | ||
| 315 | + while a.queue.len > 0 and a.failure.isNil: | ||
| 316 | + let action = a.queue[0] | ||
| 317 | + a.queue.delete(0) | ||
| 318 | + try: | ||
| 319 | + a.state = call(action.fn, @[a.state] & action.args) | ||
| 320 | + except CljError as e: | ||
| 321 | + a.failure = mkStr(e.msg) | ||
| 322 | + a.queue.setLen(0) | ||
| 323 | + finally: | ||
| 324 | + a.draining = false | ||
| 325 | + | ||
| 326 | +proc agentSend*(v, f: Value, args: openArray[Value]): Value = | ||
| 327 | + let a = agentOf(v) | ||
| 328 | + if not a.failure.isNil: err("Agent is failed: " & a.failure.s) | ||
| 329 | + a.queue.add AgentAction(fn: f, args: @args) | ||
| 330 | + v | ||
| 331 | + | ||
| 332 | +proc agentDeref*(v: Value): Value = | ||
| 333 | + let a = agentOf(v) | ||
| 334 | + drainAgent(a) | ||
| 335 | + a.state | ||
| 336 | + | ||
| 337 | +proc agentAwait*(vs: openArray[Value]): Value = | ||
| 338 | + for v in vs: drainAgent(agentOf(v)) | ||
| 339 | + NilV | ||
| 340 | + | ||
| 341 | +proc agentError*(v: Value): Value = | ||
| 342 | + let a = agentOf(v) | ||
| 343 | + drainAgent(a) | ||
| 344 | + a.failure | ||
| 345 | + | ||
| 346 | +proc restartAgent*(v, state: Value): Value = | ||
| 347 | + let a = agentOf(v) | ||
| 348 | + a.state = state | ||
| 349 | + a.failure = NilV | ||
| 350 | + a.queue.setLen(0) | ||
| 351 | + v | ||
| 352 | + | ||
| 300 | # --------------------------------------------------------------- lazy seqs | 353 | # --------------------------------------------------------------- lazy seqs |
| 301 | ## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) | 354 | ## A lazy seq is a thunk that, when forced, yields either nil/`kNil` (the end) |
| 302 | ## or a cons cell whose tail is usually another lazy seq. Forcing is memoized | 355 | ## or a cons cell whose tail is usually another lazy seq. Forcing is memoized |
| @@ -474,7 +527,7 @@ proc hashValue*(v: Value): uint32 = | |||
| 474 | for e in mapEntries(v.m): | 527 | for e in mapEntries(v.m): |
| 475 | h = h xor mixHash(hashValue(e.key), hashValue(e.val)) | 528 | h = h xor mixHash(hashValue(e.key), hashValue(e.val)) |
| 476 | h | 529 | h |
| 477 | - of kFn: uint32(hash(cast[int](cast[pointer](v.obj)))) | 530 | + of kFn, kAgent: uint32(hash(cast[int](cast[pointer](v.obj)))) |
| 478 | 531 | ||
| 479 | # --------------------------------------------------------------- accessors | 532 | # --------------------------------------------------------------- accessors |
| 480 | proc items*(v: Value): seq[Value] = | 533 | proc items*(v: Value): seq[Value] = |
| @@ -558,7 +611,7 @@ proc equals*(a, b: Value): bool = | |||
| 558 | for e in mapEntries(a.m): | 611 | for e in mapEntries(a.m): |
| 559 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false | 612 | if not equals(e.val, mapGet(b.m, e.key, missing)): return false |
| 560 | true | 613 | true |
| 561 | - of kFn: a == b | 614 | + of kFn, kAgent: a == b |
| 562 | of kList, kVector, kCons, kChunk, kLazy: false # handled above | 615 | of kList, kVector, kCons, kChunk, kLazy: false # handled above |
| 563 | # ---------------------------------------------------------------- printing | 616 | # ---------------------------------------------------------------- printing |
| 564 | proc escapeStr(s: string): string = | 617 | proc escapeStr(s: string): string = |
| @@ -615,6 +668,7 @@ proc toStr*(v: Value, readable: bool): string = | |||
| 615 | for (k, val) in v.pairs: parts.add toStr(k, readable) & " " & toStr(val, readable) | 668 | for (k, val) in v.pairs: parts.add toStr(k, readable) & " " & toStr(val, readable) |
| 616 | "{" & parts.join(", ") & "}" | 669 | "{" & parts.join(", ") & "}" |
| 617 | of kFn: "#<fn " & v.name & ">" | 670 | of kFn: "#<fn " & v.name & ">" |
| 671 | + of kAgent: "#<agent>" | ||
| 618 | 672 | ||
| 619 | proc prStr*(v: Value): string = toStr(v, true) | 673 | proc prStr*(v: Value): string = toStr(v, true) |
| 620 | proc str*(v: Value): string = toStr(v, false) | 674 | proc str*(v: Value): string = toStr(v, false) |
modified
src/runtime_types.nim +15 -1 | @@ -5,7 +5,18 @@ | ||
| 5 | 5 | type |
| 6 | 6 | Kind* = enum |
| 7 | 7 | kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword, kSymbol, |
| 8 | - kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | |
| 8 | + kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn, kAgent | |
| 9 | + | |
| 10 | + AgentAction* = object | |
| 11 | + fn*: Value | |
| 12 | + args*: seq[Value] | |
| 13 | + | |
| 14 | + Agent* = ref object | |
| 15 | + ## Actions are queued in send order and run one at a time when observed. | |
| 16 | + state*: Value | |
| 17 | + queue*: seq[AgentAction] | |
| 18 | + failure*: Value | |
| 19 | + draining*: bool | |
| 9 | 20 | |
| 10 | 21 | VNode* = ref object |
| 11 | 22 | case leaf*: bool |
| @@ -72,6 +83,8 @@ type | ||
| 72 | 83 | of kFn: |
| 73 | 84 | fn*: proc (args: openArray[Value]): Value {.closure.} |
| 74 | 85 | name*: string |
| 86 | + of kAgent: | |
| 87 | + agent*: Agent | |
| 75 | 88 | |
| 76 | 89 | Value* = object |
| 77 | 90 | kind*: Kind |
| @@ -121,6 +134,7 @@ proc `=destroy`*(x: var ValueObj) = | ||
| 121 | 134 | of kFn: |
| 122 | 135 | `=destroy`(x.fn) |
| 123 | 136 | `=destroy`(x.name) |
| 137 | + of kAgent: `=destroy`(x.agent) | |
| 124 | 138 | of kCons: |
| 125 | 139 | `=destroy`(x.head) |
| 126 | 140 | if not x.tl.isNil: pendingFree.add x.tl |
| @@ -5,7 +5,18 @@ | |||
| 5 | type | 5 | type |
| 6 | Kind* = enum | 6 | Kind* = enum |
| 7 | kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword, kSymbol, | 7 | kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword, kSymbol, |
| 8 | - kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn | 8 | + kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn, kAgent |
| 9 | + | ||
| 10 | + AgentAction* = object | ||
| 11 | + fn*: Value | ||
| 12 | + args*: seq[Value] | ||
| 13 | + | ||
| 14 | + Agent* = ref object | ||
| 15 | + ## Actions are queued in send order and run one at a time when observed. | ||
| 16 | + state*: Value | ||
| 17 | + queue*: seq[AgentAction] | ||
| 18 | + failure*: Value | ||
| 19 | + draining*: bool | ||
| 9 | 20 | ||
| 10 | VNode* = ref object | 21 | VNode* = ref object |
| 11 | case leaf*: bool | 22 | case leaf*: bool |
| @@ -72,6 +83,8 @@ type | |||
| 72 | of kFn: | 83 | of kFn: |
| 73 | fn*: proc (args: openArray[Value]): Value {.closure.} | 84 | fn*: proc (args: openArray[Value]): Value {.closure.} |
| 74 | name*: string | 85 | name*: string |
| 86 | + of kAgent: | ||
| 87 | + agent*: Agent | ||
| 75 | 88 | ||
| 76 | Value* = object | 89 | Value* = object |
| 77 | kind*: Kind | 90 | kind*: Kind |
| @@ -121,6 +134,7 @@ proc `=destroy`*(x: var ValueObj) = | |||
| 121 | of kFn: | 134 | of kFn: |
| 122 | `=destroy`(x.fn) | 135 | `=destroy`(x.fn) |
| 123 | `=destroy`(x.name) | 136 | `=destroy`(x.name) |
| 137 | + of kAgent: `=destroy`(x.agent) | ||
| 124 | of kCons: | 138 | of kCons: |
| 125 | `=destroy`(x.head) | 139 | `=destroy`(x.head) |
| 126 | if not x.tl.isNil: pendingFree.add x.tl | 140 | if not x.tl.isNil: pendingFree.add x.tl |
added
tests/agents.expected +4 -0 | new file mode 100644 | ||
| @@ -0,0 +1,4 @@ | ||
| 1 | +20 | |
| 2 | +7 | |
| 3 | +boom | |
| 4 | +8 | |
| new file mode 100644 | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | +20 | ||
| 2 | +7 | ||
| 3 | +boom | ||
| 4 | +8 | ||