go: eval.sx — method dispatch + unary + e2e programs + 14 tests; Phase 4 bar crossed [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s

Phase 4 cont. The crossings:

  * Method dispatch — Methods record under #method/TYPE/NAME (same
    mangled-key scheme the type checker uses, intentionally so eval
    and type checker can converge on a shared method-table protocol
    later). go-eval-method-call: lookup the receiver type's method,
    bind receiver param to the struct value, evaluate body. Value and
    pointer receivers treated the same in v0 (pointer semantics not
    modelled yet).
  * Method-call dispatch — In go-eval's :app branch, head=:select
    routes to go-eval-method-call. If the receiver is not a struct,
    falls back to the field-as-callable path.
  * Unary prefix ops — go-eval's :app branch checks for 1-arg :var
    head with op name "-" / "+" / "!". (Other unary ops like
    *p / &v / <-ch / ^x deferred until pointer / channel / bitwise
    semantics arrive.)

End-to-end programs verified:
  * recursive fib(10) = 55
  * struct + method + iterative loop (counter bump 7 times)
  * linear search (returns index or -1)
  * factorial via method on Counter (= 120)
  * count odd numbers in 1..10 = 5

**Phase 4 acceptance bar (80+) crossed: eval 80/80, total 457/457.**

Remaining Phase 4 work (closures, multi-return, full slice triple,
pointer semantics) refines but doesn't gate Phase 5 (goroutines).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 21:47:07 +00:00
parent 99f8f37ff8
commit 674d8115b8
5 changed files with 201 additions and 13 deletions

View File

