diff --git a/lib/go/eval.sx b/lib/go/eval.sx index 1fd51a46..e0da6002 100644 --- a/lib/go/eval.sx +++ b/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 diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index c29d6b32..e4b7b5e2 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -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"} diff --git a/lib/go/scoreboard.md b/lib/go/scoreboard.md index 8bd33624..b57a6ff5 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -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 | diff --git a/lib/go/tests/eval.sx b/lib/go/tests/eval.sx index f7a23c44..6e8a2afb 100644 --- a/lib/go/tests/eval.sx +++ b/lib/go/tests/eval.sx @@ -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)) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 68805722..81541b73 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -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