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)
(and (list? stmt) (= (first stmt) :func-decl))
(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))
(go-eval-type-decl env stmt)
:else
@@ -787,6 +789,77 @@
(go-eval-error? v) v
: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
go-eval-type-decl
;; (:type-decl NAME TYPE). For struct types we register the field-name
@@ -864,6 +937,20 @@
(go-eval-error? lv) lv
(go-eval-error? rv) 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
(let ((callee (go-eval env head)))
(cond

View File

@@ -1,12 +1,12 @@
{
"language": "go",
"total_pass": 443,
"total": 443,
"total_pass": 457,
"total": 457,
"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":66,"total":66,"status":"ok"},
{"name":"eval","pass":80,"total":80,"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"}

View File

@@ -1,13 +1,13 @@
# Go-on-SX Scoreboard
**Total: 443 / 443 tests passing**
**Total: 457 / 457 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | lex | 129 | 129 |
| ✅ | parse | 176 | 176 |
| ✅ | types | 72 | 72 |
| ✅ | eval | 66 | 66 |
| ✅ | eval | 80 | 80 |
| ⬜ | runtime | 0 | 0 |
| ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 |

View File

@@ -380,6 +380,93 @@
:go-struct "Point"
(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
go-eval-test-summary
(str "eval " go-eval-test-pass "/" go-eval-test-count))