go: eval.sx scaffold — literals + vars + binops + 25 tests; Phase 3 closed [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s

Phase 3 — bidirectional type checker — is fully ticked (short-decl
was already implemented). Phase 4 starts here.

lib/go/eval.sx single judgment:

  (go-eval ENV EXPR)  →  VALUE | (list :eval-error TAG ...)

ENV is an association list of (NAME VALUE) bindings — same shape as
the type checker's ctx, but the entries are runtime values. Values
are represented directly in SX: integers/floats as SX numbers,
strings as SX strings, booleans as true/false, nil as nil. Composite
values (slices/maps/structs/pointers/channels) arrive in later slices.

First-slice coverage:

  * go-env-empty / -lookup / -extend
  * Literal decoding:
      decimal (with underscores)
      hex (0x.. / 0X..)
      oct (0o.. / 0O..)
      bin (0b.. / 0B..)
    via go-hex-digit-value (explicit char equality — SX's nth on
    strings returns single-char strings, not numeric codes; the
    arithmetic-on-char-codes pattern from the OCaml kernel ports
    doesn't work here).
  * Identifier lookup with predeclared true / false / nil.
  * Binops: + - * / and the six comparison ops and && / ||.
  * Errors as (:eval-error TAG ...) sentinels.

Statements (block / return / short-decl / assign), control flow
(if / for), and function application / closures arrive in subsequent
slices.

eval 25/25, total 402/402.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 21:11:20 +00:00
parent 21bb17e4a6
commit ff9abe3ae6
6 changed files with 350 additions and 21 deletions

View File

@@ -30,6 +30,7 @@ SUITES=(
"lex|go-test-pass|go-test-count"
"parse|go-parse-test-pass|go-parse-test-count"
"types|go-types-test-pass|go-types-test-count"
"eval|go-eval-test-pass|go-eval-test-count"
)
cat > "$TMPFILE" <<'EPOCHS'
@@ -40,9 +41,11 @@ cat > "$TMPFILE" <<'EPOCHS'
(load "lib/go/lex.sx")
(load "lib/go/parse.sx")
(load "lib/go/types.sx")
(load "lib/go/eval.sx")
(load "lib/go/tests/lex.sx")
(load "lib/go/tests/parse.sx")
(load "lib/go/tests/types.sx")
(load "lib/go/tests/eval.sx")
EPOCHS
idx=0
@@ -107,7 +110,6 @@ cat > lib/go/scoreboard.json <<JSON
"total_pass": $TOTAL_PASS,
"total": $TOTAL_COUNT,
"suites": [$JSON_SUITES,
{"name":"eval","pass":0,"total":0,"status":"pending"},
{"name":"runtime","pass":0,"total":0,"status":"pending"},
{"name":"stdlib","pass":0,"total":0,"status":"pending"},
{"name":"e2e","pass":0,"total":0,"status":"pending"}
@@ -122,8 +124,7 @@ cat > lib/go/scoreboard.md <<MD
| | Suite | Pass | Total |
|---|---|---|---|
$MD_ROWS|| eval | 0 | 0 |
|| runtime | 0 | 0 |
$MD_ROWS|| runtime | 0 | 0 |
|| stdlib | 0 | 0 |
|| e2e | 0 | 0 |

215
lib/go/eval.sx Normal file
View File

@@ -0,0 +1,215 @@
;; lib/go/eval.sx — Go tree-walk evaluator.
;;
;; (go-eval ENV EXPR) → VALUE | (list :eval-error TAG ...)
;;
;; ENV is an association list of (NAME VALUE) bindings. Per-block scope
;; via fresh extension. Values:
;; integers → SX numbers (decimal/hex/oct/bin literals all decoded)
;; strings → SX strings
;; booleans → SX true/false
;; nil → SX nil
;; Composite Go values (slices, maps, structs, pointers, channels)
;; arrive in later slices.
(define go-env-empty (list))
(define
go-env-lookup
(fn
(env name)
(cond
(= (len env) 0)
nil
(= (first (first env)) name)
(nth (first env) 1)
:else (go-env-lookup (rest env) name))))
(define go-env-extend (fn (env name value) (cons (list name value) env)))
(define
go-eval-error?
(fn
(x)
(and
(list? x)
(not (= (len x) 0))
(= (first x) :eval-error))))
;; ── literal parsing ──────────────────────────────────────────────
(define
go-hex-digit-value
(fn
(c)
(cond
(= c "0")
0
(= c "1")
1
(= c "2")
2
(= c "3")
3
(= c "4")
4
(= c "5")
5
(= c "6")
6
(= c "7")
7
(= c "8")
8
(= c "9")
9
(= c "a")
10
(= c "b")
11
(= c "c")
12
(= c "d")
13
(= c "e")
14
(= c "f")
15
(= c "A")
10
(= c "B")
11
(= c "C")
12
(= c "D")
13
(= c "E")
14
(= c "F")
15
:else -1)))
(define
go-parse-radix-from
(fn
(v start radix)
(define
grf-loop
(fn
(i acc)
(cond
(>= i (len v))
acc
(= (nth v i) "_")
(grf-loop (+ i 1) acc)
:else (let
((d (go-hex-digit-value (nth v i))))
(cond
(or (< d 0) (>= d radix))
acc
:else (grf-loop (+ i 1) (+ (* acc radix) d)))))))
(grf-loop start 0)))
(define
go-parse-int-literal
(fn
(v)
(cond
(and
(>= (len v) 2)
(= (nth v 0) "0")
(or (= (nth v 1) "x") (= (nth v 1) "X")))
(go-parse-radix-from v 2 16)
(and
(>= (len v) 2)
(= (nth v 0) "0")
(or (= (nth v 1) "b") (= (nth v 1) "B")))
(go-parse-radix-from v 2 2)
(and
(>= (len v) 2)
(= (nth v 0) "0")
(or (= (nth v 1) "o") (= (nth v 1) "O")))
(go-parse-radix-from v 2 8)
:else (go-parse-radix-from v 0 10))))
(define
go-eval-literal
(fn
(v)
(let
((k (go-classify-literal-string v)))
(cond (= k :int) (go-parse-int-literal v) (= k :string) v :else v))))
;; ── binary ops ───────────────────────────────────────────────────
(define
go-eval-binop
(fn
(op l r)
(cond
(= op "+")
(+ l r)
(= op "-")
(- l r)
(= op "*")
(* l r)
(= op "/")
(/ l r)
(= op "==")
(= l r)
(= op "!=")
(not (= l r))
(= op "<")
(< l r)
(= op "<=")
(<= l r)
(= op ">")
(> l r)
(= op ">=")
(>= l r)
(= op "&&")
(and l r)
(= op "||")
(or l r)
:else (list :eval-error :unsupported-binop op))))
;; ── main eval ────────────────────────────────────────────────────
(define
go-eval
(fn
(env expr)
(cond
(and (list? expr) (= (first expr) :literal))
(go-eval-literal (nth expr 1))
(and (list? expr) (= (first expr) :var))
(let
((name (nth expr 1)))
(cond
(= name "true")
true
(= name "false")
false
(= name "nil")
nil
:else (let
((v (go-env-lookup env name)))
(cond (= v nil) (list :eval-error :unbound name) :else v))))
(and
(list? expr)
(= (first expr) :app)
(list? (nth expr 1))
(= (first (nth expr 1)) :var)
(= (len (nth expr 2)) 2))
(let
((op (nth (nth expr 1) 1))
(args (nth expr 2)))
(let
((lv (go-eval env (first args)))
(rv (go-eval env (nth args 1))))
(cond
(go-eval-error? lv)
lv
(go-eval-error? rv)
rv
:else (go-eval-binop op lv rv))))
:else (list :eval-error :unsupported-eval expr))))

View File

@@ -1,12 +1,12 @@
{
"language": "go",
"total_pass": 377,
"total": 377,
"total_pass": 402,
"total": 402,
"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":0,"total":0,"status":"pending"},
{"name":"eval","pass":25,"total":25,"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: 377 / 377 tests passing**
**Total: 402 / 402 tests passing**
| | Suite | Pass | Total |
|---|---|---|---|
| ✅ | lex | 129 | 129 |
| ✅ | parse | 176 | 176 |
| ✅ | types | 72 | 72 |
| | eval | 0 | 0 |
| | eval | 25 | 25 |
| ⬜ | runtime | 0 | 0 |
| ⬜ | stdlib | 0 | 0 |
| ⬜ | e2e | 0 | 0 |

95
lib/go/tests/eval.sx Normal file
View File

@@ -0,0 +1,95 @@
;; 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 ──────────────────────────────────────────────────────
(define
go-eval-test-summary
(str "eval " go-eval-test-pass "/" go-eval-test-count))