Files
rose-ash/plans/go-on-sx.md
giles 985671cd76 hs: query targets, prolog hook, loop scripts, new plans, WASM regen
Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup

Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
       designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
       plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests

WASM: regenerate sx_browser.bc.js from updated hs sources

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 09:19:56 +00:00

7.5 KiB

Go-on-SX: Go on the CEK/VM

Compile Go source to SX AST; the existing CEK evaluator runs it. The unique angle: Go's goroutines and channels map cleanly onto SX's IO suspension machinery (perform/cek-resume) — a goroutine is a cek-step-loop running in a cooperative scheduler, a channel send/receive is a perform that suspends until the other end is ready.

End-state goal: core Go programs running, including goroutines, channels, defer/panic/recover, interfaces, and structs. Not a full Go compiler — no generics, no CGo, no full stdlib — but a faithful runtime for idiomatic Go concurrent programs.

Ground rules

  • Scope: only touch lib/go/** and plans/go-on-sx.md. Do not edit spec/, hosts/, shared/, or other lib/<lang>/.
  • Shared-file issues go under "Blockers" below with a minimal repro; do not fix here.
  • SX files: use sx-tree MCP tools only.
  • Architecture: Go source → Go AST → SX AST. No standalone Go evaluator.
  • Concurrency model: cooperative, not preemptive. Goroutines yield at channel ops and time.Sleep. A round-robin scheduler in SX drives them.
  • Commits: one feature per commit. Keep ## Progress log updated and tick boxes.

Architecture sketch

Go source text
    │
    ▼
lib/go/tokenizer.sx    — Go tokens: keywords, idents, string/rune/number literals,
    │                    operators, semicolon insertion rules
    ▼
lib/go/parser.sx       — Go AST: package, import, var, const, type, func, struct,
    │                    interface, goroutine, channel ops, defer, select, for range
    ▼
lib/go/transpile.sx    — Go AST → SX AST
    │
    ▼
lib/go/runtime.sx      — goroutine scheduler, channel primitives, defer stack,
    │                    panic/recover, interface dispatch, slice/map ops
    ▼
CEK / VM

Key semantic mappings:

  • go fn() → spawn new coroutine (SX coroutine primitive, Phase 4 of primitives)
  • ch <- v (send) → perform that suspends until receiver ready; scheduler picks next goroutine
  • v := <-ch (receive) → perform that suspends until sender ready
  • select { case ... } → scheduler checks all channel readiness, picks first ready
  • defer fn() → push onto a per-goroutine defer stack; run on return/panic
  • panic(v)raise the value; recover() catches it in deferred function
  • interface{} → any SX value (duck typed)
  • struct { ... } → SX hash table with field names as keys
  • slice → SX vector with length + capacity metadata
  • map[K]V → SX mutable hash table (Phase 10 of primitives)

Roadmap

Phase 1 — tokenizer + parser

  • Tokenizer: keywords (package, import, func, var, const, type, struct, interface, go, chan, select, defer, return, if, else, for, range, switch, case, default, break, continue, goto, fallthrough, map, make, new, nil, true, false), automatic semicolon insertion, string literals (interpreted + raw `...`), rune literals 'a', number literals (int, float, hex, octal, binary, complex), operators, slices [:]
  • Parser: package clause, imports, top-level func/var/const/type; function bodies: short variable decl :=, assignments, if/else, for/range, switch, return, struct literals, slice literals, map literals, composite literals, type assertions v.(T), method calls v.Method(args), goroutine go, channel ops <-ch, ch <- v, defer, select
  • Tests in lib/go/tests/parse.sx

Phase 2 — transpile: basic Go (no goroutines)

  • go-eval-ast entry
  • Arithmetic, string ops, comparison, boolean
  • Variables, short decl, assignment, multiple assignment
  • if/else if/else
  • for (C-style), for range over slice/map/string
  • Functions: named + anonymous, multiple return values (SX multiple values, Phase 8)
  • Structs → SX hash tables; field access .field; struct literals T{f: v}
  • Slices → SX vectors; len, cap, append, copy, slice expressions s[a:b]
  • Maps → SX hash tables; make(map[K]V), m[k], m[k] = v, delete(m, k), comma-ok v, ok := m[k]
  • Pointers — modelled as single-element mutable vectors; &x creates wrapper, *p dereferences
  • fmt.Println/fmt.Printf/fmt.Sprintf → SX IO perform (print)
  • 40+ eval tests in lib/go/tests/eval.sx

Phase 3 — defer / panic / recover

  • Defer stack per function frame — SX list of thunks, run LIFO on return
  • defer statement pushes thunk; transpiler wraps function body in try/finally equivalent
  • panic(v)raise with Go panic wrapper
  • recover() → catches panic value inside a deferred function; returns nil otherwise
  • Panic propagation across call stack until recovered or fatal
  • Tests: defer ordering, panic/recover, panic in goroutine without recover

Phase 4 — goroutines + channels

  • Coroutine-based goroutine type using SX coroutine primitive (Phase 4 of primitives)
  • Round-robin scheduler in lib/go/runtime.sx: maintains run queue, steps each goroutine one turn at a time, suspends at channel ops
  • Unbuffered channels: make(chan T) → rendezvous point; send suspends until receive and vice versa. Implemented as a pair of waiting queues + cek-resume.
  • Buffered channels: make(chan T, n) → circular buffer; send only blocks when full, receive only blocks when empty
  • close(ch) — mark channel closed; receivers drain then get zero value + false
  • select — scheduler inspects all cases, picks a ready one (random if multiple), blocks if none ready until at least one becomes ready
  • go fn(args) — spawns new goroutine on run queue
  • time.Sleep(d) — yields current goroutine, re-queues after d milliseconds (simulated with IO perform timer)
  • Tests: ping-pong, fan-out, fan-in, select with default, range over channel

Phase 5 — interfaces

  • Interface type → SX dict {:type "T" :methods {...}} dispatch table
  • interface{} / any → any SX value (already implicit)
  • Type assertion v.(T) → check :type field, panic if mismatch
  • Type switch switch v.(type) { case T: ... } → dispatches on :type
  • Method sets — structs implement interfaces implicitly if they have the right methods
  • Value vs pointer receivers — pointer receiver gets the mutable vector wrapper
  • Built-in interfaces: error (Error() string), Stringer (String() string)
  • Tests: interface satisfaction, type assertion, type switch, error interface

Phase 6 — standard library subset

  • fmtPrintln, Printf, Sprintf, Fprintf, Errorf, Stringer dispatch
  • stringsContains, HasPrefix, HasSuffix, Split, Join, TrimSpace, ToUpper, ToLower, Replace, Index, Count, Repeat
  • strconvItoa, Atoi, FormatFloat, ParseFloat, ParseInt, FormatInt
  • math — full surface via SX math primitives (Phase 15)
  • sortsort.Slice, sort.Ints, sort.Strings
  • errorserrors.New, errors.Is, errors.As
  • syncsync.Mutex (cooperative — just a boolean flag + goroutine queue), sync.WaitGroup, sync.Once
  • ioio.Reader/io.Writer interfaces; io.ReadAll; strings.NewReader

Phase 7 — full conformance target

  • Vendor a Go test suite or hand-build 100+ program tests in lib/go/tests/programs/
  • Drive scoreboard

Blockers

(none yet)

Progress log

Newest first.

(awaiting phase 1)