From a019aa1edccf333dd5a169c918f13fe52e86d73d Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 21:22:34 +0000 Subject: [PATCH] =?UTF-8?q?go:=20eval.sx=20=E2=80=94=20for=20/=20break=20/?= =?UTF-8?q?=20continue=20/=20inc-dec=20+=207=20tests=20[nothing]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- lib/go/eval.sx | 80 ++++++++++++++++++++++++++++++++++++++++++ lib/go/scoreboard.json | 6 ++-- lib/go/scoreboard.md | 4 +-- lib/go/tests/eval.sx | 49 ++++++++++++++++++++++++++ plans/go-on-sx.md | 18 ++++++++-- 5 files changed, 149 insertions(+), 8 deletions(-) diff --git a/lib/go/eval.sx b/lib/go/eval.sx index dee1d574..2014c945 100644 --- a/lib/go/eval.sx +++ b/lib/go/eval.sx @@ -348,6 +348,78 @@ (body (nth stmt 4))) (go-env-extend env name (list :go-fn params body))))) +(define + go-eval-inc-dec + ;; (:inc-dec OP EXPR) where OP is "++" or "--". EXPR should be (:var NAME). + (fn (env stmt) + (let ((op (nth stmt 1)) (operand (nth stmt 2))) + (cond + (not (and (list? operand) (= (first operand) :var))) + (list :eval-error :unsupported-lhs operand) + :else + (let ((current (go-eval env operand))) + (cond + (go-eval-error? current) current + :else + (let ((new-val + (cond + (= op "++") (+ current 1) + (= op "--") (- current 1) + :else current))) + (go-env-extend env (nth operand 1) new-val)))))))) + +(define + go-eval-for + ;; (:for INIT COND POST BODY). Any may be nil. + (fn (env stmt) + (let ((init (nth stmt 1)) (cnd (nth stmt 2)) + (post (nth stmt 3)) (body (nth stmt 4))) + (let ((env0 + (cond + (= init nil) env + :else (go-eval-stmt env init)))) + (cond + (go-eval-error? env0) env0 + :else (go-for-loop env0 cnd post body)))))) + +(define + go-for-loop + (fn (env cnd post body) + (let ((c + (cond + (= cnd nil) true + :else (go-eval env cnd)))) + (cond + (go-eval-error? c) c + (not c) env + :else + (let ((r + (cond + (= body nil) env + (and (list? body) (= (first body) :block)) + (go-eval-block env (nth body 1)) + :else env))) + (cond + (and (list? r) (= (first r) :return-value)) r + (= r :break) env + (= r :continue) + (let ((env1 + (cond + (= post nil) env + :else (go-eval-stmt env post)))) + (cond + (go-eval-error? env1) env1 + :else (go-for-loop env1 cnd post body))) + (go-eval-error? r) r + :else + (let ((env1 + (cond + (= post nil) r + :else (go-eval-stmt r post)))) + (cond + (go-eval-error? env1) env1 + :else (go-for-loop env1 cnd post body))))))))) + (define go-eval-stmt (fn (env stmt) @@ -372,6 +444,12 @@ (go-eval-block env (nth stmt 1)) (and (list? stmt) (= (first stmt) :if)) (go-eval-if env stmt) + (and (list? stmt) (= (first stmt) :for)) + (go-eval-for env stmt) + (and (list? stmt) (= (first stmt) :break)) :break + (and (list? stmt) (= (first stmt) :continue)) :continue + (and (list? stmt) (= (first stmt) :inc-dec)) + (go-eval-inc-dec env stmt) (and (list? stmt) (= (first stmt) :func-decl)) (go-eval-func-decl env stmt) :else @@ -389,6 +467,8 @@ (let ((r (go-eval-stmt env (first stmts)))) (cond (and (list? r) (= (first r) :return-value)) r + (= r :break) r + (= r :continue) r (go-eval-error? r) r :else (go-eval-block r (rest stmts))))))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 6841efa5..f055d787 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,12 +1,12 @@ { "language": "go", - "total_pass": 410, - "total": 410, + "total_pass": 417, + "total": 417, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"parse","pass":176,"total":176,"status":"ok"}, {"name":"types","pass":72,"total":72,"status":"ok"}, - {"name":"eval","pass":33,"total":33,"status":"ok"}, + {"name":"eval","pass":40,"total":40,"status":"ok"}, {"name":"runtime","pass":0,"total":0,"status":"pending"}, {"name":"stdlib","pass":0,"total":0,"status":"pending"}, {"name":"e2e","pass":0,"total":0,"status":"pending"} diff --git a/lib/go/scoreboard.md b/lib/go/scoreboard.md index cc41517c..1e1ba676 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,13 +1,13 @@ # Go-on-SX Scoreboard -**Total: 410 / 410 tests passing** +**Total: 417 / 417 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | | ✅ | types | 72 | 72 | -| ✅ | eval | 33 | 33 | +| ✅ | eval | 40 | 40 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | | ⬜ | e2e | 0 | 0 | diff --git a/lib/go/tests/eval.sx b/lib/go/tests/eval.sx index ffc4e5fb..4066134b 100644 --- a/lib/go/tests/eval.sx +++ b/lib/go/tests/eval.sx @@ -146,6 +146,55 @@ (go-eval env (go-parse "fib(5)"))) 5) +(go-eval-test + "for: count to 10 with sum" + (let + ((env (go-eval-program go-env-empty (list (go-parse "var sum = 0") (go-parse "for i := 0; i < 10; i++ { sum = sum + i }"))))) + (go-env-lookup env "sum")) + 45) + +(go-eval-test + "inc-dec: x++ updates env" + (let + ((env (go-eval-program (go-env-extend go-env-empty "x" 5) (list (go-parse "x++"))))) + (go-env-lookup env "x")) + 6) + +(go-eval-test + "inc-dec: x-- updates env" + (let + ((env (go-eval-program (go-env-extend go-env-empty "x" 5) (list (go-parse "x--"))))) + (go-env-lookup env "x")) + 4) + +(go-eval-test + "for: break exits the loop" + (let + ((env (go-eval-program go-env-empty (list (go-parse "var i = 0") (go-parse "for i < 100 { if i == 5 { break } ; i++ }"))))) + (go-env-lookup env "i")) + 5) + +(go-eval-test + "for: continue skips body but runs post" + (let + ((env (go-eval-program go-env-empty (list (go-parse "var sum = 0") (go-parse "for i := 0; i < 5; i++ { if i == 2 { continue } ; sum = sum + i }"))))) + (go-env-lookup env "sum")) + 8) + +(go-eval-test + "for: infinite + break with sum" + (let + ((env (go-eval-program go-env-empty (list (go-parse "var s = 0") (go-parse "var i = 1") (go-parse "for { if i > 4 { break } ; s = s + i ; i++ }"))))) + (go-env-lookup env "s")) + 10) + +(go-eval-test + "fn: iterative factorial via for-loop" + (let + ((env (go-eval-program go-env-empty (list (go-parse "func fact(n int) int { r := 1 ; for i := 2 ; i <= n ; i++ { r = r * i } ; return r }"))))) + (go-eval env (go-parse "fact(5)"))) + 120) + (define go-eval-test-summary (str "eval " go-eval-test-pass "/" go-eval-test-count)) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 23f28cc1..d3e9c560 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -270,8 +270,10 @@ Progress-log line → push `origin/loops/go`. - [x] Scaffold: env-as-value, literal decoding (decimal/hex/oct/bin with underscores), variable lookup (incl. predeclared true/false/nil), arithmetic + comparison + logical binops. eval suite at 25/25. -- [/] Statement evaluation: block / return / short-decl / assign / - var-decl / if done; for / break / continue pending. +- [x] Statement evaluation: block / return / short-decl / assign / + var-decl / if / for (all three header shapes) / break / continue / + inc-dec all done. `:break` and `:continue` propagate as sentinel + keywords through `go-eval-block` until `go-for-loop` catches them. - [ ] Variables as mutable cells; pointer semantics: `&x` returns the cell, `*p` dereferences. - [ ] Slices: triple (length, capacity, backing-vector). `append` @@ -283,7 +285,7 @@ Progress-log line → push `origin/loops/go`. - [ ] Channels: stub (Phase 5 wires them). - Tests: arithmetic, control flow, recursion, closures, slices, maps, structs, methods, pointer semantics, multiple-return. -- **Acceptance:** eval/ suite at 80+ tests. Current: 33/33. No concurrency yet. +- **Acceptance:** eval/ suite at 80+ tests. Current: 40/40. No concurrency yet. ### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/scheduler/ — that @@ -567,6 +569,16 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`. _Newest first. Append one dated entry per commit._ +- 2026-05-27 — Phase 4 cont.: for-loops, break, continue, inc-dec. + `go-eval-for` handles all three for-header shapes (infinite, while- + like, C-style) including init+post stmts and missing-cond defaulting + to true. `:break` and `:continue` propagate as keyword sentinels + through `go-eval-block` (alongside the existing `:return-value` + sentinel) until `go-for-loop` catches them — break exits, continue + runs post and re-loops. Inc-dec `x++`/`x--` updates env via the + same shadowing model as assign. **Iterative `fact(5) = 120` and the + classic for-loop sum-to-9 (= 45) both evaluate.** +7 tests, eval + 40/40, total 417/417. `[nothing]`. - 2026-05-27 — Phase 4 cont.: statements + function application. `go-eval-stmt` handles `:return` (propagates a `:return-value` sentinel up through blocks), `:var-decl`, `:short-decl`, `:assign`