go: eval.sx + sched.sx — select stmt evaluation + 6 tests [nothing]
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>
This commit is contained in:
2026-05-27 22:03:17 +00:00
parent b693854dc4
commit 4807bc9c58
6 changed files with 183 additions and 7 deletions

View File

@@ -813,12 +813,119 @@
(cond
(go-eval-error? v) v
:else env))
(and (list? stmt) (= (first stmt) :select))
(let ((r (go-eval-select-stmt env stmt)))
(cond
(go-eval-error? r) r
(and (list? r) (= (first r) :return-value)) r
(= r :break) r
(= r :continue) r
;; Otherwise r is the env after the selected body ran;
;; propagate so assignments inside cases stick.
:else r))
:else
(let ((v (go-eval env stmt)))
(cond
(go-eval-error? v) v
:else env)))))
(define
go-select-try-case
;; Returns:
;; :not-ready — case can't proceed (recv on empty channel)
;; env-or-extended-env — case ran; for recv-into-decl/assign, env
;; carries the new binding
;; :eval-error sentinel
(fn (env comm)
(cond
;; Send case (always ready in v0 with unbounded buffer)
(and (list? comm) (= (first comm) :send))
(let ((ch (go-eval env (nth comm 1)))
(v (go-eval env (nth comm 2))))
(cond
(go-eval-error? ch) ch
(go-eval-error? v) v
(not (go-chan? ch)) (list :eval-error :send-not-chan ch)
:else (do (go-chan-send! ch v) env)))
;; Recv-into-var: x := <-ch / x = <-ch
(and (list? comm)
(or (= (first comm) :short-decl) (= (first comm) :assign)))
(let ((lhs-list (nth comm 1)) (exprs (nth comm 2)))
(cond
(not (= (len exprs) 1)) :not-ready
:else
(let ((rhs (first exprs)))
(cond
(not (and (list? rhs) (= (first rhs) :app)
(list? (nth rhs 1)) (= (first (nth rhs 1)) :var)
(= (nth (nth rhs 1) 1) "<-")
(= (len (nth rhs 2)) 1)))
:not-ready
:else
(let ((ch (go-eval env (first (nth rhs 2)))))
(cond
(go-eval-error? ch) ch
(not (go-chan? ch)) (list :eval-error :recv-not-chan ch)
(= (go-chan-len ch) 0) :not-ready
:else
(let ((v (go-chan-recv! ch)))
(cond
(= v :empty) :not-ready
:else
(let ((names (map (fn (lhs)
(cond
(and (list? lhs)
(= (first lhs) :var))
(nth lhs 1)
:else :unknown))
lhs-list)))
(cond
(= (len names) 0) env
:else
(go-env-extend env (first names) v)))))))))))
;; Bare recv: (:app (:var "<-") [CHAN])
(and (list? comm) (= (first comm) :app)
(list? (nth comm 1)) (= (first (nth comm 1)) :var)
(= (nth (nth comm 1) 1) "<-")
(= (len (nth comm 2)) 1))
(let ((ch (go-eval env (first (nth comm 2)))))
(cond
(go-eval-error? ch) ch
(not (go-chan? ch)) (list :eval-error :recv-not-chan ch)
(= (go-chan-len ch) 0) :not-ready
:else (do (go-chan-recv! ch) env)))
:else :not-ready)))
(define
go-select-pick
;; Walk cases in order. First :select-case whose comm-stmt is ready
;; wins. If none ready and a :default was seen, run it. Otherwise
;; :select-blocked-no-default.
(fn (env cases default-case)
(cond
(or (= cases nil) (= (len cases) 0))
(cond
(= default-case nil) (list :eval-error :select-blocked-no-default)
:else (go-eval-block env (nth default-case 1)))
:else
(let ((c (first cases)))
(cond
(and (list? c) (= (first c) :default))
(go-select-pick env (rest cases) c)
(and (list? c) (= (first c) :select-case))
(let ((maybe-env (go-select-try-case env (nth c 1))))
(cond
(= maybe-env :not-ready)
(go-select-pick env (rest cases) default-case)
(go-eval-error? maybe-env) maybe-env
:else (go-eval-block maybe-env (nth c 2))))
:else (go-select-pick env (rest cases) default-case))))))
(define
go-eval-select-stmt
(fn (env stmt)
(go-select-pick env (nth stmt 1) nil)))
(define
go-eval-method-decl
;; (:method-decl RECV NAME PARAMS RESULTS BODY) — register the method