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))
|
(= 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
|
||||||
|
|||||||
@@ -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"}
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user