Phase 5 (goroutines + channels) opens.
lib/go/sched.sx is the **independent implementation** referenced by
plans/lib-guest-scheduler.md — the first-consumer cut whose realised
shape will inform the eventual sister kit.
Channel representation:
(list :go-chan SEND-FN RECV-FN CLOSED?-FN CLOSE!-FN)
Each closure shares a mutable `buf` (a list mutated via append! and
set!) and a `closed` flag. Channel identity is closure-instance —
two `make()` calls produce distinct values per Go spec § Channel types.
Primitive API in sched.sx:
go-make-chan / go-chan? / go-chan-send! / go-chan-recv! /
go-chan-closed? / go-chan-close!
Eval integration in eval.sx:
* `make` and `close` added as builtins. v0 `make()` takes no args
and returns an unbounded-buffer channel.
* `:send` stmt → go-chan-send! on the channel.
* Unary `<-` recv on channel values → go-chan-recv!. `:empty`
sentinel converted to nil (stand-in for blocking semantics).
* `:go expr` → synchronous eval (v0 limitation, see sched.sx
header).
**v0 concurrency model — synchronous goroutines.** SX doesn't expose
first-class continuations to guest code, so v0 runs `go f()`
immediately and depends on the spawned goroutine running to
completion before the main goroutine receives. This is the right
semantics for the simple producer/consumer patterns covered here.
True preemption with blocking send/recv is Phase 5b — requires either
a CEK-style trampolining eval rewrite or kit-level continuation
support. Logged in sched.sx header and in the sister-plan diary.
Runtime suite (12 tests):
* 6 direct API tests: identity, FIFO order, closed-flag
* 6 source-level: make + send + recv, go ping-pong, close,
multi-goroutine fan-in, worker-with-result
Sister-plan scheduler diary updated with the channel-as-closure-
bundle insight and the v0 synchronous-spawn caveat.
runtime 12/12, total 469/469.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 — bidirectional type checker — is fully ticked (short-decl
was already implemented). Phase 4 starts here.
lib/go/eval.sx single judgment:
(go-eval ENV EXPR) → VALUE | (list :eval-error TAG ...)
ENV is an association list of (NAME VALUE) bindings — same shape as
the type checker's ctx, but the entries are runtime values. Values
are represented directly in SX: integers/floats as SX numbers,
strings as SX strings, booleans as true/false, nil as nil. Composite
values (slices/maps/structs/pointers/channels) arrive in later slices.
First-slice coverage:
* go-env-empty / -lookup / -extend
* Literal decoding:
decimal (with underscores)
hex (0x.. / 0X..)
oct (0o.. / 0O..)
bin (0b.. / 0B..)
via go-hex-digit-value (explicit char equality — SX's nth on
strings returns single-char strings, not numeric codes; the
arithmetic-on-char-codes pattern from the OCaml kernel ports
doesn't work here).
* Identifier lookup with predeclared true / false / nil.
* Binops: + - * / and the six comparison ops and && / ||.
* Errors as (:eval-error TAG ...) sentinels.
Statements (block / return / short-decl / assign), control flow
(if / for), and function application / closures arrive in subsequent
slices.
eval 25/25, total 402/402.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First slice of Phase 3 (bidirectional type checker).
lib/go/types.sx defines:
* go-ctx-empty / go-ctx-extend / go-ctx-lookup — context as a value.
* go-ctx-extend-field — consumes the (:field NAMES TYPE) shape from
the parser, binding every name to the shared type. This is the
cross-deliverable validation of the :field binding-group
observation made during Phase 2 func decls: parser produces it,
type checker consumes it, same shape end-to-end.
* go-predeclared — true / false / nil baked in. Full list expanded
on demand.
* go-synth — currently handles variable lookup; literals / calls /
binops follow in subsequent iterations.
* go-check — v0 defers to synth + structural type equality. Untyped-
constant flow and assignment-compatibility relations land later.
* Type errors carry first-class tags (:unbound, :mismatch,
:unsupported-synth) so consumers and tooling can dispatch.
Conformance.sh wired with new types suite. Scoreboard cleanup: drop
the "pending" types row since the suite is now real.
types 12/12, total 317/317. Phase 3 underway.
Sister-plan static-types-bidirectional diary updated with the
synth/check shape: judgment skeleton, error tag structure, and the
proposal that `check` should accept a `subtype?` predicate parameter
so each consumer (Go untyped-constants, TS variance, Rust lifetimes)
plugs in its own variance discipline without rewriting the judgment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Starts Phase 2. lib/go/parse.sx defines:
* go-precedence-table — Go's five operator-precedence levels in the
(NAME PREC ASSOC) entry shape from lib/guest/pratt.sx, ready for the
binary-operator iteration to consume via pratt-op-lookup.
* go-parse(src) — tokenises and parses ONE primary expression: int,
float, imag, string, rune literals become (ast-literal VALUE);
identifiers become (ast-var NAME). Built directly on lib/guest/ast.sx
constructors — no intermediate AST shape.
Conformance.sh extended to load lib/guest/{ast,pratt}.sx and run the
new parse suite. Scoreboard cleanup: drop the "pending" parse row since
the suite is now real.
parse 17/17 (lex still 129/129). Total 146/146.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First Go-on-SX iteration. Tokenizer consumes lib/guest/lex.sx character-class
predicates. Automatic semicolon insertion per Go spec § Semicolons fires on
newline, EOF, and block comments containing a newline, after
ident/int/string/rune/{break,continue,fallthrough,return}/{++,--,),],}}.
Scoreboard + conformance.sh wired; lex 78/78. Plan Phase 1 sub-items
checked; floats/raw-strings/hex-ints still ⬜.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>