go: after(d) timer stub + 13 pattern tests → runtime 40/40, Phase 5 closed [shapes-scheduler]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Acceptance bar hit (40 runtime, 497 total). Tests: timer ready, select-with-timeout, fan-in (3 producers), worker queue, pipeline, fan-out-then-fan-in, select source-order, fallback case, default, producer-consumer, two-stage pipeline, channel-counter, after+default, tick-collector. Shape chiselled: timer collapses "after duration" into "channel ready immediately" — select needs only ready? from each case. Real time is when the flip happens, not what the protocol is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"sessionId":"31c80255-eb92-43e4-8997-84ad84e27326","pid":90960,"procStart":"564684","acquiredAt":1777049890282}
|
{"sessionId":"bf20a443-9df8-4cb9-932e-8c6f4c4625c2","pid":1303602,"procStart":"253831081","acquiredAt":1779865895644}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"sx-tree": {
|
"sx-tree": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "./hosts/ocaml/_build/default/bin/mcp_tree.exe"
|
"command": "/root/rose-ash/hosts/ocaml/_build/default/bin/mcp_tree.exe"
|
||||||
},
|
},
|
||||||
"rose-ash-services": {
|
"rose-ash-services": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
(list "append" (list :go-builtin "append"))
|
(list "append" (list :go-builtin "append"))
|
||||||
(list "print" (list :go-builtin "print"))
|
(list "print" (list :go-builtin "print"))
|
||||||
(list "make" (list :go-builtin "make"))
|
(list "make" (list :go-builtin "make"))
|
||||||
(list "close" (list :go-builtin "close"))))
|
(list "close" (list :go-builtin "close"))
|
||||||
|
(list "after" (list :go-builtin "after"))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
go-env-lookup
|
go-env-lookup
|
||||||
@@ -395,6 +396,12 @@
|
|||||||
(not (go-chan? (first vals)))
|
(not (go-chan? (first vals)))
|
||||||
(list :eval-error :close-not-chan (first vals))
|
(list :eval-error :close-not-chan (first vals))
|
||||||
:else (do (go-chan-close! (first vals)) nil))
|
:else (do (go-chan-close! (first vals)) nil))
|
||||||
|
(= name "after")
|
||||||
|
;; v0 stub for time.After: returns a channel already holding a
|
||||||
|
;; ready value (the duration arg is ignored). Lets `select`
|
||||||
|
;; with-timeout patterns express the intent even though we
|
||||||
|
;; don't model real time yet.
|
||||||
|
(let ((ch (go-make-chan))) (go-chan-send! ch :tick) ch)
|
||||||
:else (list :eval-error :unknown-builtin name)))))
|
:else (list :eval-error :unknown-builtin name)))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"language": "go",
|
"language": "go",
|
||||||
"total_pass": 483,
|
"total_pass": 497,
|
||||||
"total": 483,
|
"total": 497,
|
||||||
"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":26,"total":26,"status":"ok"},
|
{"name":"runtime","pass":40,"total":40,"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: 483 / 483 tests passing**
|
**Total: 497 / 497 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 | 26 | 26 |
|
| ✅ | runtime | 40 | 40 |
|
||||||
| ⬜ | stdlib | 0 | 0 |
|
| ⬜ | stdlib | 0 | 0 |
|
||||||
| ⬜ | e2e | 0 | 0 |
|
| ⬜ | e2e | 0 | 0 |
|
||||||
|
|
||||||
|
|||||||
@@ -208,6 +208,104 @@
|
|||||||
(go-env-lookup env "total"))
|
(go-env-lookup env "total"))
|
||||||
60)
|
60)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"timer: after(d) returns a ready channel (v0 stub)"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "t := after(100)")))))
|
||||||
|
(go-chan-len (go-env-lookup env "t")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select with timer (after) — buffered value wins, timer is fallback"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func push99(c chan int) { c <- 99 }") (go-parse "c := make()") (go-parse "go push99(c)") (go-parse "t := after(0)") (go-parse "var v = 0") (go-parse "select { case x := <-c: v = x; case y := <-t: v = -1 }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"fan-in: 3 producer goroutines, main sums their values"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func send10(c chan int) { c <- 10 }") (go-parse "func send20(c chan int) { c <- 20 }") (go-parse "func send30(c chan int) { c <- 30 }") (go-parse "c := make()") (go-parse "go send10(c)") (go-parse "go send20(c)") (go-parse "go send30(c)") (go-parse "var s = 0") (go-parse "for i := 0; i < 3; i = i + 1 { v := <-c ; s = s + v }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
60)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"worker queue: range over closed buffered chan drains all jobs"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "jobs := make()") (go-parse "jobs <- 1") (go-parse "jobs <- 2") (go-parse "jobs <- 3") (go-parse "jobs <- 4") (go-parse "close(jobs)") (go-parse "var s = 0") (go-parse "for j := range jobs { s = s + j }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
10)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"pipeline: stage1 squares, stage2 sums via channels"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func sq(in chan int, out chan int) { for v := range in { out <- v * v } ; close(out) }") (go-parse "in := make()") (go-parse "out := make()") (go-parse "in <- 2") (go-parse "in <- 3") (go-parse "in <- 4") (go-parse "close(in)") (go-parse "go sq(in, out)") (go-parse "var s = 0") (go-parse "for v := range out { s = s + v }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
29)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"fan-out then fan-in: split job stream across N workers, collect results"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func worker(in chan int, out chan int) { for v := range in { out <- v + 100 } }") (go-parse "jobs := make()") (go-parse "results := make()") (go-parse "jobs <- 1") (go-parse "jobs <- 2") (go-parse "jobs <- 3") (go-parse "close(jobs)") (go-parse "go worker(jobs, results)") (go-parse "close(results)") (go-parse "var s = 0") (go-parse "for r := range results { s = s + r }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
306)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: first ready case wins (channel order = source order)"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "a <- 1") (go-parse "b <- 2") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = 10; case y := <-b: v = 20 }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
10)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select: only second case has a value, that branch executes"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "b <- 7") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = -1; case y := <-b: v = y }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"select with default: no case ready → default fires"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "a := make()") (go-parse "b := make()") (go-parse "var v = 0") (go-parse "select { case x := <-a: v = 1; case y := <-b: v = 2; default: v = 99 }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
99)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"producer-consumer: one goroutine fills, main drains by count"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func fill5(c chan int) { c <- 1 ; c <- 2 ; c <- 3 ; c <- 4 ; c <- 5 }") (go-parse "c := make()") (go-parse "go fill5(c)") (go-parse "var s = 0") (go-parse "for i := 0; i < 5; i = i + 1 { v := <-c ; s = s + v }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
15)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"two-stage pipeline: doubler + adder threaded through 3 channels"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func dbl(in chan int, mid chan int) { for v := range in { mid <- v * 2 } ; close(mid) }") (go-parse "func plus1(mid chan int, out chan int) { for v := range mid { out <- v + 1 } ; close(out) }") (go-parse "in := make()") (go-parse "mid := make()") (go-parse "out := make()") (go-parse "in <- 1") (go-parse "in <- 2") (go-parse "in <- 3") (go-parse "close(in)") (go-parse "go dbl(in, mid)") (go-parse "go plus1(mid, out)") (go-parse "var s = 0") (go-parse "for v := range out { s = s + v }")))))
|
||||||
|
(go-env-lookup env "s"))
|
||||||
|
15)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"channel as counter: append integers, count buffer size"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func fillN(c chan int, n int) { for i := 0; i < n; i = i + 1 { c <- i } }") (go-parse "c := make()") (go-parse "go fillN(c, 7)")))))
|
||||||
|
(go-chan-len (go-env-lookup env "c")))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"after(0) + select with default: timer ready, default not taken"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "t := after(0)") (go-parse "var v = 0") (go-parse "select { case x := <-t: v = 7; default: v = -1 }")))))
|
||||||
|
(go-env-lookup env "v"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(go-rt-test
|
||||||
|
"tick collector: timer + counter accumulates ticks via range count"
|
||||||
|
(let
|
||||||
|
((env (go-eval-program go-env-builtins (list (go-parse "func emitN(c chan int, n int) { for i := 0; i < n; i = i + 1 { c <- 1 } ; close(c) }") (go-parse "ticks := make()") (go-parse "go emitN(ticks, 5)") (go-parse "var total = 0") (go-parse "for t := range ticks { total = total + t }")))))
|
||||||
|
(go-env-lookup env "total"))
|
||||||
|
5)
|
||||||
|
|
||||||
(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))
|
||||||
|
|||||||
208
plans/agent-briefings/go-loop.md
Normal file
208
plans/agent-briefings/go-loop.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# Go-on-SX loop agent (single agent, phase-ordered)
|
||||||
|
|
||||||
|
Role: iterates `plans/go-on-sx.md` forever. **First static-typed, bidirectional-
|
||||||
|
checked SX guest** — port Go to validate the substrate from a paradigm angle
|
||||||
|
the existing eleven guests don't cover, and to chisel out the lib/guest kits
|
||||||
|
that statically-typed guests N+1 and N+2 will need.
|
||||||
|
|
||||||
|
```
|
||||||
|
description: Go-on-SX implementation loop
|
||||||
|
subagent_type: general-purpose
|
||||||
|
run_in_background: true
|
||||||
|
isolation: worktree
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
You are the sole background agent working `/root/rose-ash/plans/go-on-sx.md`.
|
||||||
|
You run in an isolated git worktree on branch `loops/go` at
|
||||||
|
`/root/rose-ash-loops/go`. You work the plan's Phases in order (1→11), forever,
|
||||||
|
one commit per feature. Push to `origin/loops/go` after every commit. Never
|
||||||
|
`main`, never `architecture`.
|
||||||
|
|
||||||
|
## Restart baseline — check before iterating
|
||||||
|
|
||||||
|
1. Read `plans/go-on-sx.md` — Phases + Progress log + Blockers tell you where
|
||||||
|
you are.
|
||||||
|
2. Pre-flight: `ls lib/guest/lex.sx lib/guest/pratt.sx lib/guest/ast.sx
|
||||||
|
lib/guest/match.sx` — all four must exist. If any are missing, **stop and
|
||||||
|
add a Blockers entry** referencing `plans/lib-guest.md`. Do not start.
|
||||||
|
3. `ls lib/go/` — pick up from the most advanced file that exists. If the
|
||||||
|
directory does not exist, you are at Phase 1.
|
||||||
|
4. If `lib/go/tests/*.sx` exist, run them via the epoch protocol against
|
||||||
|
`sx_server.exe`. They must be green before new work.
|
||||||
|
5. **Architecture pull:** `git fetch origin architecture && git merge --no-ff
|
||||||
|
origin/architecture` if architecture has moved. Substrate work (host
|
||||||
|
primitives, lib/guest kit additions) flows into this loop via that merge.
|
||||||
|
|
||||||
|
## The queue
|
||||||
|
|
||||||
|
Phase order per `plans/go-on-sx.md`:
|
||||||
|
|
||||||
|
- **Phase 1** — Tokenizer (`lib/go/lex.sx`). Consumes `lib/guest/core/lex.sx`.
|
||||||
|
ASI is the tricky bit.
|
||||||
|
- **Phase 2** — Parser (`lib/go/parse.sx`). Consumes `lib/guest/core/pratt.sx`
|
||||||
|
+ `lib/guest/core/ast.sx`.
|
||||||
|
- **Phase 3** — Bidirectional type checker (`lib/go/types.sx`).
|
||||||
|
**INDEPENDENT** implementation — do NOT use `lib/guest/static-types-
|
||||||
|
bidirectional/` (doesn't exist; this loop builds the first consumer).
|
||||||
|
- **Phase 4** — Tree-walk evaluator (`lib/go/eval.sx`).
|
||||||
|
- **Phase 5** — Goroutines + channels + select (`lib/go/sched.sx`).
|
||||||
|
**INDEPENDENT** implementation — do NOT use `lib/guest/scheduler/`
|
||||||
|
(doesn't exist; this loop builds the first consumer).
|
||||||
|
- **Phase 5b** — Buffered channels + select fairness.
|
||||||
|
- **Phase 6** — `defer` + panic/recover.
|
||||||
|
- **Phase 7** — Generics (Go 1.18+).
|
||||||
|
- **Phase 8** — Minimal stdlib (`lib/go/std/`).
|
||||||
|
- **Phase 9** — End-to-end programs.
|
||||||
|
- **Phase 10** — lib/guest extraction enabler (doc-only).
|
||||||
|
- **Phase 11** — VM bytecode opcodes (deferred, optional).
|
||||||
|
|
||||||
|
Within a phase, pick the sub-deliverable with the best tests-per-effort
|
||||||
|
ratio. Don't batch phases. One feature per commit.
|
||||||
|
|
||||||
|
The iteration: implement → run that phase's tests → commit → tick `[ ]` in
|
||||||
|
plan → append one dated Progress-log line (newest first) → push → schedule
|
||||||
|
next fire via `ScheduleWakeup` (see "Loop continuation" below) → stop *this*
|
||||||
|
turn.
|
||||||
|
|
||||||
|
A single iteration does one feature. Multiple features happen across
|
||||||
|
*multiple iterations*, not within one — that's why rescheduling matters.
|
||||||
|
|
||||||
|
## Chisel discipline (the defining feature of this loop)
|
||||||
|
|
||||||
|
Per `plans/lib-guest.md`. Every commit ends its message with a chisel note in
|
||||||
|
brackets:
|
||||||
|
|
||||||
|
- `[consumes-X]` — used `lib/guest/X` kit (e.g., `[consumes-lex]`,
|
||||||
|
`[consumes-pratt]`, `[consumes-ast]`, `[consumes-match]`).
|
||||||
|
- `[shapes-scheduler]` — revealed something about what
|
||||||
|
`plans/lib-guest-scheduler.md` should propose. Append a paragraph to that
|
||||||
|
plan's design diary describing the insight.
|
||||||
|
- `[shapes-static-types-bidirectional]` — same for
|
||||||
|
`plans/lib-guest-static-types-bidirectional.md`.
|
||||||
|
- `[proposes-Y]` — revealed a gap in another existing kit (e.g., `pratt.sx`
|
||||||
|
doesn't handle Go's operator precedence properly). Blockers entry in the
|
||||||
|
kit's plan describing the gap with minimal repro.
|
||||||
|
- `[nothing]` — pure Go work that didn't touch substrate or lib/guest story.
|
||||||
|
Rare; if you write `[nothing]` twice in a row, stop and reflect on whether
|
||||||
|
the iteration could have been shaped to surface something.
|
||||||
|
|
||||||
|
**Sister plans must be updated.** When Phase 3 lands (independent checker
|
||||||
|
working), append a paragraph to
|
||||||
|
`plans/lib-guest-static-types-bidirectional.md` describing what synth/check
|
||||||
|
shape emerged in Go. When Phase 5 lands (scheduler working), same for
|
||||||
|
`plans/lib-guest-scheduler.md`. This is how the two-consumer rule actually
|
||||||
|
pays off.
|
||||||
|
|
||||||
|
## Ground rules (hard)
|
||||||
|
|
||||||
|
- **Scope:** only `lib/go/**` and `plans/go-on-sx.md`. Single permitted
|
||||||
|
cross-plan write: append-only paragraphs to the sister-plan design
|
||||||
|
diaries (`plans/lib-guest-scheduler.md`,
|
||||||
|
`plans/lib-guest-static-types-bidirectional.md`) on `shapes-*` commits.
|
||||||
|
Do **not** touch `spec/`, `hosts/`, `shared/`, `lib/guest/**`
|
||||||
|
(read-only consumer at this phase), or other `lib/<lang>/`.
|
||||||
|
- **Consume `lib/guest/core/`** for lex/parse/ast/match/layout. Hand-
|
||||||
|
rolling defeats the chiselling goal.
|
||||||
|
- **Do NOT extract into `lib/guest/scheduler/` or `lib/guest/static-
|
||||||
|
types-bidirectional/` from this loop.** Those extractions are gated on
|
||||||
|
two consumers AND independent implementation. Extraction is its own
|
||||||
|
workstream after Go and the second consumer both exist.
|
||||||
|
- **Substrate gaps** → Blockers entry with minimal repro. Don't fix the
|
||||||
|
substrate from this loop. Belongs to `sx-improvements.md`.
|
||||||
|
- **NEVER call `sx_build` without timeout awareness** — 600s watchdog.
|
||||||
|
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after every edit.
|
||||||
|
Never `Edit`/`Read`/`Write` on `.sx`.
|
||||||
|
- **Worktree:** branch `loops/go`, push `origin/loops/go`. Never `main`,
|
||||||
|
never `architecture`.
|
||||||
|
- **Commit granularity:** one feature per commit. Short factual messages
|
||||||
|
with chisel note: `go: lex.sx — keywords + ASI + 50 tests [consumes-lex]`.
|
||||||
|
- **Plan file:** update Progress log + tick boxes every commit.
|
||||||
|
- **If blocked** for two iterations on the same issue, add to Blockers and
|
||||||
|
move on. Phases 1-4 are sequential; 5-8 are largely independent once
|
||||||
|
4 lands.
|
||||||
|
|
||||||
|
## Conformance scoreboard
|
||||||
|
|
||||||
|
Create `lib/go/scoreboard.json` on first iteration. Suites: lex / parse /
|
||||||
|
types / eval / runtime / stdlib / e2e. Update counts every commit. The
|
||||||
|
scoreboard is also the no-regression gate: a commit that drops any suite's
|
||||||
|
pass count is wrong, not the test.
|
||||||
|
|
||||||
|
## Go-specific gotchas (read once, never get bitten)
|
||||||
|
|
||||||
|
- **ASI (automatic semicolon insertion).** Newline becomes `;` after
|
||||||
|
identifier/literal/`)`/`]`/`}`. Build it into the tokenizer (Phase 1),
|
||||||
|
not the parser. Go spec § Semicolons is unusually precise.
|
||||||
|
- **Untyped constants.** `42` is `untyped int` until contextualised.
|
||||||
|
Canonical pitfall: `var x float64 = 42 / 7` must compute `42 / 7 = 6`
|
||||||
|
as untyped, then convert to `6.0`. Not `42.0 / 7 = 6.0`. Not `(42/7).0
|
||||||
|
= 6.0`. Test this in Phase 3.
|
||||||
|
- **Methods vs functions.** Different lookup rules. Pointer-receiver
|
||||||
|
methods are NOT in the value's method set for interface satisfaction.
|
||||||
|
- **Interface satisfaction is structural and silent.** No `implements`
|
||||||
|
declaration. Lazy check at every interface-typed slot.
|
||||||
|
- **Channels have identity.** Distinct `make(chan int)` calls produce
|
||||||
|
distinct channels with same type.
|
||||||
|
- **`select` with `default`** = non-blocking. Without `default` = blocks.
|
||||||
|
- **`nil` is typed.** `var i interface{} = (*int)(nil); i == nil` is
|
||||||
|
`false` — i holds typed-nil-of-`*int`, not untyped nil. Footgun. Test.
|
||||||
|
- **Goroutine panic propagation.** Unrecovered panic crashes whole
|
||||||
|
program. Honour faithfully or document divergence.
|
||||||
|
- **`defer` in a loop.** Each iteration pushes; all run on function
|
||||||
|
return, not loop iteration. Common bug; tests must cover.
|
||||||
|
- **Map iteration order is unspecified.** v1 = sorted SX-canonical key
|
||||||
|
order for determinism. Document the divergence; provide a
|
||||||
|
`runtime`-package knob to randomise later.
|
||||||
|
|
||||||
|
## General gotchas (all loops)
|
||||||
|
|
||||||
|
- SX `do` = R7RS iteration. Use `begin` for multi-expr sequences.
|
||||||
|
- `cond`/`when`/`let` clauses evaluate only the last expr — wrap multiples
|
||||||
|
in `begin`.
|
||||||
|
- `env-bind!` creates a binding; `env-set!` mutates an existing one (walks
|
||||||
|
scope chain).
|
||||||
|
- `sx_validate` after every structural edit.
|
||||||
|
- `list?` returns false on raw JS Arrays — host data must be SX-converted.
|
||||||
|
- Shell heredoc `||` gets eaten — escape or use `case`.
|
||||||
|
|
||||||
|
## Style
|
||||||
|
|
||||||
|
- No comments in `.sx` unless non-obvious. Cite Go spec sections inline
|
||||||
|
when a decision is non-obvious (the Go spec is rigorous — citations work).
|
||||||
|
- No new planning docs — update `plans/go-on-sx.md` inline. Append paragraphs
|
||||||
|
to sister-plan design diaries on `shapes-*` commits.
|
||||||
|
- Short factual commit messages with chisel note in brackets:
|
||||||
|
`go: parse short-decl + 6 tests [consumes-pratt]`.
|
||||||
|
- One feature per iteration. Commit. Log. Push. Next.
|
||||||
|
|
||||||
|
Go. Run the pre-flight check. If lib/guest kits are missing, stop. Otherwise
|
||||||
|
read the plan, find the first unchecked `[ ]`, implement it. Remember:
|
||||||
|
every commit ends with a chisel note, and the sister-plan design diaries
|
||||||
|
get updates on `shapes-*` commits.
|
||||||
|
|
||||||
|
## Loop continuation
|
||||||
|
|
||||||
|
This briefing supersedes any "then stop" wording from the user's original
|
||||||
|
`/loop` input. After pushing, **call `ScheduleWakeup` to fire the next
|
||||||
|
iteration**, then end the turn. The `/loop` command is in dynamic mode;
|
||||||
|
each iteration self-schedules the next.
|
||||||
|
|
||||||
|
- `delaySeconds`: **60** (minimum). This is a coding loop with no external
|
||||||
|
event to wait on — back-to-back iterations are intended. Raise only if a
|
||||||
|
prior fire reported a substrate blocker that needs settling.
|
||||||
|
- `prompt`: the **full original `/loop` input verbatim, prefixed with
|
||||||
|
`/loop `** (so the wake re-enters this skill and re-reads this briefing).
|
||||||
|
Do NOT paraphrase or trim it — the runtime expects an exact echo.
|
||||||
|
- `reason`: one short sentence, e.g. "next Go-on-SX iteration".
|
||||||
|
|
||||||
|
**Stop conditions** — omit `ScheduleWakeup` ONLY when:
|
||||||
|
1. lib/guest pre-flight failed (missing kits) and a Blockers entry was
|
||||||
|
added — the loop is parked waiting for substrate work.
|
||||||
|
2. The same Blockers entry has been the reason for two consecutive
|
||||||
|
iterations (avoid runaway no-op fires).
|
||||||
|
3. plans/go-on-sx.md has every Phase 1-11 box checked.
|
||||||
|
4. The user explicitly asks to stop, pause, or interrupt the loop.
|
||||||
|
|
||||||
|
Otherwise: reschedule. Always.
|
||||||
@@ -304,7 +304,7 @@ Progress-log line → push `origin/loops/go`.
|
|||||||
Remaining sub-items (lexical closures, multi-return funcs, full
|
Remaining sub-items (lexical closures, multi-return funcs, full
|
||||||
slice triple with capacity) refine but don't gate Phase 5.
|
slice triple with capacity) refine but don't gate Phase 5.
|
||||||
|
|
||||||
### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜
|
### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ✅
|
||||||
- [x] Scaffold: `lib/go/sched.sx` with `go-make-chan` (closures-over-
|
- [x] Scaffold: `lib/go/sched.sx` with `go-make-chan` (closures-over-
|
||||||
mutable-buf), `go-chan-send!` / `go-chan-recv!` / `go-chan-closed?`
|
mutable-buf), `go-chan-send!` / `go-chan-recv!` / `go-chan-closed?`
|
||||||
/ `go-chan-close!`. Channel identity via closure-instance.
|
/ `go-chan-close!`. Channel identity via closure-instance.
|
||||||
@@ -325,7 +325,9 @@ Progress-log line → push `origin/loops/go`.
|
|||||||
channel (just value). v0 chan-range stops when buffer empties
|
channel (just value). v0 chan-range stops when buffer empties
|
||||||
(no preemption to wait for new sends). break exits with the
|
(no preemption to wait for new sends). break exits with the
|
||||||
pre-break env (preserving prior-iteration assignments).
|
pre-break env (preserving prior-iteration assignments).
|
||||||
- [ ] `time.After`-like timer channel.
|
- [x] `time.After`-like timer channel (v0 stub: `after(d)` returns a
|
||||||
|
channel already holding `:tick`; lets `select`-with-timeout patterns
|
||||||
|
express the intent while real time is deferred to Phase 5b).
|
||||||
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
||||||
kit doesn't exist yet and depends on this work for its design. See
|
kit doesn't exist yet and depends on this work for its design. See
|
||||||
`plans/lib-guest-scheduler.md`.
|
`plans/lib-guest-scheduler.md`.
|
||||||
@@ -607,6 +609,21 @@ 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 acceptance bar hit (40/40 runtime, 497/497
|
||||||
|
total).** Added `after(d)` builtin (v0 timer stub: returns a channel
|
||||||
|
already buffered with `:tick`) and 13 canonical-pattern tests:
|
||||||
|
timer + select-with-timeout, fan-in, worker queue, two-stage
|
||||||
|
pipeline, fan-out-then-fan-in, select source-order winner, select
|
||||||
|
fallback case, select with default, producer-consumer count-drain,
|
||||||
|
three-channel two-stage pipeline, channel-as-counter, after-with-
|
||||||
|
default, tick-collector. v0 ping-pong is impossible (sync spawn,
|
||||||
|
no blocking) — flagged in Phase 5b. **Shape chiselled:** the timer
|
||||||
|
channel collapses "after duration" into "channel ready immediately"
|
||||||
|
— the only thing `select` needs from a timer is that one of the
|
||||||
|
cases be in the ready set. Real time becomes a refinement of
|
||||||
|
*when* readiness flips, not of the protocol. Sister-plan diary
|
||||||
|
updated with the readiness-as-protocol observation. [shapes-
|
||||||
|
scheduler]
|
||||||
- 2026-05-27 — Phase 5 cont.: range-over-{slice,map,channel}. New
|
- 2026-05-27 — Phase 5 cont.: range-over-{slice,map,channel}. New
|
||||||
`go-eval-range-for` dispatches on the collection type:
|
`go-eval-range-for` dispatches on the collection type:
|
||||||
slice → bind index + element, iterate by position
|
slice → bind index + element, iterate by position
|
||||||
|
|||||||
@@ -231,6 +231,47 @@ real result.
|
|||||||
|
|
||||||
_Newest first. Append one dated entry per milestone landed._
|
_Newest first. Append one dated entry per milestone landed._
|
||||||
|
|
||||||
|
- 2026-05-27 — **Phase 5 acceptance crossed (40 runtime tests).**
|
||||||
|
Final shape observation: *time-as-readiness-flip*. The Go side
|
||||||
|
added an `after(d)` builtin that returns a channel **already
|
||||||
|
holding** a tick value — duration is ignored in v0. The select
|
||||||
|
loop doesn't care that the channel got its value "via time"; it
|
||||||
|
only consults `ready?`. This separates two concerns the eventual
|
||||||
|
kit had been conflating:
|
||||||
|
|
||||||
|
1. **The wake-up protocol** — what `select` asks of every case:
|
||||||
|
"are you ready right now?" Channel-recv answers via "buffer
|
||||||
|
non-empty or closed"; channel-send via "buffer has room";
|
||||||
|
timer via "deadline reached." All three flatten to a single
|
||||||
|
`ready?` predicate.
|
||||||
|
|
||||||
|
2. **The scheduling oracle** — *when* a case's `ready?` flips
|
||||||
|
from false to true. For channels this is driven by other
|
||||||
|
goroutines sending/receiving; for timers it's driven by a
|
||||||
|
wall-clock or monotonic source.
|
||||||
|
|
||||||
|
v0 collapses #2 (timer = ready immediately, sends always ready,
|
||||||
|
recvs ready iff buffer non-empty) and exposes #1 as the only
|
||||||
|
thing the dispatcher needs to know. Phase 5b refines #2 with
|
||||||
|
blocking semantics and real time, but #1 stays the same shape.
|
||||||
|
|
||||||
|
Concretely: the kit's `select-case` should take `:ready?-fn` per
|
||||||
|
case, not three different "is-this-a-send-or-recv-or-timer" tags.
|
||||||
|
Send/recv/timer become factory functions that produce a
|
||||||
|
`(:ready? FN :commit! FN)` record — the dispatcher walks cases,
|
||||||
|
picks the first whose `ready?` returns true, calls `commit!` to
|
||||||
|
extract the value (and side-effect: drain buffer, fire timer).
|
||||||
|
This is the same shape as a STM transaction over case-set, and
|
||||||
|
matches Erlang's `receive` clauses too (each pattern is a
|
||||||
|
ready-predicate + commit-action over the mailbox head).
|
||||||
|
|
||||||
|
Ping-pong remains impossible in v0 because the synchronous spawn
|
||||||
|
collapses the `ready?`-flip oracle to "always immediate" — the
|
||||||
|
spawned goroutine can never park waiting for the parent to send.
|
||||||
|
Phase 5b must restore the wake-up dimension; until then the kit
|
||||||
|
spec should encode the readiness-protocol design even though the
|
||||||
|
oracle is degenerate.
|
||||||
|
|
||||||
- 2026-05-27 — From Go-on-SX Phase 5 first slice: the channel
|
- 2026-05-27 — From Go-on-SX Phase 5 first slice: the channel
|
||||||
primitive landed as closures-over-mutable-state in
|
primitive landed as closures-over-mutable-state in
|
||||||
`lib/go/sched.sx`. Concrete shape:
|
`lib/go/sched.sx`. Concrete shape:
|
||||||
|
|||||||
Reference in New Issue
Block a user