go: eval.sx — maps + index-assign + 8 tests; word-count e2e [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Phase 4 cont. Adds map values and index-assignment for both
slices and maps.
Map representation: (list :go-map ENTRIES) where ENTRIES is an
association list of (key value) pairs.
go-map-get / go-map-set — primitive lookup + functional-update.
go-slice-set — same idea for slices.
go-extract-map-entries reads each :kv element in a composite literal,
evaluating key and value. go-eval-composite dispatches on :ty-map to
build the :go-map value.
go-eval-index extended: when OBJ is a :go-map, look up the key via
go-map-get. Missing keys return nil in v0 (Go's real semantics is
the zero value of the value type — needs runtime type info that this
slice doesn't yet thread through).
go-eval-builtin's len handles :go-map alongside :go-slice and strings.
go-eval-assign-pairs gets a new branch for (:index OBJ IDX) LHS:
- var-rooted indexing only (a[i] = v / m["k"] = v)
- slice → go-slice-set then rebind the var
- map → go-map-set then rebind the var
**Word-counter via map[string]int works end-to-end:**
words := []string{"a", "b", "a", "c", "a"}
counts := map[string]int{}
for i := 0; i < len(words); i++ {
counts[words[i]] = counts[words[i]] + 1
}
// counts["a"] == 3
Builds on:
- map composite literal eval
- map index lookup
- map index-assign
- slice indexing
- len() builtin
- nil + 1 = 1 (numeric-coercion of missing-key default)
eval 58/58, total 435/435.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -239,6 +239,34 @@
|
||||
(go-env-extend env (first names) (first vals))
|
||||
(rest names) (rest vals)))))
|
||||
|
||||
(define
|
||||
go-map-get
|
||||
(fn (entries key)
|
||||
(cond
|
||||
(= (len entries) 0) nil
|
||||
(= (first (first entries)) key) (nth (first entries) 1)
|
||||
:else (go-map-get (rest entries) key))))
|
||||
|
||||
(define
|
||||
go-map-set
|
||||
;; Update the key's value if present, else append. Returns a new entry list.
|
||||
(fn (entries key value)
|
||||
(cond
|
||||
(= (len entries) 0) (list (list key value))
|
||||
(= (first (first entries)) key)
|
||||
(cons (list key value) (rest entries))
|
||||
:else (cons (first entries) (go-map-set (rest entries) key value)))))
|
||||
|
||||
(define
|
||||
go-slice-set
|
||||
;; Functional update on a list at index IDX. Out-of-range no-ops in v0.
|
||||
(fn (elems idx value)
|
||||
(cond
|
||||
(>= idx (len elems)) elems
|
||||
(< idx 0) elems
|
||||
(= idx 0) (cons value (rest elems))
|
||||
:else (cons (first elems) (go-slice-set (rest elems) (- idx 1) value)))))
|
||||
|
||||
(define
|
||||
go-eval-builtin
|
||||
;; Run Go's predeclared builtins (len, append, print). args are
|
||||
@@ -255,6 +283,7 @@
|
||||
(let ((arg (first vals)))
|
||||
(cond
|
||||
(and (list? arg) (= (first arg) :go-slice)) (len (nth arg 1))
|
||||
(and (list? arg) (= (first arg) :go-map)) (len (nth arg 1))
|
||||
(string? arg) (len arg)
|
||||
:else (list :eval-error :len-not-applicable arg))))
|
||||
(= name "append")
|
||||
@@ -293,9 +322,30 @@
|
||||
(go-eval-error? rest-vs) rest-vs
|
||||
:else (cons v rest-vs)))))))))
|
||||
|
||||
(define
|
||||
go-extract-map-entries
|
||||
(fn (env elems)
|
||||
(cond
|
||||
(or (= elems nil) (= (len elems) 0)) (list)
|
||||
:else
|
||||
(let ((e (first elems)))
|
||||
(cond
|
||||
(not (and (list? e) (= (first e) :kv)))
|
||||
(list :eval-error :map-elem-missing-key e)
|
||||
:else
|
||||
(let ((k (go-eval env (nth e 1))) (v (go-eval env (nth e 2))))
|
||||
(cond
|
||||
(go-eval-error? k) k
|
||||
(go-eval-error? v) v
|
||||
:else
|
||||
(let ((rest-es (go-extract-map-entries env (rest elems))))
|
||||
(cond
|
||||
(go-eval-error? rest-es) rest-es
|
||||
:else (cons (list k v) rest-es))))))))))
|
||||
|
||||
(define
|
||||
go-eval-composite
|
||||
;; (:composite TYPE-OR-EXPR ELEMS). v0 supports slice/array; map/struct
|
||||
;; (:composite TYPE-OR-EXPR ELEMS). v0 supports slice/array/map; struct
|
||||
;; later.
|
||||
(fn (env expr)
|
||||
(let ((ty (nth expr 1)) (elems (nth expr 2)))
|
||||
@@ -306,11 +356,16 @@
|
||||
(cond
|
||||
(go-eval-error? vals) vals
|
||||
:else (list :go-slice vals)))
|
||||
(and (list? ty) (= (first ty) :ty-map))
|
||||
(let ((entries (go-extract-map-entries env elems)))
|
||||
(cond
|
||||
(go-eval-error? entries) entries
|
||||
:else (list :go-map entries)))
|
||||
:else (list :eval-error :unsupported-composite ty)))))
|
||||
|
||||
(define
|
||||
go-eval-index
|
||||
;; (:index OBJ IDX-EXPR). v0: slice indexing only.
|
||||
;; (:index OBJ IDX-EXPR). v0: slice or map.
|
||||
(fn (env expr)
|
||||
(let ((obj (go-eval env (nth expr 1)))
|
||||
(idx (go-eval env (nth expr 2))))
|
||||
@@ -323,6 +378,10 @@
|
||||
(or (< idx 0) (>= idx (len elems)))
|
||||
(list :eval-error :index-out-of-range idx (len elems))
|
||||
:else (nth elems idx)))
|
||||
(and (list? obj) (= (first obj) :go-map))
|
||||
;; v0: returns nil for missing keys. Go's real semantics is the
|
||||
;; zero value of the value type — needs runtime type info.
|
||||
(go-map-get (nth obj 1) idx)
|
||||
:else (list :eval-error :not-indexable obj)))))
|
||||
|
||||
(define
|
||||
@@ -453,12 +512,37 @@
|
||||
(cond
|
||||
(= (len lhs-list) 0) env
|
||||
:else
|
||||
(let ((lhs (first lhs-list)))
|
||||
(let ((lhs (first lhs-list)) (rhs-val (first vals)))
|
||||
(cond
|
||||
(and (list? lhs) (= (first lhs) :var))
|
||||
(go-eval-assign-pairs
|
||||
(go-env-extend env (nth lhs 1) (first vals))
|
||||
(go-env-extend env (nth lhs 1) rhs-val)
|
||||
(rest lhs-list) (rest vals))
|
||||
;; (:index OBJ IDX) — slice or map element assignment
|
||||
(and (list? lhs) (= (first lhs) :index))
|
||||
(let ((obj-expr (nth lhs 1)) (idx-expr (nth lhs 2)))
|
||||
(cond
|
||||
;; only support var-rooted indexing for now
|
||||
(not (and (list? obj-expr) (= (first obj-expr) :var)))
|
||||
(list :eval-error :unsupported-lhs lhs)
|
||||
:else
|
||||
(let ((obj (go-eval env obj-expr)) (idx (go-eval env idx-expr)))
|
||||
(cond
|
||||
(go-eval-error? obj) obj
|
||||
(go-eval-error? idx) idx
|
||||
(and (list? obj) (= (first obj) :go-slice))
|
||||
(go-eval-assign-pairs
|
||||
(go-env-extend env (nth obj-expr 1)
|
||||
(list :go-slice
|
||||
(go-slice-set (nth obj 1) idx rhs-val)))
|
||||
(rest lhs-list) (rest vals))
|
||||
(and (list? obj) (= (first obj) :go-map))
|
||||
(go-eval-assign-pairs
|
||||
(go-env-extend env (nth obj-expr 1)
|
||||
(list :go-map
|
||||
(go-map-set (nth obj 1) idx rhs-val)))
|
||||
(rest lhs-list) (rest vals))
|
||||
:else (list :eval-error :unsupported-lhs lhs)))))
|
||||
:else (list :eval-error :unsupported-lhs lhs))))))
|
||||
|
||||
(define
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"language": "go",
|
||||
"total_pass": 427,
|
||||
"total": 427,
|
||||
"total_pass": 435,
|
||||
"total": 435,
|
||||
"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":50,"total":50,"status":"ok"},
|
||||
{"name":"eval","pass":58,"total":58,"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: 427 / 427 tests passing**
|
||||
**Total: 435 / 435 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | lex | 129 | 129 |
|
||||
| ✅ | parse | 176 | 176 |
|
||||
| ✅ | types | 72 | 72 |
|
||||
| ✅ | eval | 50 | 50 |
|
||||
| ✅ | eval | 58 | 58 |
|
||||
| ⬜ | runtime | 0 | 0 |
|
||||
| ⬜ | stdlib | 0 | 0 |
|
||||
| ⬜ | e2e | 0 | 0 |
|
||||
|
||||
@@ -262,6 +262,60 @@
|
||||
(go-env-lookup env "sum"))
|
||||
15)
|
||||
|
||||
(go-eval-test
|
||||
"map: map[string]int{...} → :go-map"
|
||||
(gtev go-env-empty "map[string]int{\"a\": 1, \"b\": 2}")
|
||||
(list :go-map (list (list "a" 1) (list "b" 2))))
|
||||
|
||||
(go-eval-test
|
||||
"map: m[\"a\"] → 1"
|
||||
(let
|
||||
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1, \"b\": 2}")))))
|
||||
(go-eval env (go-parse "m[\"a\"]")))
|
||||
1)
|
||||
|
||||
(go-eval-test
|
||||
"map: missing key → nil (v0 stand-in for zero value)"
|
||||
(let
|
||||
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1}")))))
|
||||
(go-eval env (go-parse "m[\"missing\"]")))
|
||||
nil)
|
||||
|
||||
(go-eval-test
|
||||
"map: len(m) = 2"
|
||||
(let
|
||||
((env (go-eval-program go-env-builtins (list (go-parse "m := map[string]int{\"a\": 1, \"b\": 2}")))))
|
||||
(go-eval env (go-parse "len(m)")))
|
||||
2)
|
||||
|
||||
(go-eval-test
|
||||
"map: index-assign updates existing key"
|
||||
(let
|
||||
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1}") (go-parse "m[\"a\"] = 99")))))
|
||||
(go-eval env (go-parse "m[\"a\"]")))
|
||||
99)
|
||||
|
||||
(go-eval-test
|
||||
"map: index-assign adds new key"
|
||||
(let
|
||||
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{}") (go-parse "m[\"new\"] = 7")))))
|
||||
(go-eval env (go-parse "m[\"new\"]")))
|
||||
7)
|
||||
|
||||
(go-eval-test
|
||||
"slice: index-assign a[0] = 99"
|
||||
(let
|
||||
((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30}") (go-parse "a[0] = 99")))))
|
||||
(go-eval env (go-parse "a[0]")))
|
||||
99)
|
||||
|
||||
(go-eval-test
|
||||
"map: word count via loop"
|
||||
(let
|
||||
((env (go-eval-program go-env-builtins (list (go-parse "words := []string{\"a\", \"b\", \"a\", \"c\", \"a\"}") (go-parse "counts := map[string]int{}") (go-parse "for i := 0; i < len(words); i++ { counts[words[i]] = counts[words[i]] + 1 }")))))
|
||||
(go-eval env (go-parse "counts[\"a\"]")))
|
||||
3)
|
||||
|
||||
(define
|
||||
go-eval-test-summary
|
||||
(str "eval " go-eval-test-pass "/" go-eval-test-count))
|
||||
|
||||
@@ -282,14 +282,18 @@ Progress-log line → push `origin/loops/go`.
|
||||
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.
|
||||
- [x] Maps: v0 represents `m` as `(list :go-map ENTRIES)` where ENTRIES
|
||||
is an assoc list. Composite-literal `map[K]V{...}`, `m[k]` lookup
|
||||
(nil for missing key, until runtime type info enables zero-value),
|
||||
`m[k] = v` index-assignment, `len(m)`. Index-assignment for slices
|
||||
also lands here (`a[i] = v` rebuilds via `go-slice-set`).
|
||||
- [ ] Structs: SX dict + type tag. Methods looked up via type's table.
|
||||
- [/] 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: 50/50. No concurrency yet.
|
||||
- **Acceptance:** eval/ suite at 80+ tests. Current: 58/58. No concurrency yet.
|
||||
|
||||
### Phase 5 — Goroutines + channels + select (`lib/go/sched.sx`) ⬜
|
||||
- **Independent implementation.** Do NOT use lib/guest/scheduler/ — that
|
||||
@@ -573,6 +577,16 @@ 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.: maps + index-assignment. Maps represented
|
||||
as `(list :go-map ENTRIES)` where ENTRIES is an assoc list. New
|
||||
helpers `go-map-get` / `go-map-set` / `go-slice-set`. Composite-lit
|
||||
for `map[K]V{...}` evaluates via `go-extract-map-entries`. `m[k]`
|
||||
index lookup added to `go-eval-index`; `len(m)` extended in
|
||||
`go-eval-builtin`. **Index-assignment** for both slices and maps
|
||||
added to `go-eval-assign-pairs`: only var-rooted LHS for v0
|
||||
(`a[0] = 99`, `m["k"] = v`), enough for canonical programs.
|
||||
**Word-count via `counts[words[i]] = counts[words[i]] + 1` works
|
||||
end-to-end.** +8 tests, eval 58/58, total 435/435. `[nothing]`.
|
||||
- 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/
|
||||
|
||||
Reference in New Issue
Block a user