go: eval.sx — structs + selector + selector-assign + 8 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s

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

Struct representation: (list :go-struct TYPE-NAME FIELDS) where
FIELDS is an association list of (field-name value) pairs.

`type T struct { ... }` is now significant at eval-time. The new
go-eval-type-decl registers field-name lists in env under
(:go-struct-type FIELD-NAMES) so positional composite literals can
map argument positions to field names. Non-struct type aliases are
silent no-ops in v0.

go-eval-composite extended:
  * If type is (:var TYPE-NAME), look up in env. Must be a
    :go-struct-type entry — error otherwise.
  * go-eval-struct-lit branches on whether the first elem is :kv
    (keyed) or not (positional). Keyed mode reads key-name from each
    :kv's key (which is a :var node). Positional mode arity-checks
    against the field-names list and zips positionally.

go-eval-select handles (:select OBJ FIELD-NAME) — field lookup with
go-map-get on the FIELDS assoc list.

go-eval-assign-pairs gets a new (:select OBJ FIELD) LHS branch:
  - var-rooted only for v0
  - rebuilds the struct via go-map-set, rebinds the var

**Functions taking and returning structs round-trip end-to-end:**

  type Point struct { x, y int }
  func add(a, b Point) Point { return Point{a.x + b.x, a.y + b.y} }
  add(Point{1, 2}, Point{3, 4})  // Point{4, 6}

Method-dispatch (calling p.M() where M is a method on Point's type)
is the next step; needs threading the type checker's #method/T/N
scheme into eval-time so functions can be looked up by receiver type.

eval 66/66, total 443/443.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 21:39:06 +00:00
parent 9ed58bd0fc
commit 99f8f37ff8
5 changed files with 219 additions and 7 deletions

View File

@@ -267,6 +267,89 @@
(= idx 0) (cons value (rest elems))
:else (cons (first elems) (go-slice-set (rest elems) (- idx 1) value)))))
(define
go-struct-field-names
;; FIELDS is a list of (:field NAMES TYPE) groups; flatten to names.
(fn (fields)
(cond
(or (= fields nil) (= (len fields) 0)) (list)
:else
(let ((f (first fields)))
(let ((names (nth f 1)))
(go-name-concat names (go-struct-field-names (rest fields))))))))
(define
go-zip-fields
(fn (names vals)
(cond
(= (len names) 0) (list)
:else
(cons (list (first names) (first vals))
(go-zip-fields (rest names) (rest vals))))))
(define
go-eval-keyed-fields
;; Each elem is (:kv (:var FIELD-NAME) VALUE-EXPR).
(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 :struct-elem-missing-key e)
:else
(let ((k (nth e 1)) (v (go-eval env (nth e 2))))
(cond
(go-eval-error? v) v
(not (and (list? k) (= (first k) :var)))
(list :eval-error :struct-key-not-ident k)
:else
(let ((rest-fields
(go-eval-keyed-fields env (rest elems))))
(cond
(go-eval-error? rest-fields) rest-fields
:else
(cons (list (nth k 1) v) rest-fields))))))))))
(define
go-eval-struct-lit
(fn (env type-name field-names elems)
(cond
(or (= elems nil) (= (len elems) 0))
(list :go-struct type-name (list))
(and (list? (first elems)) (= (first (first elems)) :kv))
(let ((fields (go-eval-keyed-fields env elems)))
(cond
(go-eval-error? fields) fields
:else (list :go-struct type-name fields)))
:else
(cond
(not (= (len elems) (len field-names)))
(list :eval-error :struct-arity-mismatch type-name
(len field-names) (len elems))
:else
(let ((vals (go-eval-args env elems)))
(cond
(go-eval-error? vals) vals
:else
(list :go-struct type-name
(go-zip-fields field-names vals))))))))
(define
go-eval-select
;; (:select OBJ FIELD-NAME) — struct field access.
(fn (env expr)
(let ((obj (go-eval env (nth expr 1))) (field-name (nth expr 2)))
(cond
(go-eval-error? obj) obj
(and (list? obj) (= (first obj) :go-struct))
(let ((v (go-map-get (nth obj 2) field-name)))
(cond
(= v nil) (list :eval-error :unknown-field field-name)
:else v))
:else (list :eval-error :not-selectable obj)))))
(define
go-eval-builtin
;; Run Go's predeclared builtins (len, append, print). args are
@@ -361,6 +444,18 @@
(cond
(go-eval-error? entries) entries
:else (list :go-map entries)))
;; Named struct type (Point{1, 2}). Lookup the type info.
(and (list? ty) (= (first ty) :var))
(let ((type-info (go-env-lookup env (nth ty 1))))
(cond
(= type-info nil)
(list :eval-error :unknown-struct-type (nth ty 1))
(not (and (list? type-info)
(= (first type-info) :go-struct-type)))
(list :eval-error :not-struct-type (nth ty 1) type-info)
:else
(go-eval-struct-lit env (nth ty 1)
(nth type-info 1) elems)))
:else (list :eval-error :unsupported-composite ty)))))
(define
@@ -543,6 +638,23 @@
(go-map-set (nth obj 1) idx rhs-val)))
(rest lhs-list) (rest vals))
:else (list :eval-error :unsupported-lhs lhs)))))
;; (:select OBJ FIELD) — struct field assignment
(and (list? lhs) (= (first lhs) :select))
(let ((obj-expr (nth lhs 1)) (field-name (nth lhs 2)))
(cond
(not (and (list? obj-expr) (= (first obj-expr) :var)))
(list :eval-error :unsupported-lhs lhs)
:else
(let ((obj (go-eval env obj-expr)))
(cond
(go-eval-error? obj) obj
(and (list? obj) (= (first obj) :go-struct))
(go-eval-assign-pairs
(go-env-extend env (nth obj-expr 1)
(list :go-struct (nth obj 1)
(go-map-set (nth obj 2) field-name rhs-val)))
(rest lhs-list) (rest vals))
:else (list :eval-error :unsupported-lhs lhs)))))
:else (list :eval-error :unsupported-lhs lhs))))))
(define
@@ -667,12 +779,27 @@
(go-eval-inc-dec env stmt)
(and (list? stmt) (= (first stmt) :func-decl))
(go-eval-func-decl env stmt)
(and (list? stmt) (= (first stmt) :type-decl))
(go-eval-type-decl env stmt)
:else
(let ((v (go-eval env stmt)))
(cond
(go-eval-error? v) v
:else env)))))
(define
go-eval-type-decl
;; (:type-decl NAME TYPE). For struct types we register the field-name
;; list so positional composite literals like Point{1, 2} can map
;; positions to field names. Other type aliases are silent no-ops in v0.
(fn (env stmt)
(let ((name (nth stmt 1)) (ty (nth stmt 2)))
(cond
(and (list? ty) (= (first ty) :ty-struct))
(go-env-extend env name
(list :go-struct-type (go-struct-field-names (nth ty 1))))
:else env))))
(define
go-eval-block
(fn (env stmts)
@@ -724,6 +851,8 @@
(go-eval-index env expr)
(and (list? expr) (= (first expr) :slice))
(go-eval-slice env expr)
(and (list? expr) (= (first expr) :select))
(go-eval-select env expr)
(and (list? expr) (= (first expr) :app))
(let ((head (nth expr 1)) (args (nth expr 2)))
(cond