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)) (= idx 0) (cons value (rest elems))
:else (cons (first elems) (go-slice-set (rest elems) (- idx 1) value))))) :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 (define
go-eval-builtin go-eval-builtin
;; Run Go's predeclared builtins (len, append, print). args are ;; Run Go's predeclared builtins (len, append, print). args are
@@ -361,6 +444,18 @@
(cond (cond
(go-eval-error? entries) entries (go-eval-error? entries) entries
:else (list :go-map 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))))) :else (list :eval-error :unsupported-composite ty)))))
(define (define
@@ -543,6 +638,23 @@
(go-map-set (nth obj 1) idx rhs-val))) (go-map-set (nth obj 1) idx rhs-val)))
(rest lhs-list) (rest vals)) (rest lhs-list) (rest vals))
:else (list :eval-error :unsupported-lhs lhs))))) :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)))))) :else (list :eval-error :unsupported-lhs lhs))))))
(define (define
@@ -667,12 +779,27 @@
(go-eval-inc-dec env stmt) (go-eval-inc-dec env stmt)
(and (list? stmt) (= (first stmt) :func-decl)) (and (list? stmt) (= (first stmt) :func-decl))
(go-eval-func-decl env stmt) (go-eval-func-decl env stmt)
(and (list? stmt) (= (first stmt) :type-decl))
(go-eval-type-decl env stmt)
:else :else
(let ((v (go-eval env stmt))) (let ((v (go-eval env stmt)))
(cond (cond
(go-eval-error? v) v (go-eval-error? v) v
:else env))))) :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 (define
go-eval-block go-eval-block
(fn (env stmts) (fn (env stmts)
@@ -724,6 +851,8 @@
(go-eval-index env expr) (go-eval-index env expr)
(and (list? expr) (= (first expr) :slice)) (and (list? expr) (= (first expr) :slice))
(go-eval-slice env expr) (go-eval-slice env expr)
(and (list? expr) (= (first expr) :select))
(go-eval-select 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": 435, "total_pass": 443,
"total": 435, "total": 443,
"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":58,"total":58,"status":"ok"}, {"name":"eval","pass":66,"total":66,"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: 435 / 435 tests passing** **Total: 443 / 443 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 | 58 | 58 | | ✅ | eval | 66 | 66 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |
| ⬜ | stdlib | 0 | 0 | | ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 | | ⬜ | e2e | 0 | 0 |

View File

@@ -316,6 +316,70 @@
(go-eval env (go-parse "counts[\"a\"]"))) (go-eval env (go-parse "counts[\"a\"]")))
3) 3)
(go-eval-test
"type-decl: registers struct field names"
(go-env-lookup
(go-eval-program
go-env-empty
(list (go-parse "type Point struct { x, y int }")))
"Point")
(list :go-struct-type (list "x" "y")))
(go-eval-test
"struct: positional composite Point{1, 2}"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }")))))
(go-eval env (go-parse "Point{1, 2}")))
(list
:go-struct "Point"
(list (list "x" 1) (list "y" 2))))
(go-eval-test
"struct: keyed composite Point{x: 5, y: 10}"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }")))))
(go-eval env (go-parse "Point{x: 5, y: 10}")))
(list
:go-struct "Point"
(list (list "x" 5) (list "y" 10))))
(go-eval-test
"struct: selector p.x = 1"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "p := Point{1, 2}")))))
(go-eval env (go-parse "p.x")))
1)
(go-eval-test
"struct: selector p.y = 2"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "p := Point{1, 2}")))))
(go-eval env (go-parse "p.y")))
2)
(go-eval-test
"struct: selector-assign p.x = 99"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "p := Point{1, 2}") (go-parse "p.x = 99")))))
(go-eval env (go-parse "p.x")))
99)
(go-eval-test
"struct: positional arity-mismatch"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }")))))
(go-eval env (go-parse "Point{1}")))
(list :eval-error :struct-arity-mismatch "Point" 2 1))
(go-eval-test
"struct: function takes/returns struct"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "func add(a, b Point) Point { return Point{a.x + b.x, a.y + b.y} }")))))
(go-eval env (go-parse "add(Point{1, 2}, Point{3, 4})")))
(list
:go-struct "Point"
(list (list "x" 4) (list "y" 6))))
(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

@@ -287,13 +287,19 @@ Progress-log line → push `origin/loops/go`.
(nil for missing key, until runtime type info enables zero-value), (nil for missing key, until runtime type info enables zero-value),
`m[k] = v` index-assignment, `len(m)`. Index-assignment for slices `m[k] = v` index-assignment, `len(m)`. Index-assignment for slices
also lands here (`a[i] = v` rebuilds via `go-slice-set`). also lands here (`a[i] = v` rebuilds via `go-slice-set`).
- [ ] Structs: SX dict + type tag. Methods looked up via type's table. - [/] Structs: `(list :go-struct TYPE-NAME FIELDS)` where FIELDS is an
assoc list. `type Point struct {...}` registers field names in
env via `(:go-struct-type FIELD-NAMES)`; positional and keyed
composite literals build struct values; `p.field` selector and
`p.field = v` selector-assignment work. Methods lookup-by-receiver
pending — depends on threading the type checker's method-key
scheme into eval.
- [/] Functions: top-level definition + call (incl. recursion via the - [/] Functions: top-level definition + call (incl. recursion via the
calling env). Lexical closures and multiple return values pending. calling env). Lexical closures and multiple return values pending.
- [ ] 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: 58/58. No concurrency yet. - **Acceptance:** eval/ suite at 80+ tests. Current: 66/66. 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
@@ -577,6 +583,19 @@ 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.: structs + selector access +
selector-assignment. `(:go-struct TYPE-NAME FIELDS)` value, with
FIELDS an assoc list. `type T struct {...}` is now significant at
eval-time too: registers `(:go-struct-type FIELD-NAMES)` in env so
positional composite literals like `Point{1, 2}` can map positions
to field names. Keyed literals `Point{x: 5, y: 10}` also work.
`go-eval-select` does field lookup; LHS `:select` in
`go-eval-assign-pairs` does field update. **`add(Point{1,2},
Point{3,4}) → Point{4,6}` works end-to-end** — functions receiving
and returning structs round-trip through the evaluator. +8 tests,
eval 66/66, total 443/443. Method-dispatch (looking up methods by
receiver type) pending; needs threading the type checker's
`#method/T/N` scheme into eval. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: maps + index-assignment. Maps represented - 2026-05-27 — Phase 4 cont.: maps + index-assignment. Maps represented
as `(list :go-map ENTRIES)` where ENTRIES is an assoc list. New as `(list :go-map ENTRIES)` where ENTRIES is an assoc list. New
helpers `go-map-get` / `go-map-set` / `go-slice-set`. Composite-lit helpers `go-map-get` / `go-map-set` / `go-slice-set`. Composite-lit