gp-parse-type-params consumes the optional [NAMES CONSTRAINT, ...]
clause after a func name. AST stays backward-compatible: 5-slot
func-decl when no [...] is present, 6-slot when it is.
Typer binds each type-param name as (:ty-param NAME CONSTRAINT) so
body's (:ty-name "T") references resolve. Eval is type-erasing —
ignores type info, dispatches by name + arity.
10 new tests: parse (3), types (5), eval (2). Total 527/527.
Shape: the field binding-group from the canonical kit now feeds
6 consumers (struct fields, var-decls, const-decls, params,
receivers, type-params). Confirms it as a TRUE cross-deliverable
shape — sister-plan diary documents the 5 roles binding-groups
take and why the kit should expose ONE parser + pluggable validators.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wired panic through :go stmt (v0 sync surfaces back to spawner —
matches real Go's "crash whole program" end-effect) and through
go-eval-for (was swallowing panic at the loop boundary).
8 tests added: goroutine-panic-surfaces, goroutine-recover-via-
spawner-defer, multi-defer-LIFO-with-recover, defer-fires-on-panic-
path, panic(nil), panic-in-loop, defer-still-runs-in-panicking-fn,
args-eager-on-panic-path. 20 Phase-6 tests total; +20 acceptance
bar cleared (eval/ 80 → 100).
Shape: 4 control-flow sites now repeat the same sentinel dispatch
arm (return-value, break, continue, eval-error, go-panic). The
scheduler kit should bake in a single propagates? helper rather
than have each guest evaluator list every sentinel inline — diary
documents the cross-cutting abstraction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Panic/recover builtins + per-frame __go-panic-cell of shape
(STATE V). Body panic flips cell :none→:raised BEFORE defers drain
so recover() can find it. recover() walks env chain past shadowing
cells to the outermost :raised one — flips it :recovered, returns V.
Frame exit checks cell: :recovered → return clean; :raised →
propagate (:go-panic V).
6 tests: uncaught-from-program, panic-from-fn, defer-recover-swallow,
recover-captures-via-channel, propagation-through-no-defer-chain,
middle-frame-catches-deeper-panic.
Shape: panic cell is a frame-attached out-of-band channel that
survives function boundaries via env-chain walk. Same primitive
slots into the scheduler kit's termination-record + cleanup-with-
error-context hook. Maps cleanly to Erlang try/catch/after.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6 first slice. New :defer stmt dispatch, go-eval-defer-stmt
captures (callee, eagerly-evaluated args) onto a frame-local
__go-defer-stack mutable list. go-eval-call installs the stack and
drains LIFO before returning; go-eval-program does the same for
the implicit main frame. New :quoted-value AST node lets defer
re-invoke calls with the frozen arg values.
6 eval tests: single defer, multi-LIFO, args-eager-at-defer-time,
fires-on-early-return, frame-local (no bleed to outer), defer-in-loop.
Shape: defer is a per-frame cleanup queue (LIFO on frame exit) that
the scheduler kit will reuse for panic-unwind + clean-exit + select-
case-rollback paths. Distinct from the scheduler's ready-queue —
diary updated to keep that distinction explicit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. The crossings:
* Method dispatch — Methods record under #method/TYPE/NAME (same
mangled-key scheme the type checker uses, intentionally so eval
and type checker can converge on a shared method-table protocol
later). go-eval-method-call: lookup the receiver type's method,
bind receiver param to the struct value, evaluate body. Value and
pointer receivers treated the same in v0 (pointer semantics not
modelled yet).
* Method-call dispatch — In go-eval's :app branch, head=:select
routes to go-eval-method-call. If the receiver is not a struct,
falls back to the field-as-callable path.
* Unary prefix ops — go-eval's :app branch checks for 1-arg :var
head with op name "-" / "+" / "!". (Other unary ops like
*p / &v / <-ch / ^x deferred until pointer / channel / bitwise
semantics arrive.)
End-to-end programs verified:
* recursive fib(10) = 55
* struct + method + iterative loop (counter bump 7 times)
* linear search (returns index or -1)
* factorial via method on Counter (= 120)
* count odd numbers in 1..10 = 5
**Phase 4 acceptance bar (80+) crossed: eval 80/80, total 457/457.**
Remaining Phase 4 work (closures, multi-return, full slice triple,
pointer semantics) refines but doesn't gate Phase 5 (goroutines).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. Adds runtime support for Go's struct type.
Struct representation: (list :go-struct TYPE-NAME FIELDS) where
FIELDS is an association list of (field-name value) pairs.
`type T struct { ... }` is now significant at eval-time. The new
go-eval-type-decl registers field-name lists in env under
(:go-struct-type FIELD-NAMES) so positional composite literals can
map argument positions to field names. Non-struct type aliases are
silent no-ops in v0.
go-eval-composite extended:
* If type is (:var TYPE-NAME), look up in env. Must be a
:go-struct-type entry — error otherwise.
* go-eval-struct-lit branches on whether the first elem is :kv
(keyed) or not (positional). Keyed mode reads key-name from each
:kv's key (which is a :var node). Positional mode arity-checks
against the field-names list and zips positionally.
go-eval-select handles (:select OBJ FIELD-NAME) — field lookup with
go-map-get on the FIELDS assoc list.
go-eval-assign-pairs gets a new (:select OBJ FIELD) LHS branch:
- var-rooted only for v0
- rebuilds the struct via go-map-set, rebinds the var
**Functions taking and returning structs round-trip end-to-end:**
type Point struct { x, y int }
func add(a, b Point) Point { return Point{a.x + b.x, a.y + b.y} }
add(Point{1, 2}, Point{3, 4}) // Point{4, 6}
Method-dispatch (calling p.M() where M is a method on Point's type)
is the next step; needs threading the type checker's #method/T/N
scheme into eval-time so functions can be looked up by receiver type.
eval 66/66, total 443/443.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. Adds map values and index-assignment for both
slices and maps.
Map representation: (list :go-map ENTRIES) where ENTRIES is an
association list of (key value) pairs.
go-map-get / go-map-set — primitive lookup + functional-update.
go-slice-set — same idea for slices.
go-extract-map-entries reads each :kv element in a composite literal,
evaluating key and value. go-eval-composite dispatches on :ty-map to
build the :go-map value.
go-eval-index extended: when OBJ is a :go-map, look up the key via
go-map-get. Missing keys return nil in v0 (Go's real semantics is
the zero value of the value type — needs runtime type info that this
slice doesn't yet thread through).
go-eval-builtin's len handles :go-map alongside :go-slice and strings.
go-eval-assign-pairs gets a new branch for (:index OBJ IDX) LHS:
- var-rooted indexing only (a[i] = v / m["k"] = v)
- slice → go-slice-set then rebind the var
- map → go-map-set then rebind the var
**Word-counter via map[string]int works end-to-end:**
words := []string{"a", "b", "a", "c", "a"}
counts := map[string]int{}
for i := 0; i < len(words); i++ {
counts[words[i]] = counts[words[i]] + 1
}
// counts["a"] == 3
Builds on:
- map composite literal eval
- map index lookup
- map index-assign
- slice indexing
- len() builtin
- nil + 1 = 1 (numeric-coercion of missing-key default)
eval 58/58, total 435/435.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. Adds runtime support for Go's slice type.
Slice representation: (list :go-slice ELEMS) — a simple wrapper around
a list of element values. v0 deferring the full
(length, capacity, backing-vector) triple from the Go spec until
programs need it.
go-eval-composite → for (:composite TYPE-OR-EXPR ELEMS) where
TYPE is :ty-slice / :ty-array, eval each
element (handling :kv index-keyed
shorthand by taking only the value) and
wrap in :go-slice.
go-eval-index → (:index OBJ IDX). Bounds-checked; out-of-
range returns (:eval-error :index-out-of-range).
go-eval-slice → (:slice OBJ LOW HIGH MAX). Two-index slice
with omitted low → 0, omitted high → len.
Returns a new :go-slice.
go-list-slice → primitive list-slicing helper.
Builtins live in a new starter env go-env-builtins:
len(slice|string) → count
append(slice, ...x) → new slice with x appended
print(...) → no-op in v0
Builtins are bound as (:go-builtin NAME); go-eval-call recognises the
shape and routes to go-eval-builtin instead of go-eval-fn.
**Summing a slice via the canonical Go for-loop works end-to-end:**
a := []int{1, 2, 3, 4, 5}
sum := 0
for i := 0; i < len(a); i++ {
sum = sum + a[i]
}
// sum == 15
eval 50/50, total 427/427.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. go-eval-for handles all three for-header shapes:
for { ... } — infinite (cond defaults to true)
for cond { ... } — while-like (init=nil, post=nil)
for init ; cond ; post { ... } — C-style
Implementation:
* Run INIT (if any), extending env.
* Loop: eval COND. If false, exit with current env.
Eval body (a :block). Catch sentinels:
:return-value → propagate up
:break → exit loop with pre-break env
:continue → still runs POST, then re-loops
Otherwise: run POST, re-loop.
:break and :continue propagate as keyword sentinels through
go-eval-block alongside the existing :return-value sentinel. The
block returns whichever sentinel hit first; control-flow constructs
(for, switch, select) catch them.
inc-dec (x++ / x--) updates env via the same shadowing model used by
assign — `(go-env-extend env name (+ current 1))`.
**Iterative fact(5) = 120 and the classic sum-to-9 = 45 both
evaluate.** Demonstrates the for-loop machinery is solid enough for
real programs.
eval 40/40, total 417/417.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 cont. go-eval-stmt dispatches on:
:return → wraps value in (:return-value V) sentinel
:var-decl → bind each NAME via go-eval-var-decl
:short-decl → bind each (:var NAME) lhs to corresponding expr value
:assign → immutable-env shadowing (true mutation deferred)
:block → run stmts via go-eval-block, propagating :return-value
:if / :else → cond-driven dispatch
:func-decl → bind name to (list :go-fn PARAMS BODY)
else → expression statement, evaluate for side effects
go-eval-call extends the CALLER's env with param-names → arg-values
(dynamic-scope-ish — closures don't capture lexical env yet), runs the
body block, catches :return-value and unwraps.
**Recursive fib(5) = 5 evaluates correctly.** Recursion works because
top-level func bindings are in the calling env before the recursive
call happens.
True lexical closures (let bind sees outer var; assignments visible to
nested funcs) need an env-cell model with mutation; deferred to a
later slice.
eval 33/33, total 410/410.
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>