@@ -779,6 +779,8 @@
(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) :method-decl))
(go-eval-method-decl env stmt)
(and (list? stmt) (= (first stmt) :type-decl)) (and (list? stmt) (= (first stmt) :type-decl))
(go-eval-type-decl env stmt) (go-eval-type-decl env stmt)
:else :else
@@ -787,6 +789,77 @@
(go-eval-error? v) v (go-eval-error? v) v
:else env))))) :else env)))))
(define
go-eval-method-decl
;; (:method-decl RECV NAME PARAMS RESULTS BODY) — register the method
;; under #method/RECV-TYPE-NAME/METHOD-NAME, value is a :go-method.
(fn (env stmt)
(let ((recv (nth stmt 1)) (name (nth stmt 2))
(params (nth stmt 3)) (body (nth stmt 5)))
(let ((recv-names (nth recv 1)) (recv-ty (nth recv 2)))
(let ((recv-name
(cond
(= (len recv-names) 0) "_"
:else (first recv-names))))
(let ((type-name (go-extract-recv-ty-name recv-ty)))
(cond
(= type-name nil) env
:else
(go-env-extend env
(str "#method/" type-name "/" name)
(list :go-method recv-name params body)))))))))
(define
go-eval-method-call
;; Method dispatch: lookup #method/TYPE/NAME in env, bind receiver
;; to OBJ-value and params to ARGS, run body.
(fn (env obj-expr method-name args)
(let ((obj (go-eval env obj-expr)))
(cond
(go-eval-error? obj) obj
(not (and (list? obj) (= (first obj) :go-struct)))
;; Not a struct: maybe it's a callable field access? Try the
;; normal select-then-call path.
(let ((callee (go-eval env (list :select obj-expr method-name))))
(cond
(go-eval-error? callee) callee
:else (go-eval-call env callee args)))
:else
(let ((type-name (nth obj 1)))
(let ((method-val (go-env-lookup env
(str "#method/" type-name "/" method-name))))
(cond
(= method-val nil)
(list :eval-error :no-such-method type-name method-name)
:else
(let ((recv-name (nth method-val 1))
(params (nth method-val 2))
(body (nth method-val 3)))
(let ((arg-vals (go-eval-args env args)))
(cond
(go-eval-error? arg-vals) arg-vals
:else
(let ((param-names (go-flatten-param-names params)))
(cond
(not (= (len param-names) (len arg-vals)))
(list :eval-error :arity-mismatch
(len param-names) (len arg-vals))
:else
(let ((call-env
(go-env-extend
(go-bind-names env param-names arg-vals)
recv-name obj)))
(cond
(= body nil) nil
(and (list? body) (= (first body) :block))
(let ((r (go-eval-block call-env (nth body 1))))
(cond
(and (list? r) (= (first r) :return-value))
(nth r 1)
(go-eval-error? r) r
:else nil))
:else nil))))))))))))))
(define (define
go-eval-type-decl go-eval-type-decl
;; (:type-decl NAME TYPE). For struct types we register the field-name ;; (:type-decl NAME TYPE). For struct types we register the field-name
@@ -864,6 +937,20 @@
(go-eval-error? lv) lv (go-eval-error? lv) lv
(go-eval-error? rv) rv (go-eval-error? rv) rv
:else (go-eval-binop op lv rv)))) :else (go-eval-binop op lv rv))))
;; Unary prefix op: head is :var with op name + 1 arg.
(and (list? head) (= (first head) :var) (= (len args) 1)
(some (fn (o) (= o (nth head 1)))
(list "-" "+" "!")))
(let ((op (nth head 1)) (v (go-eval env (first args))))
(cond
(go-eval-error? v) v
(= op "-") (- 0 v)
(= op "+") v
(= op "!") (not v)
:else (list :eval-error :unsupported-unary op)))
;; Method-call shape: head is (:select OBJ METHOD-NAME).
(and (list? head) (= (first head) :select))
(go-eval-method-call env (nth head 1) (nth head 2) args)
:else :else
(let ((callee (go-eval env head))) (let ((callee (go-eval env head)))
(cond (cond

View File

@@ -1,12 +1,12 @@
{ {
"language": "go", "language": "go",
"total_pass": 443, "total_pass": 457,
"total": 443, "total": 457,
"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":66,"total":66,"status":"ok"}, {"name":"eval","pass":80,"total":80,"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: 443 / 443 tests passing** **Total: 457 / 457 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 | 66 | 66 | | ✅ | eval | 80 | 80 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |
| ⬜ | stdlib | 0 | 0 | | ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 | | ⬜ | e2e | 0 | 0 |

View File

@@ -380,6 +380,93 @@
:go-struct "Point" :go-struct "Point"
(list (list "x" 4) (list "y" 6)))) (list (list "x" 4) (list "y" 6))))
(go-eval-test
"method: p.Sum() = 3"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "func (p Point) Sum() int { return p.x + p.y }") (go-parse "p := Point{1, 2}")))))
(go-eval env (go-parse "p.Sum()")))
3)
(go-eval-test
"method: p.Add(5) = 6 (with arg)"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "func (p Point) Add(d int) int { return p.x + d }") (go-parse "p := Point{1, 2}")))))
(go-eval env (go-parse "p.Add(5)")))
6)
(go-eval-test
"method: pointer receiver works value-style in v0"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Point struct { x, y int }") (go-parse "func (p *Point) GetX() int { return p.x }") (go-parse "p := Point{1, 2}")))))
(go-eval env (go-parse "p.GetX()")))
1)
(go-eval-test
"method: missing method → :no-such-method"
(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.Ghost()")))
(list :eval-error :no-such-method "Point" "Ghost"))
(go-eval-test
"unary: -x"
(go-eval (go-env-extend go-env-empty "x" 5) (go-parse "-x"))
-5)
(go-eval-test "unary: !true → false" (gtev go-env-empty "!true") false)
(go-eval-test "unary: !false → true" (gtev go-env-empty "!false") true)
(go-eval-test
"unary: -3 + 5 = 2 (unary binds tighter)"
(gtev go-env-empty "-3 + 5")
2)
(go-eval-test
"e2e: count odd numbers in 1..10 = 5"
(let
((env (go-eval-program go-env-empty
(list (go-parse "odds := 0")
(go-parse "i := 1")
(go-parse "for i <= 10 { odds = odds + 1; i = i + 2 }")))))
(go-env-lookup env "odds"))
5)
(go-eval-test
"e2e: factorial via method on Counter"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Acc struct { v int }") (go-parse "func (a Acc) Mul(x int) Acc { return Acc{a.v * x} }") (go-parse "a := Acc{1}") (go-parse "for i := 1; i <= 5; i++ { a = a.Mul(i) }")))))
(go-eval env (go-parse "a.v")))
120)
(go-eval-test
"e2e: recursive fibonacci fib(10) = 55"
(let
((env (go-eval-program go-env-empty (list (go-parse "func fib(n int) int { if n < 2 { return n } return fib(n-1) + fib(n-2) }")))))
(go-eval env (go-parse "fib(10)")))
55)
(go-eval-test
"e2e: struct + method + iterative loop"
(let
((env (go-eval-program go-env-empty (list (go-parse "type Counter struct { n int }") (go-parse "func (c Counter) Bump() Counter { return Counter{c.n + 1} }") (go-parse "c := Counter{0}") (go-parse "for i := 0; i < 7; i++ { c = c.Bump() }")))))
(go-eval env (go-parse "c.n")))
7)
(go-eval-test
"e2e: linear search returns index"
(let
((env (go-eval-program go-env-builtins (list (go-parse "func find(a []int, x int) int { for i := 0; i < len(a); i++ { if a[i] == x { return i } } ; return -1 }") (go-parse "nums := []int{10, 20, 30, 40}")))))
(go-eval env (go-parse "find(nums, 30)")))
2)
(go-eval-test
"e2e: linear search returns -1 when missing"
(let
((env (go-eval-program go-env-builtins (list (go-parse "func find(a []int, x int) int { for i := 0; i < len(a); i++ { if a[i] == x { return i } } ; return -1 }") (go-parse "nums := []int{10, 20, 30}")))))
(go-eval env (go-parse "find(nums, 99)")))
-1)
(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,19 +287,22 @@ 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: `(list :go-struct TYPE-NAME FIELDS)` where FIELDS is an - [x] Structs + method dispatch. `(list :go-struct TYPE-NAME FIELDS)`
assoc list. `type Point struct {...}` registers field names in assoc-list. `type T struct {...}` registers `:go-struct-type`
env via `(:go-struct-type FIELD-NAMES)`; positional and keyed with field names. Positional + keyed composite literals; `p.f`
composite literals build struct values; `p.field` selector and / `p.f = v` selectors. Methods bind under `#method/T/N` mangled
`p.field = v` selector-assignment work. Methods lookup-by-receiver keys — same scheme as the type checker. `p.M(...)` dispatches via
pending — depends on threading the type checker's method-key receiver type lookup, binds the receiver param to the struct
scheme into eval. value, runs body. Both value and pointer receivers work in v0
(treated the same since pointer semantics aren't modelled yet).
- [/] 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: 66/66. No concurrency yet. - **Acceptance:** eval/ suite at 80+ tests. **Bar crossed: 80/80.**
Remaining sub-items (lexical closures, multi-return funcs, full
slice triple with capacity) refine but don't gate Phase 5.
### 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
@@ -583,6 +586,17 @@ 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.: **method dispatch + unary ops + e2e
programs. Acceptance bar (80+) crossed.** Methods register under
`#method/TYPE/NAME` (same scheme the type checker uses). When `p.M(...)`
is called, `go-eval-method-call` looks up the receiver type's method,
binds the receiver param to the struct value, runs the body. Both
value and pointer receivers work in v0 (treated the same — no
pointer semantics yet). Unary `-x` / `+x` / `!x` in `go-eval`.
E2E programs evaluating end-to-end now include: counter-via-method
(factorial), linear search returning index or -1, recursive
fibonacci(10) = 55, and the counter-bump-N-times pattern. +14 tests,
eval 80/80, total 457/457. `[nothing]`.
- 2026-05-27 — Phase 4 cont.: structs + selector access + - 2026-05-27 — Phase 4 cont.: structs + selector access +
selector-assignment. `(:go-struct TYPE-NAME FIELDS)` value, with selector-assignment. `(:go-struct TYPE-NAME FIELDS)` value, with
FIELDS an assoc list. `type T struct {...}` is now significant at FIELDS an assoc list. `type T struct {...}` is now significant at