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>