go: eval.sx — structs + selector + selector-assign + 8 tests [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 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:
129
lib/go/eval.sx
129
lib/go/eval.sx
@@ -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
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"language": "go",
|
||||
"total_pass": 435,
|
||||
"total": 435,
|
||||
"total_pass": 443,
|
||||
"total": 443,
|
||||
"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":58,"total":58,"status":"ok"},
|
||||
{"name":"eval","pass":66,"total":66,"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: 435 / 435 tests passing**
|
||||
**Total: 443 / 443 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | lex | 129 | 129 |
|
||||
| ✅ | parse | 176 | 176 |
|
||||
| ✅ | types | 72 | 72 |
|
||||
| ✅ | eval | 58 | 58 |
|
||||
| ✅ | eval | 66 | 66 |
|
||||
| ⬜ | runtime | 0 | 0 |
|
||||
| ⬜ | stdlib | 0 | 0 |
|
||||
| ⬜ | e2e | 0 | 0 |
|
||||
|
||||
@@ -316,6 +316,70 @@
|
||||
(go-eval env (go-parse "counts[\"a\"]")))
|
||||
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
|
||||
go-eval-test-summary
|
||||
(str "eval " go-eval-test-pass "/" go-eval-test-count))
|
||||
|
||||
@@ -287,13 +287,19 @@ Progress-log line → push `origin/loops/go`.
|
||||
(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.
|
||||
- [/] 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
|
||||
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: 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`) ⬜
|
||||
- **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._
|
||||
|
||||
- 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
|
||||
as `(list :go-map ENTRIES)` where ENTRIES is an assoc list. New
|
||||
helpers `go-map-get` / `go-map-set` / `go-slice-set`. Composite-lit
|
||||
|
||||
Reference in New Issue
Block a user