Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Phase 5 cont. Adds `select` statement evaluation:
go-select-try-case env COMM →
:not-ready / extended-env / :eval-error
go-select-pick env CASES DEFAULT-OR-NIL →
body-result / blocked-error
go-eval-select-stmt env STMT — public entry
Walks cases in declared order:
* :send case — always ready in v0 (unbounded buffer). Sends value
via go-chan-send! and returns env unchanged.
* :short-decl / :assign case — RHS expected to be unary <- on a
channel. Ready iff go-chan-len > 0; on success, recv-into-var
binds the new value in env.
* Bare recv (:app (:var "<-") [CHAN]) — ready iff len > 0; consumes
the value (discarded).
* :default — deferred until end of walk. Runs if no other case
ready. Absence + no ready case → (:eval-error :select-blocked-
no-default).
New `go-chan-len` accessor on the channel closure-bundle so the
select can peek without consuming.
Subtle bug fix: the :select stmt branch in go-eval-stmt was returning
the old env instead of the env returned by the case body. Assignments
inside select cases (`select { case <-ch: x = 1 ; default: x = 99 }`)
now stick.
Tests (6):
default fires when no case ready
recv case fires when ready
recv-into-var binds the value
send case always ready
picks first ready case (deterministic order in v0)
no default + nothing ready → blocked error
combined with goroutine fan-in
runtime 18/18, total 475/475.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
2.3 KiB
Plaintext
67 lines
2.3 KiB
Plaintext
;; lib/go/sched.sx — Go scheduler primitives: channels + goroutines.
|
|
;;
|
|
;; This is **the independent implementation** referenced by
|
|
;; plans/lib-guest-scheduler.md. The shape that emerges here informs
|
|
;; the eventual sister kit; this file's structures are the Phase 5
|
|
;; "first-consumer" cut.
|
|
;;
|
|
;; v0 concurrency model — IMPORTANT
|
|
;;
|
|
;; SX has no first-class continuations exposed to guest code, so we
|
|
;; can't suspend a goroutine mid-statement. v0 runs `go f()` SYNCHRO-
|
|
;; NOUSLY (it's an immediate call whose return value is dropped). This
|
|
;; preserves the right semantics for patterns where the spawned
|
|
;; goroutine simply pushes to a channel that the main goroutine then
|
|
;; receives — because the spawned goroutine runs to completion first
|
|
;; and leaves the value in the channel buffer.
|
|
;;
|
|
;; True preemption with blocking sends/recvs is a Phase 5b refinement.
|
|
;; The sister-plan diary tracks the design insight (single
|
|
;; sched-spawn primitive, channel-op direction tag) so the eventual
|
|
;; kit doesn't bake in v0's synchronous limitation.
|
|
;;
|
|
;; Channel representation
|
|
;;
|
|
;; (list :go-chan ACCESSORS-FN-LIST)
|
|
;;
|
|
;; ACCESSORS-FN-LIST is a list of closures sharing a mutable buffer
|
|
;; and a closed flag. The closures expose:
|
|
;; index 1: send-fn — (lambda (val) ...)
|
|
;; index 2: recv-fn — (lambda () val-or-:empty)
|
|
;; index 3: closed?-fn — (lambda () bool)
|
|
;; index 4: close!-fn — (lambda () ...)
|
|
;;
|
|
;; Channel identity: distinct calls to go-make-chan produce closures
|
|
;; with distinct identity — `(= ch1 ch2)` is false for distinct
|
|
;; channels, matching Go spec § Channel types.
|
|
|
|
(define
|
|
go-make-chan
|
|
(fn
|
|
()
|
|
(let
|
|
((buf (list)) (closed false))
|
|
(list
|
|
:go-chan (fn (v) (append! buf v) nil)
|
|
(fn
|
|
()
|
|
(cond
|
|
(= (len buf) 0)
|
|
:empty :else
|
|
(let ((v (first buf))) (set! buf (rest buf)) v)))
|
|
(fn () closed)
|
|
(fn () (set! closed true) nil)
|
|
(fn () (len buf))))))
|
|
|
|
(define
|
|
go-chan?
|
|
(fn
|
|
(v)
|
|
(and (list? v) (not (= (len v) 0)) (= (first v) :go-chan))))
|
|
|
|
(define go-chan-send! (fn (ch val) ((nth ch 1) val)))
|
|
(define go-chan-recv! (fn (ch) ((nth ch 2))))
|
|
(define go-chan-closed? (fn (ch) ((nth ch 3))))
|
|
(define go-chan-close! (fn (ch) ((nth ch 4))))
|
|
(define go-chan-len (fn (ch) ((nth ch 5))))
|