Files
rose-ash/lib/tests/test-vm-primitives.sx
giles c923a34fa8 Fix WASM browser click handlers: 8 bugs, 50 new VM tests
The sx-get links were doing full page refreshes because click handlers
never attached. Root causes: VM frame management bug, missing primitives,
CEK/VM type dispatch mismatch, and silent error swallowing.

Fixes:
- VM frame exhaustion: frames <- [] now properly pops to rest_frames
- length primitive: add alias for len in OCaml primitives
- call_sx_fn: use sx_call directly instead of eval_expr (CEK checks
  for type "lambda" but VmClosure reports "function")
- Boot error surfacing: Sx.init() now has try/catch + failure summary
- Callback error surfacing: catch-all handler for non-Eval_error exceptions
- Silent JIT failures: log before CEK fallback instead of swallowing
- vm→env sync: loadModule now calls sync_vm_to_env()
- sx_build_bytecode MCP tool added for bytecode compilation

Tests: 50 new tests across test-vm.sx and test-vm-primitives.sx covering
nested VM calls, frame integrity, CEK bridge, primitive availability,
cross-module symbol resolution, and callback dispatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:37:21 +00:00

173 lines
5.0 KiB
Plaintext

(define
vm-eval
(fn
(expr)
(let
((code (compile expr)))
(vm-execute-module (code-from-value code) {}))))
(define
vm-eval-with
(fn
(expr globals)
(let
((code (compile expr)))
(vm-execute-module (code-from-value code) globals))))
(define
try-eval
(fn
(expr)
(try-catch (fn () (vm-eval expr)) (fn (err) (str "Error: " err)))))
(defsuite
"vm-primitive-availability"
(deftest
"length is available"
(assert-equal 3 (vm-eval (quote (length (list 1 2 3))))))
(deftest
"len is available"
(assert-equal 3 (vm-eval (quote (len (list 1 2 3))))))
(deftest
"first is available"
(assert-equal 1 (vm-eval (quote (first (list 1 2 3))))))
(deftest
"rest is available"
(assert-equal (list 2 3) (vm-eval (quote (rest (list 1 2 3))))))
(deftest
"nth is available"
(assert-equal 2 (vm-eval (quote (nth (list 1 2 3) 1)))))
(deftest
"get is available"
(assert-equal "v" (vm-eval (quote (get {:k "v"} :k)))))
(deftest
"type-of is available"
(assert-equal "number" (vm-eval (quote (type-of 42)))))
(deftest
"str concatenation works"
(assert-equal "ab" (vm-eval (quote (str "a" "b")))))
(deftest
"empty? is available"
(assert-equal true (vm-eval (quote (empty? (list))))))
(deftest
"not is available"
(assert-equal true (vm-eval (quote (not false)))))
(deftest
"append is available"
(assert-equal
(list 1 2 3)
(vm-eval (quote (append (list 1 2) (list 3))))))
(deftest
"assoc is available"
(assert-equal "b" (vm-eval (quote (get (assoc {:a "a"} :a "b") :a)))))
(deftest
"keys is available"
(assert-equal 1 (vm-eval (quote (len (keys {:a 1}))))))
(deftest
"contains? is available"
(assert-equal true (vm-eval (quote (contains? "hello" "ell")))))
(deftest
"starts-with? is available"
(assert-equal true (vm-eval (quote (starts-with? "hello" "hel")))))
(deftest
"split is available"
(assert-equal 3 (vm-eval (quote (len (split "a,b,c" ","))))))
(deftest
"join is available"
(assert-equal "a,b" (vm-eval (quote (join "," (list "a" "b"))))))
(deftest
"trim is available"
(assert-equal "x" (vm-eval (quote (trim " x ")))))
(deftest
"upper is available"
(assert-equal "ABC" (vm-eval (quote (upper "abc"))))))
(defsuite
"vm-cross-module-symbols"
(deftest
"module B sees module A definition"
(let
((g (dict)))
(vm-eval-with (quote (define helper (fn (x) (+ x 100)))) g)
(vm-eval-with (quote (define user (fn (x) (helper x)))) g)
(assert-equal 142 (vm-eval-with (quote (user 42)) g))))
(deftest
"module C sees definitions from both A and B"
(let
((g (dict)))
(vm-eval-with (quote (define a (fn (x) (* x 2)))) g)
(vm-eval-with (quote (define b (fn (x) (+ (a x) 1)))) g)
(vm-eval-with (quote (define c (fn (x) (b (a x))))) g)
(assert-equal 21 (vm-eval-with (quote (c 5)) g))))
(deftest
"late-defined symbol visible to earlier closure"
(let
((g (dict)))
(vm-eval-with (quote (define caller (fn () (callee 10)))) g)
(vm-eval-with (quote (define callee (fn (x) (* x 3)))) g)
(assert-equal 30 (vm-eval-with (quote (caller)) g)))))
(defsuite
"vm-callback-dispatch"
(deftest
"simple closure callback"
(let
((g (dict)))
(vm-eval-with
(quote (define make-fn (fn (n) (fn (x) (+ n x)))))
g)
(let
((result (vm-eval-with (quote (let ((f (make-fn 10))) (f 5))) g)))
(assert-equal 15 result))))
(deftest
"closure capturing multiple vars"
(let
((g (dict)))
(vm-eval-with
(quote (define make-adder (fn (a b) (fn (x) (+ a b x)))))
g)
(assert-equal 60 (vm-eval-with (quote ((make-adder 10 20) 30)) g))))
(deftest
"nested closure callback"
(let
((g (dict)))
(vm-eval-with
(quote (define wrap (fn (f) (fn (x) (f (f x))))))
g)
(vm-eval-with (quote (define inc (fn (x) (+ x 1)))) g)
(assert-equal 12 (vm-eval-with (quote ((wrap inc) 10)) g)))))
(defsuite
"vm-error-surfacing"
(deftest
"undefined symbol in compiled fn raises error"
(let
((g (dict)) (ok false))
(vm-eval-with (quote (define good-fn (fn () 42))) g)
(set! ok (= 42 (vm-eval-with (quote (good-fn)) g)))
(assert-equal true ok)))
(deftest
"compiled fn calling another compiled fn works"
(let
((g (dict)))
(vm-eval-with (quote (define a (fn (x) (+ x 1)))) g)
(vm-eval-with (quote (define b (fn (x) (a x)))) g)
(assert-equal 6 (vm-eval-with (quote (b 5)) g))))
(deftest
"when block executes all body forms"
(assert-equal
30
(vm-eval
(quote
(let ((x 0)) (when true (set! x 10) (set! x (+ x 20))) x)))))
(deftest
"when block: function call then side effect"
(assert-equal
50
(vm-eval
(quote
(let
((x 0) (f (fn (n) (* n 10))))
(when true (f 1) (set! x (f 5)))
x))))))