diff --git a/lib/go/eval.sx b/lib/go/eval.sx index 567abdca..dee1d574 100644 --- a/lib/go/eval.sx +++ b/lib/go/eval.sx @@ -174,6 +174,238 @@ ;; ── main eval ──────────────────────────────────────────────────── +(define + go-eval-binop-ops + (list "+" "-" "*" "/" "==" "!=" "<" "<=" ">" ">=" "&&" "||")) + +(define + go-is-eval-binop? + (fn (head args) + (and (list? head) (= (first head) :var) + (= (len args) 2) + (some (fn (op) (= op (nth head 1))) go-eval-binop-ops)))) + +(define + go-eval-args + ;; Returns a list of arg values or a (:eval-error ...). + (fn (env args) + (cond + (or (= args nil) (= (len args) 0)) (list) + :else + (let ((v (go-eval env (first args)))) + (cond + (go-eval-error? v) v + :else + (let ((rest-vs (go-eval-args env (rest args)))) + (cond + (go-eval-error? rest-vs) rest-vs + :else (cons v rest-vs)))))))) + +(define + go-flatten-param-names + ;; PARAMS is a list of (:field NAMES TYPE) groups; return a flat name list. + (fn (params) + (cond + (or (= params nil) (= (len params) 0)) (list) + :else + (let ((field (first params))) + (let ((names (nth field 1))) + (go-name-concat names (go-flatten-param-names (rest params)))))))) + +(define + go-name-concat + (fn (a b) + (cond + (= (len a) 0) b + :else (cons (first a) (go-name-concat (rest a) b))))) + +(define + go-bind-names + (fn (env names vals) + (cond + (= (len names) 0) env + :else + (go-bind-names + (go-env-extend env (first names) (first vals)) + (rest names) (rest vals))))) + +(define + go-eval-call + ;; Apply a callable VAL to ARG-EXPRS in CALLER-ENV. Result is the + ;; function's return value or a (:eval-error ...). + ;; + ;; Closure semantics: the function value carries no captured env in v0 + ;; (dynamic scope wrt outer bindings). Recursion at top level works + ;; because the calling env already has the function bound. Nested + ;; lexical closures arrive in a later slice. + (fn (caller-env callee-val args) + (cond + (not (and (list? callee-val) (= (first callee-val) :go-fn))) + (list :eval-error :not-callable callee-val) + :else + (let ((params (nth callee-val 1)) (body (nth callee-val 2))) + (let ((arg-vals (go-eval-args caller-env args))) + (cond + (go-eval-error? arg-vals) arg-vals + :else + (let ((param-names (go-flatten-param-names params))) + (cond + (not (= (len param-names) (len arg-vals))) + (list :eval-error :arity-mismatch + (len param-names) (len arg-vals)) + :else + (let ((call-env + (go-bind-names caller-env param-names arg-vals))) + (cond + (= body nil) nil + (and (list? body) (= (first body) :block)) + (let ((r (go-eval-block call-env (nth body 1)))) + (cond + (and (list? r) (= (first r) :return-value)) + (nth r 1) + (go-eval-error? r) r + :else nil)) + :else nil)))))))))) + +(define + go-eval-var-decl + ;; (:var-decl (:field NAMES TYPE) EXPRS) — bind each NAME to either + ;; the corresponding EXPR's value or nil (zero-init when no EXPRS). + (fn (env stmt) + (let ((field (nth stmt 1)) (exprs (nth stmt 2))) + (let ((names (nth field 1))) + (cond + (or (= exprs nil) (= (len exprs) 0)) + (go-bind-names env names + (go-zeros (len names))) + :else + (let ((vals (go-eval-args env exprs))) + (cond + (go-eval-error? vals) vals + :else (go-bind-names env names vals)))))))) + +(define + go-zeros (fn (n) (cond (<= n 0) (list) :else (cons nil (go-zeros (- n 1)))))) + +(define + go-eval-short-decl + ;; (:short-decl LHS-LIST EXPRS) — LHS list of (:var NAME) nodes. + (fn (env stmt) + (let ((lhs-list (nth stmt 1)) (exprs (nth stmt 2))) + (let ((names + (map (fn (lhs) + (cond + (and (list? lhs) (= (first lhs) :var)) + (nth lhs 1) + :else :unknown)) + lhs-list))) + (let ((vals (go-eval-args env exprs))) + (cond + (go-eval-error? vals) vals + :else (go-bind-names env names vals))))))) + +(define + go-eval-assign + ;; v0: assignment shadows via env extension (immutable env model). + ;; Mutation through closures deferred. + (fn (env stmt) + (let ((lhs-list (nth stmt 1)) (rhs-list (nth stmt 2))) + (let ((vals (go-eval-args env rhs-list))) + (cond + (go-eval-error? vals) vals + :else + (go-eval-assign-pairs env lhs-list vals)))))) + +(define + go-eval-assign-pairs + (fn (env lhs-list vals) + (cond + (= (len lhs-list) 0) env + :else + (let ((lhs (first lhs-list))) + (cond + (and (list? lhs) (= (first lhs) :var)) + (go-eval-assign-pairs + (go-env-extend env (nth lhs 1) (first vals)) + (rest lhs-list) (rest vals)) + :else (list :eval-error :unsupported-lhs lhs)))))) + +(define + go-eval-if + (fn (env stmt) + (let ((cnd (nth stmt 1)) (then (nth stmt 2)) (els (nth stmt 3))) + (let ((c (go-eval env cnd))) + (cond + (go-eval-error? c) c + c (go-eval-stmt env then) + (not (= els nil)) (go-eval-stmt env els) + :else env))))) + +(define + go-eval-func-decl + (fn (env stmt) + (let ((name (nth stmt 1)) (params (nth stmt 2)) + (body (nth stmt 4))) + (go-env-extend env name (list :go-fn params body))))) + +(define + go-eval-stmt + (fn (env stmt) + (cond + (and (list? stmt) (= (first stmt) :return)) + (let ((exprs (nth stmt 1))) + (cond + (or (= exprs nil) (= (len exprs) 0)) + (list :return-value nil) + :else + (let ((v (go-eval env (first exprs)))) + (cond + (go-eval-error? v) v + :else (list :return-value v))))) + (and (list? stmt) (= (first stmt) :var-decl)) + (go-eval-var-decl env stmt) + (and (list? stmt) (= (first stmt) :short-decl)) + (go-eval-short-decl env stmt) + (and (list? stmt) (= (first stmt) :assign)) + (go-eval-assign env stmt) + (and (list? stmt) (= (first stmt) :block)) + (go-eval-block env (nth stmt 1)) + (and (list? stmt) (= (first stmt) :if)) + (go-eval-if env stmt) + (and (list? stmt) (= (first stmt) :func-decl)) + (go-eval-func-decl env stmt) + :else + (let ((v (go-eval env stmt))) + (cond + (go-eval-error? v) v + :else env))))) + +(define + go-eval-block + (fn (env stmts) + (cond + (or (= stmts nil) (= (len stmts) 0)) env + :else + (let ((r (go-eval-stmt env (first stmts)))) + (cond + (and (list? r) (= (first r) :return-value)) r + (go-eval-error? r) r + :else (go-eval-block r (rest stmts))))))) + +(define + go-eval-program + ;; Evaluate a sequence of top-level forms in ENV. Returns the final + ;; env (or :eval-error / :return-value if either propagates). + (fn (env forms) + (cond + (or (= forms nil) (= (len forms) 0)) env + :else + (let ((r (go-eval-stmt env (first forms)))) + (cond + (and (list? r) (= (first r) :return-value)) r + (go-eval-error? r) r + :else (go-eval-program r (rest forms))))))) + (define go-eval (fn @@ -185,31 +417,26 @@ (let ((name (nth expr 1))) (cond - (= name "true") - true - (= name "false") - false - (= name "nil") - nil + (= name "true") true + (= name "false") false + (= name "nil") nil :else (let ((v (go-env-lookup env name))) (cond (= v nil) (list :eval-error :unbound name) :else v)))) - (and - (list? expr) - (= (first expr) :app) - (list? (nth expr 1)) - (= (first (nth expr 1)) :var) - (= (len (nth expr 2)) 2)) - (let - ((op (nth (nth expr 1) 1)) - (args (nth expr 2))) - (let - ((lv (go-eval env (first args))) - (rv (go-eval env (nth args 1)))) - (cond - (go-eval-error? lv) - lv - (go-eval-error? rv) - rv - :else (go-eval-binop op lv rv)))) + (and (list? expr) (= (first expr) :app)) + (let ((head (nth expr 1)) (args (nth expr 2))) + (cond + (go-is-eval-binop? head args) + (let ((op (nth head 1))) + (let ((lv (go-eval env (first args))) + (rv (go-eval env (nth args 1)))) + (cond + (go-eval-error? lv) lv + (go-eval-error? rv) rv + :else (go-eval-binop op lv rv)))) + :else + (let ((callee (go-eval env head))) + (cond + (go-eval-error? callee) callee + :else (go-eval-call env callee args))))) :else (list :eval-error :unsupported-eval expr)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index f986ce45..6841efa5 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,12 +1,12 @@ { "language": "go", - "total_pass": 402, - "total": 402, + "total_pass": 410, + "total": 410, "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":25,"total":25,"status":"ok"}, + {"name":"eval","pass":33,"total":33,"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 c1eaa5c0..cc41517c 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,13 +1,13 @@ # Go-on-SX Scoreboard -**Total: 402 / 402 tests passing** +**Total: 410 / 410 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | | ✅ | types | 72 | 72 | -| ✅ | eval | 25 | 25 | +| ✅ | eval | 33 | 33 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | | ⬜ | e2e | 0 | 0 | diff --git a/lib/go/tests/eval.sx b/lib/go/tests/eval.sx index ff49ef13..ffc4e5fb 100644 --- a/lib/go/tests/eval.sx +++ b/lib/go/tests/eval.sx @@ -90,6 +90,62 @@ true) ;; ── report ────────────────────────────────────────────────────── +(go-eval-test + "var-decl: var x = 5 — env has x=5" + (go-env-lookup + (go-eval-program go-env-empty (list (go-parse "var x = 5"))) + "x") + 5) + +(go-eval-test + "short-decl: a, b := 3, 4 — env has both" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a, b := 3, 4"))))) + (list (go-env-lookup env "a") (go-env-lookup env "b"))) + (list 3 4)) + +(go-eval-test + "assign: x = 5 then x → 5" + (let + ((env (go-eval-program (go-env-extend go-env-empty "x" 1) (list (go-parse "x = 5"))))) + (go-env-lookup env "x")) + 5) + +(go-eval-test + "if: true branch evaluates" + (let + ((env (go-eval-program (go-env-extend go-env-empty "x" 0) (list (go-parse "if true { x = 1 }"))))) + (go-env-lookup env "x")) + 1) + +(go-eval-test + "if-else: false → else branch" + (let + ((env (go-eval-program (go-env-extend go-env-empty "x" 0) (list (go-parse "if false { x = 1 } else { x = 2 }"))))) + (go-env-lookup env "x")) + 2) + +(go-eval-test + "fn: define + call — double(7) = 14" + (let + ((env (go-eval-program go-env-empty (list (go-parse "func double(x int) int { return x * 2 }"))))) + (go-eval env (go-parse "double(7)"))) + 14) + +(go-eval-test + "fn: add(2, 3) = 5" + (let + ((env (go-eval-program go-env-empty (list (go-parse "func add(x, y int) int { return x + y }"))))) + (go-eval env (go-parse "add(2, 3)"))) + 5) + +(go-eval-test + "fn: recursive fib(5) = 5" + (let + ((env (go-eval-program go-env-empty (list (go-parse "func fib(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) }"))))) + (go-eval env (go-parse "fib(5)"))) + 5) + (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 4155d8e4..23f28cc1 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -270,19 +270,20 @@ 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 / for / break / continue. +- [/] Statement evaluation: block / return / short-decl / assign / + var-decl / if done; for / break / continue pending. - [ ] Variables as mutable cells; pointer semantics: `&x` returns the cell, `*p` dereferences. - [ ] Slices: triple (length, capacity, backing-vector). `append` honours capacity-grow per spec. - [ ] Maps: SX dict + key-type metadata. - [ ] Structs: SX dict + type tag. Methods looked up via type's table. -- [ ] Functions: closures over enclosing scope; multiple return values. +- [/] Functions: top-level definition + call (incl. recursion via the + calling env). Lexical closures and multiple return values pending. - [ ] 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: 25/25. No concurrency yet. +- **Acceptance:** eval/ suite at 80+ tests. Current: 33/33. No concurrency yet. ### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/scheduler/ — that @@ -566,6 +567,18 @@ 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.: statements + function application. + `go-eval-stmt` handles `:return` (propagates a `:return-value` + sentinel up through blocks), `:var-decl`, `:short-decl`, `:assign` + (immutable-env shadowing), `:block`, `:if`/`:else`, and `:func-decl` + (binds a `:go-fn` value). `go-eval-call` extends the caller's env + with params → arg values, runs the body block, unwraps the return. + **Recursive `fib(5) = 5` evaluates correctly** — recursion works + because top-level funcs are bound in the calling env before any + recursive call happens; the func value carries no captured env in + v0 (dynamic-scope-ish), so true lexical closures wait for a later + slice. +8 tests, eval 33/33, total 410/410. `[nothing]` — pure eval + composition. - 2026-05-27 — **Phase 3 ticked; Phase 4 scaffold.** Short-decl `:=` marked done (was already covered by go-check-short-decl from the decl-checking iteration). New `lib/go/eval.sx`: env-as-value (same