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

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:
2026-05-27 21:33:17 +00:00
parent ab04ec1cf7
commit 9ed58bd0fc
5 changed files with 163 additions and 11 deletions

View File

@@ -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