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
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:
107
lib/go/eval.sx
107
lib/go/eval.sx
@@ -813,12 +813,119 @@
|
|||||||
(cond
|
(cond
|
||||||
(go-eval-error? v) v
|
(go-eval-error? v) v
|
||||||
:else env))
|
: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
|
:else
|
||||||
(let ((v (go-eval env stmt)))
|
(let ((v (go-eval env stmt)))
|
||||||
(cond
|
(cond
|
||||||
(go-eval-error? v) v
|
(go-eval-error? v) v
|
||||||
:else env)))))
|
: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
|
(define
|
||||||
go-eval-method-decl
|
go-eval-method-decl
|
||||||
;; (:method-decl RECV NAME PARAMS RESULTS BODY) — register the method
|
;; (:method-decl RECV NAME PARAMS RESULTS BODY) — register the method
|
||||||
|
|||||||
@@ -50,7 +50,8 @@
|
|||||||
:empty :else
|
:empty :else
|
||||||
(let ((v (first buf))) (set! buf (rest buf)) v)))
|
(let ((v (first buf))) (set! buf (rest buf)) v)))
|
||||||
(fn () closed)
|
(fn () closed)
|
||||||
(fn () (set! closed true) nil)))))
|
(fn () (set! closed true) nil)
|
||||||
|
(fn () (len buf))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
go-chan?
|
go-chan?
|
||||||
@@ -62,3 +63,4 @@
|
|||||||
(define go-chan-recv! (fn (ch) ((nth ch 2))))
|
(define go-chan-recv! (fn (ch) ((nth ch 2))))
|
||||||
(define go-chan-closed? (fn (ch) ((nth ch 3))))
|
(define go-chan-closed? (fn (ch) ((nth ch 3))))
|
||||||
(define go-chan-close! (fn (ch) ((nth ch 4))))
|
(define go-chan-close! (fn (ch) ((nth ch 4))))
|
||||||
|
(define go-chan-len (fn (ch) ((nth ch 5))))
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"language": "go",
|
"language": "go",
|
||||||
"total_pass": 469,
|
"total_pass": 475,
|
||||||
"total": 469,
|
"total": 475,
|
||||||
"suites": [
|
"suites": [
|
||||||
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
||||||
{"name":"parse","pass":176,"total":176,"status":"ok"},
|
{"name":"parse","pass":176,"total":176,"status":"ok"},
|
||||||
{"name":"types","pass":72,"total":72,"status":"ok"},
|
{"name":"types","pass":72,"total":72,"status":"ok"},
|
||||||
{"name":"eval","pass":80,"total":80,"status":"ok"},
|
{"name":"eval","pass":80,"total":80,"status":"ok"},
|
||||||
{"name":"runtime","pass":12,"total":12,"status":"ok"},
|
{"name":"runtime","pass":18,"total":18,"status":"ok"},
|
||||||
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
|
||||||
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
{"name":"e2e","pass":0,"total":0,"status":"pending"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Go-on-SX Scoreboard
|
# Go-on-SX Scoreboard
|
||||||
|
|
||||||
**Total: 469 / 469 tests passing**
|
**Total: 475 / 475 tests passing**
|
||||||
|
|
||||||
| | Suite | Pass | Total |
|
| | Suite | Pass | Total |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
| ✅ | parse | 176 | 176 |
|
| ✅ | parse | 176 | 176 |
|
||||||
| ✅ | types | 72 | 72 |
|
| ✅ | types | 72 | 72 |
|
||||||
| ✅ | eval | 80 | 80 |
|
| ✅ | eval | 80 | 80 |
|
||||||
| ✅ | runtime | 12 | 12 |
|
| ✅ | runtime | 18 | 18 |
|
||||||
| ⬜ | stdlib | 0 | 0 |
|
| ⬜ | stdlib | 0 | 0 |
|
||||||
| ⬜ | e2e | 0 | 0 |
|
| ⬜ | e2e | 0 | 0 |
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,55 @@
|
|||||||
20)
|
20)
|
||||||
|
|
||||||
;; ── report ─────────────────────────────────────────────────────
|
;; ── report ─────────────────────────────────────────────────────
|
||||||
|
(go-rt-test
|
||||||
|
"select: default runs when no case is ready"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "x := 0") (go-parse "select { case <-ch: x = 1 ; default: x = 99 }")))))
|
||||||
|
(go-env-lookup env "x"))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: recv case fires when ready"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 7") (go-parse "x := 0") (go-parse "select { case <-ch: x = 1 ; default: x = 99 }")))))
|
||||||
|
(go-env-lookup env "x"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: recv-into-var binds the value"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "ch <- 42") (go-parse "select { case v := <-ch: v }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: send case (always ready in v0)"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()") (go-parse "select { case ch <- 5: }")))))
|
||||||
|
(go-chan-len (go-env-lookup env "ch")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: picks first ready case"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "b <- 100") (go-parse "x := 0") (go-parse "select { case <-a: x = 1 ; case <-b: x = 2 ; default: x = 99 }")))))
|
||||||
|
(go-env-lookup env "x"))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: no default + nothing ready → blocked error"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "ch := make()")))))
|
||||||
|
(go-eval-stmt env (go-parse "select { case <-ch: }") (list)))
|
||||||
|
(list :eval-error :select-blocked-no-default))
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: combined with goroutine fan-in"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func push(c chan int, v int) { c <- v }") (go-parse "ch := make()") (go-parse "go push(ch, 7)") (go-parse "result := 0") (go-parse "select { case v := <-ch: result = v ; default: result = -1 }")))))
|
||||||
|
(go-env-lookup env "result"))
|
||||||
|
7)
|
||||||
|
|
||||||
(define
|
(define
|
||||||
go-rt-test-summary
|
go-rt-test-summary
|
||||||
(str "runtime " go-rt-test-pass "/" go-rt-test-count))
|
(str "runtime " go-rt-test-pass "/" go-rt-test-count))
|
||||||
|
|||||||
@@ -313,7 +313,13 @@ Progress-log line → push `origin/loops/go`.
|
|||||||
sched.sx header).
|
sched.sx header).
|
||||||
- [ ] Real preemption (suspending sends on full buffer / recvs on empty).
|
- [ ] Real preemption (suspending sends on full buffer / recvs on empty).
|
||||||
Requires reified execution state; deferred to Phase 5b.
|
Requires reified execution state; deferred to Phase 5b.
|
||||||
- [ ] `select { case ... }` multiplexing.
|
- [x] `select { case ... }` multiplexing. v0 tries cases in declared
|
||||||
|
order, picks first ready (sends always ready; recv ready iff
|
||||||
|
channel has buffered values). Default runs if nothing else
|
||||||
|
ready; no default and nothing ready → typed error.
|
||||||
|
Recv-into-var (`case v := <-ch`) binds; recv-into-assign
|
||||||
|
(`case v = <-ch`) re-binds. Real-Go random selection among
|
||||||
|
ready cases deferred (v0 deterministic).
|
||||||
- [ ] `range` over channels.
|
- [ ] `range` over channels.
|
||||||
- [ ] `time.After`-like timer channel.
|
- [ ] `time.After`-like timer channel.
|
||||||
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
||||||
@@ -597,6 +603,18 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`.
|
|||||||
|
|
||||||
_Newest first. Append one dated entry per commit._
|
_Newest first. Append one dated entry per commit._
|
||||||
|
|
||||||
|
- 2026-05-27 — Phase 5 cont.: `select` statement evaluation. New
|
||||||
|
`go-eval-select-stmt` + `go-select-pick` + `go-select-try-case`.
|
||||||
|
Walks cases in declared order: send always ready in v0; recv ready
|
||||||
|
iff `(go-chan-len ch) > 0` (new accessor added to sched.sx);
|
||||||
|
recv-into-decl/assign binds the value into env. Default deferred
|
||||||
|
until end of walk; if nothing ready and no default, returns
|
||||||
|
`(:eval-error :select-blocked-no-default)`. Subtle bug fixed where
|
||||||
|
`:select` stmt was returning the old env instead of the
|
||||||
|
case-body-extended env — assignments inside cases now stick. +6
|
||||||
|
tests, runtime 18/18, total 475/475. `[nothing]` — `:select-case`
|
||||||
|
uniform shape was already chiselled into the sister-plan diary when
|
||||||
|
the parser landed.
|
||||||
- 2026-05-27 — **Phase 5 first slice.** `lib/go/sched.sx` lands with
|
- 2026-05-27 — **Phase 5 first slice.** `lib/go/sched.sx` lands with
|
||||||
the v0 channel primitive: `go-make-chan` returns a closures-over-
|
the v0 channel primitive: `go-make-chan` returns a closures-over-
|
||||||
mutable-buf channel. Send appends, recv pops first, close flips a
|
mutable-buf channel. Send appends, recv pops first, close flips a
|
||||||
|
|||||||
Reference in New Issue
Block a user