go: eval.sx — slices + index + slice expr + len/append builtins + 10 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s

Phase 4 cont. Adds runtime support for Go's slice type.

Slice representation: (list :go-slice ELEMS) — a simple wrapper around
a list of element values. v0 deferring the full
(length, capacity, backing-vector) triple from the Go spec until
programs need it.

  go-eval-composite      → for (:composite TYPE-OR-EXPR ELEMS) where
                            TYPE is :ty-slice / :ty-array, eval each
                            element (handling :kv index-keyed
                            shorthand by taking only the value) and
                            wrap in :go-slice.
  go-eval-index          → (:index OBJ IDX). Bounds-checked; out-of-
                            range returns (:eval-error :index-out-of-range).
  go-eval-slice          → (:slice OBJ LOW HIGH MAX). Two-index slice
                            with omitted low → 0, omitted high → len.
                            Returns a new :go-slice.
  go-list-slice          → primitive list-slicing helper.

Builtins live in a new starter env go-env-builtins:
  len(slice|string)      → count
  append(slice, ...x)    → new slice with x appended
  print(...)             → no-op in v0

Builtins are bound as (:go-builtin NAME); go-eval-call recognises the
shape and routes to go-eval-builtin instead of go-eval-fn.

**Summing a slice via the canonical Go for-loop works end-to-end:**

  a := []int{1, 2, 3, 4, 5}
  sum := 0
  for i := 0; i < len(a); i++ {
    sum = sum + a[i]
  }
  // sum == 15

eval 50/50, total 427/427.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 21:28:12 +00:00
parent a019aa1edc
commit ab04ec1cf7
5 changed files with 229 additions and 10 deletions

View File

@@ -13,6 +13,16 @@
(define go-env-empty (list)) (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 (define
go-env-lookup go-env-lookup
(fn (fn
@@ -229,10 +239,129 @@
(go-env-extend env (first names) (first vals)) (go-env-extend env (first names) (first vals))
(rest names) (rest 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 (define
go-eval-call 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 ;; Closure semantics: the function value carries no captured env in v0
;; (dynamic scope wrt outer bindings). Recursion at top level works ;; (dynamic scope wrt outer bindings). Recursion at top level works
@@ -240,6 +369,8 @@
;; lexical closures arrive in a later slice. ;; lexical closures arrive in a later slice.
(fn (caller-env callee-val args) (fn (caller-env callee-val args)
(cond (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))) (not (and (list? callee-val) (= (first callee-val) :go-fn)))
(list :eval-error :not-callable callee-val) (list :eval-error :not-callable callee-val)
:else :else
@@ -503,6 +634,12 @@
:else (let :else (let
((v (go-env-lookup env name))) ((v (go-env-lookup env name)))
(cond (= v nil) (list :eval-error :unbound name) :else v)))) (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)) (and (list? expr) (= (first expr) :app))
(let ((head (nth expr 1)) (args (nth expr 2))) (let ((head (nth expr 1)) (args (nth expr 2)))
(cond (cond

View File

@@ -1,12 +1,12 @@
{ {
"language": "go", "language": "go",
"total_pass": 417, "total_pass": 427,
"total": 417, "total": 427,
"suites": [ "suites": [
{"name":"lex","pass":129,"total":129,"status":"ok"}, {"name":"lex","pass":129,"total":129,"status":"ok"},
{"name":"parse","pass":176,"total":176,"status":"ok"}, {"name":"parse","pass":176,"total":176,"status":"ok"},
{"name":"types","pass":72,"total":72,"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":"runtime","pass":0,"total":0,"status":"pending"},
{"name":"stdlib","pass":0,"total":0,"status":"pending"}, {"name":"stdlib","pass":0,"total":0,"status":"pending"},
{"name":"e2e","pass":0,"total":0,"status":"pending"} {"name":"e2e","pass":0,"total":0,"status":"pending"}

View File

@@ -1,13 +1,13 @@
# Go-on-SX Scoreboard # Go-on-SX Scoreboard
**Total: 417 / 417 tests passing** **Total: 427 / 427 tests passing**
| | Suite | Pass | Total | | | Suite | Pass | Total |
|---|---|---|---| |---|---|---|---|
| ✅ | lex | 129 | 129 | | ✅ | lex | 129 | 129 |
| ✅ | parse | 176 | 176 | | ✅ | parse | 176 | 176 |
| ✅ | types | 72 | 72 | | ✅ | types | 72 | 72 |
| ✅ | eval | 40 | 40 | | ✅ | eval | 50 | 50 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |
| ⬜ | stdlib | 0 | 0 | | ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 | | ⬜ | e2e | 0 | 0 |

View File

@@ -195,6 +195,73 @@
(go-eval env (go-parse "fact(5)"))) (go-eval env (go-parse "fact(5)")))
120) 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 (define
go-eval-test-summary go-eval-test-summary
(str "eval " go-eval-test-pass "/" go-eval-test-count)) (str "eval " go-eval-test-pass "/" go-eval-test-count))

View File

@@ -276,8 +276,12 @@ Progress-log line → push `origin/loops/go`.
keywords through `go-eval-block` until `go-for-loop` catches them. keywords through `go-eval-block` until `go-for-loop` catches them.
- [ ] Variables as mutable cells; pointer semantics: `&x` returns the - [ ] Variables as mutable cells; pointer semantics: `&x` returns the
cell, `*p` dereferences. cell, `*p` dereferences.
- [ ] Slices: triple (length, capacity, backing-vector). `append` - [/] Slices: v0 represents a slice as `(list :go-slice ELEMS)`
honours capacity-grow per spec. 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. - [ ] Maps: SX dict + key-type metadata.
- [ ] Structs: SX dict + type tag. Methods looked up via type's table. - [ ] Structs: SX dict + type tag. Methods looked up via type's table.
- [/] Functions: top-level definition + call (incl. recursion via the - [/] 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). - [ ] Channels: stub (Phase 5 wires them).
- Tests: arithmetic, control flow, recursion, closures, slices, maps, - Tests: arithmetic, control flow, recursion, closures, slices, maps,
structs, methods, pointer semantics, multiple-return. 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`) ⬜ ### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that - **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._ _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. - 2026-05-27 — Phase 4 cont.: for-loops, break, continue, inc-dec.
`go-eval-for` handles all three for-header shapes (infinite, while- `go-eval-for` handles all three for-header shapes (infinite, while-
like, C-style) including init+post stmts and missing-cond defaulting like, C-style) including init+post stmts and missing-cond defaulting