diff --git a/lib/go/conformance.sh b/lib/go/conformance.sh index c7d1127a..b86930e3 100755 --- a/lib/go/conformance.sh +++ b/lib/go/conformance.sh @@ -30,6 +30,7 @@ SUITES=( "lex|go-test-pass|go-test-count" "parse|go-parse-test-pass|go-parse-test-count" "types|go-types-test-pass|go-types-test-count" + "eval|go-eval-test-pass|go-eval-test-count" ) cat > "$TMPFILE" <<'EPOCHS' @@ -40,9 +41,11 @@ cat > "$TMPFILE" <<'EPOCHS' (load "lib/go/lex.sx") (load "lib/go/parse.sx") (load "lib/go/types.sx") +(load "lib/go/eval.sx") (load "lib/go/tests/lex.sx") (load "lib/go/tests/parse.sx") (load "lib/go/tests/types.sx") +(load "lib/go/tests/eval.sx") EPOCHS idx=0 @@ -107,7 +110,6 @@ cat > lib/go/scoreboard.json < lib/go/scoreboard.md <= i (len v)) + acc + (= (nth v i) "_") + (grf-loop (+ i 1) acc) + :else (let + ((d (go-hex-digit-value (nth v i)))) + (cond + (or (< d 0) (>= d radix)) + acc + :else (grf-loop (+ i 1) (+ (* acc radix) d))))))) + (grf-loop start 0))) + +(define + go-parse-int-literal + (fn + (v) + (cond + (and + (>= (len v) 2) + (= (nth v 0) "0") + (or (= (nth v 1) "x") (= (nth v 1) "X"))) + (go-parse-radix-from v 2 16) + (and + (>= (len v) 2) + (= (nth v 0) "0") + (or (= (nth v 1) "b") (= (nth v 1) "B"))) + (go-parse-radix-from v 2 2) + (and + (>= (len v) 2) + (= (nth v 0) "0") + (or (= (nth v 1) "o") (= (nth v 1) "O"))) + (go-parse-radix-from v 2 8) + :else (go-parse-radix-from v 0 10)))) + +(define + go-eval-literal + (fn + (v) + (let + ((k (go-classify-literal-string v))) + (cond (= k :int) (go-parse-int-literal v) (= k :string) v :else v)))) + +;; ── binary ops ─────────────────────────────────────────────────── + +(define + go-eval-binop + (fn + (op l r) + (cond + (= op "+") + (+ l r) + (= op "-") + (- l r) + (= op "*") + (* l r) + (= op "/") + (/ l r) + (= op "==") + (= l r) + (= op "!=") + (not (= l r)) + (= op "<") + (< l r) + (= op "<=") + (<= l r) + (= op ">") + (> l r) + (= op ">=") + (>= l r) + (= op "&&") + (and l r) + (= op "||") + (or l r) + :else (list :eval-error :unsupported-binop op)))) + +;; ── main eval ──────────────────────────────────────────────────── + +(define + go-eval + (fn + (env expr) + (cond + (and (list? expr) (= (first expr) :literal)) + (go-eval-literal (nth expr 1)) + (and (list? expr) (= (first expr) :var)) + (let + ((name (nth expr 1))) + (cond + (= 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)))) + :else (list :eval-error :unsupported-eval expr)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index b77942fd..f986ce45 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,12 +1,12 @@ { "language": "go", - "total_pass": 377, - "total": 377, + "total_pass": 402, + "total": 402, "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":0,"total":0,"status":"pending"}, + {"name":"eval","pass":25,"total":25,"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 427ab404..c1eaa5c0 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,13 +1,13 @@ # Go-on-SX Scoreboard -**Total: 377 / 377 tests passing** +**Total: 402 / 402 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | | ✅ | types | 72 | 72 | -| ⬜ | eval | 0 | 0 | +| ✅ | eval | 25 | 25 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | | ⬜ | e2e | 0 | 0 | diff --git a/lib/go/tests/eval.sx b/lib/go/tests/eval.sx new file mode 100644 index 00000000..ff49ef13 --- /dev/null +++ b/lib/go/tests/eval.sx @@ -0,0 +1,95 @@ +;; Go evaluator tests. + +(define go-eval-test-count 0) +(define go-eval-test-pass 0) +(define go-eval-test-fails (list)) + +(define + go-eval-test + (fn + (name actual expected) + (set! go-eval-test-count (+ go-eval-test-count 1)) + (if + (= actual expected) + (set! go-eval-test-pass (+ go-eval-test-pass 1)) + (append! go-eval-test-fails {:name name :expected expected :actual actual})))) + +(define gtev (fn (env src) (go-eval env (go-parse src)))) + +;; ── env ────────────────────────────────────────────────────────── +(go-eval-test + "env: empty lookup returns nil" + (go-env-lookup go-env-empty "x") + nil) + +(go-eval-test + "env: extend then lookup" + (go-env-lookup (go-env-extend go-env-empty "x" 42) "x") + 42) + +;; ── literals ──────────────────────────────────────────────────── +(go-eval-test "lit: 42 → 42" (gtev go-env-empty "42") 42) + +(go-eval-test "lit: 0 → 0" (gtev go-env-empty "0") 0) + +(go-eval-test "lit: 0xFF → 255" (gtev go-env-empty "0xFF") 255) + +(go-eval-test "lit: 0b1010 → 10" (gtev go-env-empty "0b1010") 10) + +(go-eval-test "lit: 0o17 → 15" (gtev go-env-empty "0o17") 15) + +(go-eval-test + "lit: underscore separator 1_000 → 1000" + (gtev go-env-empty "1_000") + 1000) + +(go-eval-test "lit: string" (gtev go-env-empty "\"hello\"") "hello") + +;; ── predeclared ───────────────────────────────────────────────── +(go-eval-test "var: true" (gtev go-env-empty "true") true) +(go-eval-test "var: false" (gtev go-env-empty "false") false) +(go-eval-test "var: nil" (gtev go-env-empty "nil") nil) + +;; ── variable lookup ───────────────────────────────────────────── +(go-eval-test + "var: bound x → 5" + (go-eval (go-env-extend go-env-empty "x" 5) (go-parse "x")) + 5) + +(go-eval-test + "var: unbound y → :eval-error" + (gtev go-env-empty "y") + (list :eval-error :unbound "y")) + +;; ── binary ops ───────────────────────────────────────────────── +(go-eval-test "binop: 1 + 2 → 3" (gtev go-env-empty "1 + 2") 3) +(go-eval-test "binop: 10 - 4 → 6" (gtev go-env-empty "10 - 4") 6) +(go-eval-test "binop: 3 * 7 → 21" (gtev go-env-empty "3 * 7") 21) +(go-eval-test "binop: 42 / 7 → 6" (gtev go-env-empty "42 / 7") 6) +(go-eval-test + "binop: 2 + 3 * 4 → 14 (prec)" + (gtev go-env-empty "2 + 3 * 4") + 14) +(go-eval-test + "binop: a + b uses env" + (go-eval + (go-env-extend (go-env-extend go-env-empty "a" 3) "b" 4) + (go-parse "a + b")) + 7) + +(go-eval-test "binop: 1 < 2 → true" (gtev go-env-empty "1 < 2") true) +(go-eval-test "binop: 5 == 5 → true" (gtev go-env-empty "5 == 5") true) +(go-eval-test "binop: 5 != 5 → false" (gtev go-env-empty "5 != 5") false) +(go-eval-test + "binop: true && false → false" + (gtev go-env-empty "true && false") + false) +(go-eval-test + "binop: false || true → true" + (gtev go-env-empty "false || true") + true) + +;; ── report ────────────────────────────────────────────────────── +(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 2f562668..4155d8e4 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -213,7 +213,7 @@ Progress-log line → push `origin/loops/go`. **Phase 2 complete.** Type-switch is the one syntactic shape still deferred to a follow-up; it doesn't gate Phase 3. -### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ +### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ✅ - [x] Scaffold: `go-synth` / `go-check` skeletons; context-as-value (`go-ctx-empty` / `-extend` / `-lookup` / `-extend-field`); predeclared `true`/`false`/`nil`; structural type equality. @@ -255,7 +255,9 @@ Progress-log line → push `origin/loops/go`. value and pointer receivers). `go-iface-satisfies?` walks an interface's `:method` elements and looks each up; partial sets and arity-mismatches fail. Embedded interfaces deferred. -- [ ] Short variable declaration `:=` (synth RHS into LHS bindings). +- [x] Short variable declaration `:=` (synth RHS into LHS bindings). + Handled inline by `go-check-short-decl` since the decl-checking + slice; works both at top-level and inside `for`/`if` init clauses. - Defer: generics (Phase 7), full conversion rules, type assertions, type switches. - **Acceptance:** types/ suite at 60+ tests. **Bar crossed: 72/72.** @@ -265,19 +267,22 @@ Progress-log line → push `origin/loops/go`. cross-language record. ### Phase 4 — Tree-walk evaluator (`lib/go/eval.sx`) ⬜ -- AST-walking interpreter over CEK. Each Go statement maps to one step - function (precedent: `step-sf-if` etc. in spec/evaluator.sx). -- Variables: 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 method table. -- Functions: closures over enclosing scope; multiple return values. -- Channels: stub (Phase 5 wires them). +- [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. +- [ ] 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. +- [ ] 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. No concurrency yet. +- **Acceptance:** eval/ suite at 80+ tests. Current: 25/25. No concurrency yet. ### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/scheduler/ — that @@ -561,6 +566,19 @@ 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 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 + shape as ctx but bound to runtime values), literal decoding for + decimal/hex/oct/bin int literals (with underscores), variable lookup, + predeclared `true`/`false`/`nil`, and the full set of arithmetic / + comparison / logical binops via `go-eval-binop`. Hex/oct/bin parsing + via `go-hex-digit-value` (explicit char-equality dispatch since SX's + nth-on-string returns single-char strings, not numeric codes — + cleaner than the char-arithmetic the kernel ports use). eval suite + 25/25, total 402/402. `[nothing]` — pure Go eval mechanics, the + cross-language insights are about type-checking which is in the + sister-plan diary. - 2026-05-27 — Phase 3 cont.: **interface satisfaction** — the headline Go-distinguishing typing feature this loop set out to validate. Method decls record under `#method/TYPE-NAME/METHOD-NAME` keys in