diff --git a/lib/go/eval.sx b/lib/go/eval.sx index 2014c945..0ffef19f 100644 --- a/lib/go/eval.sx +++ b/lib/go/eval.sx @@ -13,6 +13,16 @@ (define go-env-empty (list)) +(define + go-env-builtins + ;; A starter env containing the Go builtins eval understands. + ;; Tests can call (go-env-builtins) instead of go-env-empty when they + ;; need len/append/print. + (list + (list "len" (list :go-builtin "len")) + (list "append" (list :go-builtin "append")) + (list "print" (list :go-builtin "print")))) + (define go-env-lookup (fn @@ -229,10 +239,129 @@ (go-env-extend env (first names) (first vals)) (rest names) (rest vals))))) +(define + go-eval-builtin + ;; Run Go's predeclared builtins (len, append, print). args are + ;; expressions; we eval them in the caller env then dispatch on NAME. + (fn (caller-env name args) + (let ((vals (go-eval-args caller-env args))) + (cond + (go-eval-error? vals) vals + (= name "len") + (cond + (not (= (len vals) 1)) + (list :eval-error :builtin-arity name 1 (len vals)) + :else + (let ((arg (first vals))) + (cond + (and (list? arg) (= (first arg) :go-slice)) (len (nth arg 1)) + (string? arg) (len arg) + :else (list :eval-error :len-not-applicable arg)))) + (= name "append") + (cond + (< (len vals) 1) + (list :eval-error :builtin-arity name 1 (len vals)) + :else + (let ((slc (first vals)) (extra (rest vals))) + (cond + (and (list? slc) (= (first slc) :go-slice)) + (list :go-slice (go-name-concat (nth slc 1) extra)) + :else (list :eval-error :append-not-slice slc)))) + (= name "print") + nil ;; v0: silent. Real impl would write to stdout. + :else (list :eval-error :unknown-builtin name))))) + +(define + go-extract-composite-vals + ;; For slice/array composite literals: read each element's value + ;; (skipping :kv keys, only using values for Go's index-keyed shorthand). + (fn (env elems) + (cond + (or (= elems nil) (= (len elems) 0)) (list) + :else + (let ((e (first elems))) + (let ((v + (cond + (and (list? e) (= (first e) :kv)) + (go-eval env (nth e 2)) + :else (go-eval env e)))) + (cond + (go-eval-error? v) v + :else + (let ((rest-vs (go-extract-composite-vals env (rest elems)))) + (cond + (go-eval-error? rest-vs) rest-vs + :else (cons v rest-vs))))))))) + +(define + go-eval-composite + ;; (:composite TYPE-OR-EXPR ELEMS). v0 supports slice/array; map/struct + ;; later. + (fn (env expr) + (let ((ty (nth expr 1)) (elems (nth expr 2))) + (cond + (and (list? ty) + (or (= (first ty) :ty-slice) (= (first ty) :ty-array))) + (let ((vals (go-extract-composite-vals env elems))) + (cond + (go-eval-error? vals) vals + :else (list :go-slice vals))) + :else (list :eval-error :unsupported-composite ty))))) + +(define + go-eval-index + ;; (:index OBJ IDX-EXPR). v0: slice indexing only. + (fn (env expr) + (let ((obj (go-eval env (nth expr 1))) + (idx (go-eval env (nth expr 2)))) + (cond + (go-eval-error? obj) obj + (go-eval-error? idx) idx + (and (list? obj) (= (first obj) :go-slice)) + (let ((elems (nth obj 1))) + (cond + (or (< idx 0) (>= idx (len elems))) + (list :eval-error :index-out-of-range idx (len elems)) + :else (nth elems idx))) + :else (list :eval-error :not-indexable obj))))) + +(define + go-eval-slice + ;; (:slice OBJ LOW HIGH MAX). v0: two-index slice on go-slice values. + (fn (env expr) + (let ((obj (go-eval env (nth expr 1))) + (low (cond + (= (nth expr 2) nil) 0 + :else (go-eval env (nth expr 2)))) + (high-expr (nth expr 3))) + (cond + (go-eval-error? obj) obj + (go-eval-error? low) low + (not (and (list? obj) (= (first obj) :go-slice))) + (list :eval-error :not-sliceable obj) + :else + (let ((elems (nth obj 1))) + (let ((high + (cond + (= high-expr nil) (len elems) + :else (go-eval env high-expr)))) + (cond + (go-eval-error? high) high + :else + (list :go-slice (go-list-slice elems low high))))))))) + +(define + go-list-slice + (fn (lst low high) + (cond + (>= low high) (list) + (>= low (len lst)) (list) + :else + (cons (nth lst low) + (go-list-slice lst (+ low 1) high))))) + (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 @@ -240,6 +369,8 @@ ;; lexical closures arrive in a later slice. (fn (caller-env callee-val args) (cond + (and (list? callee-val) (= (first callee-val) :go-builtin)) + (go-eval-builtin caller-env (nth callee-val 1) args) (not (and (list? callee-val) (= (first callee-val) :go-fn))) (list :eval-error :not-callable callee-val) :else @@ -503,6 +634,12 @@ :else (let ((v (go-env-lookup env name))) (cond (= v nil) (list :eval-error :unbound name) :else v)))) + (and (list? expr) (= (first expr) :composite)) + (go-eval-composite env expr) + (and (list? expr) (= (first expr) :index)) + (go-eval-index env expr) + (and (list? expr) (= (first expr) :slice)) + (go-eval-slice env expr) (and (list? expr) (= (first expr) :app)) (let ((head (nth expr 1)) (args (nth expr 2))) (cond diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index f055d787..a84ab350 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,12 +1,12 @@ { "language": "go", - "total_pass": 417, - "total": 417, + "total_pass": 427, + "total": 427, "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":40,"total":40,"status":"ok"}, + {"name":"eval","pass":50,"total":50,"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 1e1ba676..a13a7213 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,13 +1,13 @@ # Go-on-SX Scoreboard -**Total: 417 / 417 tests passing** +**Total: 427 / 427 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | | ✅ | parse | 176 | 176 | | ✅ | types | 72 | 72 | -| ✅ | eval | 40 | 40 | +| ✅ | eval | 50 | 50 | | ⬜ | runtime | 0 | 0 | | ⬜ | stdlib | 0 | 0 | | ⬜ | e2e | 0 | 0 | diff --git a/lib/go/tests/eval.sx b/lib/go/tests/eval.sx index 4066134b..c73e8885 100644 --- a/lib/go/tests/eval.sx +++ b/lib/go/tests/eval.sx @@ -195,6 +195,73 @@ (go-eval env (go-parse "fact(5)"))) 120) +(go-eval-test + "slice: []int{1,2,3} → :go-slice" + (gtev go-env-empty "[]int{1, 2, 3}") + (list :go-slice (list 1 2 3))) + +(go-eval-test + "index: a[0] = 10, a[2] = 30" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30}"))))) + (list (go-eval env (go-parse "a[0]")) (go-eval env (go-parse "a[2]")))) + (list 10 30)) + +(go-eval-test + "index: out-of-range error" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2}"))))) + (go-eval env (go-parse "a[5]"))) + (list :eval-error :index-out-of-range 5 2)) + +(go-eval-test + "builtin: len(slice) = 3" + (let + ((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3}"))))) + (go-eval env (go-parse "len(a)"))) + 3) + +(go-eval-test + "builtin: len(string)" + (go-eval go-env-builtins (go-parse "len(\"hello\")")) + 5) + +(go-eval-test + "builtin: append(a, 4, 5)" + (let + ((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3}"))))) + (go-eval env (go-parse "append(a, 4, 5)"))) + (list + :go-slice (list 1 2 3 4 5))) + +(go-eval-test + "slice expr: a[1:3]" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30, 40}"))))) + (go-eval env (go-parse "a[1:3]"))) + (list :go-slice (list 20 30))) + +(go-eval-test + "slice expr: a[:2] (omitted low)" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2, 3, 4}"))))) + (go-eval env (go-parse "a[:2]"))) + (list :go-slice (list 1 2))) + +(go-eval-test + "slice expr: a[2:] (omitted high)" + (let + ((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2, 3, 4}"))))) + (go-eval env (go-parse "a[2:]"))) + (list :go-slice (list 3 4))) + +(go-eval-test + "fn: sum slice via for-loop with len + index" + (let + ((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3, 4, 5}") (go-parse "sum := 0") (go-parse "for i := 0; i < len(a); i++ { sum = sum + a[i] }"))))) + (go-env-lookup env "sum")) + 15) + (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 d3e9c560..ef7317a5 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -276,8 +276,12 @@ Progress-log line → push `origin/loops/go`. 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` - honours capacity-grow per spec. +- [/] Slices: v0 represents a slice as `(list :go-slice ELEMS)` — + simpler than the full (length, capacity, backing-vector) triple. + Composite-literal `[]T{...}` evaluates to a `:go-slice`; `a[i]` + indexes, `a[low:high]` slices, `len(a)` returns count, `append(a, ...)` + extends. The full triple with capacity-grow comes in a later + slice when programs need it. - [ ] Maps: SX dict + key-type metadata. - [ ] Structs: SX dict + type tag. Methods looked up via type's table. - [/] Functions: top-level definition + call (incl. recursion via the @@ -285,7 +289,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: 40/40. No concurrency yet. +- **Acceptance:** eval/ suite at 80+ tests. Current: 50/50. No concurrency yet. ### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/scheduler/ — that @@ -569,6 +573,17 @@ 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.: slice values + index/slice exprs + the + `len`/`append`/`print` builtins. Slice representation is + `(list :go-slice ELEMS)` for v0 (deferring the full length/cap/ + backing-vector triple). `go-eval-composite` handles `[]T{...}` / + `[N]T{...}` literals (maps next). `go-eval-index` returns the i-th + element with bounds-check. `go-eval-slice` handles two-index slicing + with omitted low/high. New `go-env-builtins` starter env binds the + three builtins as `(:go-builtin NAME)` values; `go-eval-call` + recognises them and dispatches to `go-eval-builtin`. **Summing a + slice via `for i := 0; i < len(a); i++ { sum = sum + a[i] }` works + end-to-end.** +10 tests, eval 50/50, total 427/427. `[nothing]`. - 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