;; 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))