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>
473 lines
16 KiB
Plaintext
473 lines
16 KiB
Plaintext
;; Go evaluator tests.
|
|
|
|
(define go-eval-test-count 0)
|
|
(define go-eval-test-pass 0)
|
|
(define go-eval-test-fails (list))
|
|
|
|
(define
|
|
go-eval-test
|
|
(fn
|
|
(name actual expected)
|
|
(set! go-eval-test-count (+ go-eval-test-count 1))
|
|
(if
|
|
(= actual expected)
|
|
(set! go-eval-test-pass (+ go-eval-test-pass 1))
|
|
(append! go-eval-test-fails {:name name :expected expected :actual actual}))))
|
|
|
|
(define gtev (fn (env src) (go-eval env (go-parse src))))
|
|
|
|
;; ── env ──────────────────────────────────────────────────────────
|
|
(go-eval-test
|
|
"env: empty lookup returns nil"
|
|
(go-env-lookup go-env-empty "x")
|
|
nil)
|
|
|
|
(go-eval-test
|
|
"env: extend then lookup"
|
|
(go-env-lookup (go-env-extend go-env-empty "x" 42) "x")
|
|
42)
|
|
|
|
;; ── literals ────────────────────────────────────────────────────
|
|
(go-eval-test "lit: 42 → 42" (gtev go-env-empty "42") 42)
|
|
|
|
(go-eval-test "lit: 0 → 0" (gtev go-env-empty "0") 0)
|
|
|
|
(go-eval-test "lit: 0xFF → 255" (gtev go-env-empty "0xFF") 255)
|
|
|
|
(go-eval-test "lit: 0b1010 → 10" (gtev go-env-empty "0b1010") 10)
|
|
|
|
(go-eval-test "lit: 0o17 → 15" (gtev go-env-empty "0o17") 15)
|
|
|
|
(go-eval-test
|
|
"lit: underscore separator 1_000 → 1000"
|
|
(gtev go-env-empty "1_000")
|
|
1000)
|
|
|
|
(go-eval-test "lit: string" (gtev go-env-empty "\"hello\"") "hello")
|
|
|
|
;; ── predeclared ─────────────────────────────────────────────────
|
|
(go-eval-test "var: true" (gtev go-env-empty "true") true)
|
|
(go-eval-test "var: false" (gtev go-env-empty "false") false)
|
|
(go-eval-test "var: nil" (gtev go-env-empty "nil") nil)
|
|
|
|
;; ── variable lookup ─────────────────────────────────────────────
|
|
(go-eval-test
|
|
"var: bound x → 5"
|
|
(go-eval (go-env-extend go-env-empty "x" 5) (go-parse "x"))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"var: unbound y → :eval-error"
|
|
(gtev go-env-empty "y")
|
|
(list :eval-error :unbound "y"))
|
|
|
|
;; ── binary ops ─────────────────────────────────────────────────
|
|
(go-eval-test "binop: 1 + 2 → 3" (gtev go-env-empty "1 + 2") 3)
|
|
(go-eval-test "binop: 10 - 4 → 6" (gtev go-env-empty "10 - 4") 6)
|
|
(go-eval-test "binop: 3 * 7 → 21" (gtev go-env-empty "3 * 7") 21)
|
|
(go-eval-test "binop: 42 / 7 → 6" (gtev go-env-empty "42 / 7") 6)
|
|
(go-eval-test
|
|
"binop: 2 + 3 * 4 → 14 (prec)"
|
|
(gtev go-env-empty "2 + 3 * 4")
|
|
14)
|
|
(go-eval-test
|
|
"binop: a + b uses env"
|
|
(go-eval
|
|
(go-env-extend (go-env-extend go-env-empty "a" 3) "b" 4)
|
|
(go-parse "a + b"))
|
|
7)
|
|
|
|
(go-eval-test "binop: 1 < 2 → true" (gtev go-env-empty "1 < 2") true)
|
|
(go-eval-test "binop: 5 == 5 → true" (gtev go-env-empty "5 == 5") true)
|
|
(go-eval-test "binop: 5 != 5 → false" (gtev go-env-empty "5 != 5") false)
|
|
(go-eval-test
|
|
"binop: true && false → false"
|
|
(gtev go-env-empty "true && false")
|
|
false)
|
|
(go-eval-test
|
|
"binop: false || true → true"
|
|
(gtev go-env-empty "false || true")
|
|
true)
|
|
|
|
;; ── report ──────────────────────────────────────────────────────
|
|
(go-eval-test
|
|
"var-decl: var x = 5 — env has x=5"
|
|
(go-env-lookup
|
|
(go-eval-program go-env-empty (list (go-parse "var x = 5")))
|
|
"x")
|
|
5)
|
|
|
|
(go-eval-test
|
|
"short-decl: a, b := 3, 4 — env has both"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a, b := 3, 4")))))
|
|
(list (go-env-lookup env "a") (go-env-lookup env "b")))
|
|
(list 3 4))
|
|
|
|
(go-eval-test
|
|
"assign: x = 5 then x → 5"
|
|
(let
|
|
((env (go-eval-program (go-env-extend go-env-empty "x" 1) (list (go-parse "x = 5")))))
|
|
(go-env-lookup env "x"))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"if: true branch evaluates"
|
|
(let
|
|
((env (go-eval-program (go-env-extend go-env-empty "x" 0) (list (go-parse "if true { x = 1 }")))))
|
|
(go-env-lookup env "x"))
|
|
1)
|
|
|
|
(go-eval-test
|
|
"if-else: false → else branch"
|
|
(let
|
|
((env (go-eval-program (go-env-extend go-env-empty "x" 0) (list (go-parse "if false { x = 1 } else { x = 2 }")))))
|
|
(go-env-lookup env "x"))
|
|
2)
|
|
|
|
(go-eval-test
|
|
"fn: define + call — double(7) = 14"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "func double(x int) int { return x * 2 }")))))
|
|
(go-eval env (go-parse "double(7)")))
|
|
14)
|
|
|
|
(go-eval-test
|
|
"fn: add(2, 3) = 5"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "func add(x, y int) int { return x + y }")))))
|
|
(go-eval env (go-parse "add(2, 3)")))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"fn: recursive fib(5) = 5"
|
|
(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(5)")))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"for: count to 10 with sum"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "var sum = 0") (go-parse "for i := 0; i < 10; i++ { sum = sum + i }")))))
|
|
(go-env-lookup env "sum"))
|
|
45)
|
|
|
|
(go-eval-test
|
|
"inc-dec: x++ updates env"
|
|
(let
|
|
((env (go-eval-program (go-env-extend go-env-empty "x" 5) (list (go-parse "x++")))))
|
|
(go-env-lookup env "x"))
|
|
6)
|
|
|
|
(go-eval-test
|
|
"inc-dec: x-- updates env"
|
|
(let
|
|
((env (go-eval-program (go-env-extend go-env-empty "x" 5) (list (go-parse "x--")))))
|
|
(go-env-lookup env "x"))
|
|
4)
|
|
|
|
(go-eval-test
|
|
"for: break exits the loop"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "var i = 0") (go-parse "for i < 100 { if i == 5 { break } ; i++ }")))))
|
|
(go-env-lookup env "i"))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"for: continue skips body but runs post"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "var sum = 0") (go-parse "for i := 0; i < 5; i++ { if i == 2 { continue } ; sum = sum + i }")))))
|
|
(go-env-lookup env "sum"))
|
|
8)
|
|
|
|
(go-eval-test
|
|
"for: infinite + break with sum"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "var s = 0") (go-parse "var i = 1") (go-parse "for { if i > 4 { break } ; s = s + i ; i++ }")))))
|
|
(go-env-lookup env "s"))
|
|
10)
|
|
|
|
(go-eval-test
|
|
"fn: iterative factorial via for-loop"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "func fact(n int) int { r := 1 ; for i := 2 ; i <= n ; i++ { r = r * i } ; return r }")))))
|
|
(go-eval env (go-parse "fact(5)")))
|
|
120)
|
|
|
|
(go-eval-test
|
|
"slice: []int{1,2,3} → :go-slice"
|
|
(gtev go-env-empty "[]int{1, 2, 3}")
|
|
(list :go-slice (list 1 2 3)))
|
|
|
|
(go-eval-test
|
|
"index: a[0] = 10, a[2] = 30"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30}")))))
|
|
(list (go-eval env (go-parse "a[0]")) (go-eval env (go-parse "a[2]"))))
|
|
(list 10 30))
|
|
|
|
(go-eval-test
|
|
"index: out-of-range error"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2}")))))
|
|
(go-eval env (go-parse "a[5]")))
|
|
(list :eval-error :index-out-of-range 5 2))
|
|
|
|
(go-eval-test
|
|
"builtin: len(slice) = 3"
|
|
(let
|
|
((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3}")))))
|
|
(go-eval env (go-parse "len(a)")))
|
|
3)
|
|
|
|
(go-eval-test
|
|
"builtin: len(string)"
|
|
(go-eval go-env-builtins (go-parse "len(\"hello\")"))
|
|
5)
|
|
|
|
(go-eval-test
|
|
"builtin: append(a, 4, 5)"
|
|
(let
|
|
((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3}")))))
|
|
(go-eval env (go-parse "append(a, 4, 5)")))
|
|
(list
|
|
:go-slice (list 1 2 3 4 5)))
|
|
|
|
(go-eval-test
|
|
"slice expr: a[1:3]"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30, 40}")))))
|
|
(go-eval env (go-parse "a[1:3]")))
|
|
(list :go-slice (list 20 30)))
|
|
|
|
(go-eval-test
|
|
"slice expr: a[:2] (omitted low)"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2, 3, 4}")))))
|
|
(go-eval env (go-parse "a[:2]")))
|
|
(list :go-slice (list 1 2)))
|
|
|
|
(go-eval-test
|
|
"slice expr: a[2:] (omitted high)"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{1, 2, 3, 4}")))))
|
|
(go-eval env (go-parse "a[2:]")))
|
|
(list :go-slice (list 3 4)))
|
|
|
|
(go-eval-test
|
|
"fn: sum slice via for-loop with len + index"
|
|
(let
|
|
((env (go-eval-program go-env-builtins (list (go-parse "a := []int{1, 2, 3, 4, 5}") (go-parse "sum := 0") (go-parse "for i := 0; i < len(a); i++ { sum = sum + a[i] }")))))
|
|
(go-env-lookup env "sum"))
|
|
15)
|
|
|
|
(go-eval-test
|
|
"map: map[string]int{...} → :go-map"
|
|
(gtev go-env-empty "map[string]int{\"a\": 1, \"b\": 2}")
|
|
(list :go-map (list (list "a" 1) (list "b" 2))))
|
|
|
|
(go-eval-test
|
|
"map: m[\"a\"] → 1"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1, \"b\": 2}")))))
|
|
(go-eval env (go-parse "m[\"a\"]")))
|
|
1)
|
|
|
|
(go-eval-test
|
|
"map: missing key → nil (v0 stand-in for zero value)"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1}")))))
|
|
(go-eval env (go-parse "m[\"missing\"]")))
|
|
nil)
|
|
|
|
(go-eval-test
|
|
"map: len(m) = 2"
|
|
(let
|
|
((env (go-eval-program go-env-builtins (list (go-parse "m := map[string]int{\"a\": 1, \"b\": 2}")))))
|
|
(go-eval env (go-parse "len(m)")))
|
|
2)
|
|
|
|
(go-eval-test
|
|
"map: index-assign updates existing key"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{\"a\": 1}") (go-parse "m[\"a\"] = 99")))))
|
|
(go-eval env (go-parse "m[\"a\"]")))
|
|
99)
|
|
|
|
(go-eval-test
|
|
"map: index-assign adds new key"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "m := map[string]int{}") (go-parse "m[\"new\"] = 7")))))
|
|
(go-eval env (go-parse "m[\"new\"]")))
|
|
7)
|
|
|
|
(go-eval-test
|
|
"slice: index-assign a[0] = 99"
|
|
(let
|
|
((env (go-eval-program go-env-empty (list (go-parse "a := []int{10, 20, 30}") (go-parse "a[0] = 99")))))
|
|
(go-eval env (go-parse "a[0]")))
|
|
99)
|
|
|
|
(go-eval-test
|
|
"map: word count via loop"
|
|
(let
|
|
((env (go-eval-program go-env-builtins (list (go-parse "words := []string{\"a\", \"b\", \"a\", \"c\", \"a\"}") (go-parse "counts := map[string]int{}") (go-parse "for i := 0; i < len(words); i++ { counts[words[i]] = counts[words[i]] + 1 }")))))
|
|
(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))))
|
|
|
|
(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))
|