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
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:
141
lib/go/eval.sx
141
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
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user