Commit graph

3 commits

Author SHA1 Message Date
Yogthos
54db79e927 feat: core.async Phase 3 — channel transducers + dropping/sliding buffers
(chan n xform) applies a transducer on the put side: a jolt transducer is a
directly-callable closure, composed over a reducing fn whose step gives each
output value into the channel (honoring its buffer kind). One put may yield zero
or more values; a reduced result (e.g. from take) closes the channel; close!
runs the transducer completion arity to flush stateful remainders. Works with
map/filter/mapcat/take/comp/etc.

Buffers: (buffer n) fixed, (dropping-buffer n) drops new values when full,
(sliding-buffer n) drops the oldest. Implemented via a non-blocking give —
(ev/select [ch v] closed-chan) detects a full buffer without parking.

harness: run-spec flushes per suite. spec: core.async/channel-transducers (5),
core.async/buffers (3). jpm test green.

Note: distinct/dedupe/partition-all/partition-by still lack a 0-coll transducer
arity in core (separate gap), so they can't yet be used as channel xforms.
2026-06-05 15:40:24 -04:00
Yogthos
e8cb4605ac feat: core.async Phase 2 — dynamic var binding conveyance
Jolt's dynamic-var binding stack was a single global array, so concurrent go
blocks interleaved each other's bindings and a go block didn't see the bindings
in effect when it was spawned.

Move the binding stack into Janet's fiber-local dyn (:jolt/binding-stack): each
fiber (go block) lazily gets its own array, so bindings can't interleave. Janet
ev/go fibers inherit the parent's dyn, but go-spawn now snapshots the binding
stack at spawn time and installs a private copy in the new fiber — Clojure
binding conveyance. A go block's own (binding ...) shadows the conveyed frame.

types.janet: cur-binding-stack / snapshot-bindings / install-bindings; var-get/
var-set/push/pop use the fiber-local stack. async.janet: go-spawn conveys.

spec: core.async/binding-conveyance (4 cases — conveyance, isolation, no leak to
root, inner shadowing). jpm test green.
2026-06-05 15:22:38 -04:00
Yogthos
7d1e1f42e1 feat: clojure.core.async on Janet fibers (Phase 1 — API layer)
Janet fibers are stackful coroutines, so a go block is just its body run in a
fiber that parks on channel ops by yielding to the event loop — the interpreter
call stack rides along, no CPS/state-machine transform. So <!/>! work anywhere
(inside try, nested fns, loops), unlike Clojure's go macro.

src/jolt/async.janet implements chan/chan?/close!/<!/>!/<!!/>!!/go/go-loop/
thread/alts!/timeout/put!/take! over ev/ channels and fibers, installed as the
clojure.core.async namespace (pre-populated in init, so require finds it).

A channel is a pair of ev/chans (:ch values + :done close-signal); a take is
(ev/select :ch :done), which drains buffered values before the close signal —
giving Clojure's drain-then-nil semantics without the buffer loss of
ev/chan-close, and with no leaked fibers (close! just closes :done).

Single-threaded cooperative scheduling: <! (park) and <!! (block) coincide.
Dynamic-var conveyance (Phase 2) and channel transducers (Phase 3) are TODO.

spec: test/spec/core-async-spec (16 cases — go/channels, buffering+close,
go-loop pipelines, alts!/timeout, parking inside try/nested-fn). jpm test green.
2026-06-05 15:17:15 -04:00