Files
rose-ash/spec/tests/vm-inline.sx
giles 57cffb8bcc Fix isomorphic SSR: revert inline opcodes, add named let compilation, fix cookie decode
Three bugs broke island SSR rendering of the home stepper widget:

1. Inline VM opcodes (OP_ADD..OP_DEC) broke JIT-compiled functions.
   The compiler emitted single-byte opcodes for first/rest/len/= etc.
   that produced wrong results in complex recursive code (sx-parse
   returned nil, split-tag produced 1 step instead of 16). Reverted
   compiler to use CALL_PRIM for all primitives. VM opcode handlers
   kept for future use.

2. Named let (let loop ((x init)) body) had no compiler support —
   silently produced broken bytecode. Added desugaring to letrec.

3. URL-encoded cookie values not decoded server-side. Client set-cookie
   uses encodeURIComponent but Werkzeug doesn't decode cookie values.
   Added unquote() in bridge cookie injection.

Also: call-lambda used eval_expr which copies Dict values (signals),
breaking mutations through aser lambda calls. Switched to cek_call.

Also: stepper preview now includes ~cssx/tw spreads for SSR styling.

Tests: 1317 JS, 1114 OCaml, 26 integration (2 pre-existing failures)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 22:32:51 +00:00

118 lines
4.9 KiB
Plaintext

;; vm-inline.sx — Tests for inline VM opcodes (OP_ADD, OP_EQ, etc.)
;;
;; These verify that the JIT-compiled inline opcodes produce
;; identical results to the CALL_PRIM fallback.
;; --------------------------------------------------------------------------
;; Arithmetic
;; --------------------------------------------------------------------------
(test "inline + integers" (= (+ 3 4) 7))
(test "inline + floats" (= (+ 1.5 2.5) 4.0))
(test "inline + string concat" (= (+ "hello" " world") "hello world"))
(test "inline - integers" (= (- 10 3) 7))
(test "inline - negative" (= (- 3 10) -7))
(test "inline * integers" (= (* 6 7) 42))
(test "inline * float" (= (* 2.5 4.0) 10.0))
(test "inline / integers" (= (/ 10 2) 5))
(test "inline / float" (= (/ 7.0 2.0) 3.5))
(test "inline inc" (= (inc 5) 6))
(test "inline dec" (= (dec 5) 4))
(test "inline inc float" (= (inc 2.5) 3.5))
(test "inline dec zero" (= (dec 0) -1))
;; --------------------------------------------------------------------------
;; Comparison
;; --------------------------------------------------------------------------
(test "inline = numbers" (= 5 5))
(test "inline = strings" (= "hello" "hello"))
(test "inline = false" (not (= 5 6)))
(test "inline = nil" (= nil nil))
(test "inline = mixed false" (not (= 5 "5")))
(test "inline < numbers" (< 3 5))
(test "inline < false" (not (< 5 3)))
(test "inline < equal" (not (< 5 5)))
(test "inline < strings" (< "abc" "def"))
(test "inline > numbers" (> 5 3))
(test "inline > false" (not (> 3 5)))
(test "inline > equal" (not (> 5 5)))
(test "inline not true" (= (not true) false))
(test "inline not false" (= (not false) true))
(test "inline not nil" (= (not nil) true))
(test "inline not number" (= (not 0) true))
(test "inline not string" (= (not "") true))
(test "inline not nonempty" (= (not "x") false))
;; --------------------------------------------------------------------------
;; Collection ops
;; --------------------------------------------------------------------------
(test "inline len list" (= (len (list 1 2 3)) 3))
(test "inline len string" (= (len "hello") 5))
(test "inline len empty" (= (len (list)) 0))
(test "inline len nil" (= (len nil) 0))
(test "inline first" (= (first (list 10 20 30)) 10))
(test "inline first empty" (= (first (list)) nil))
(test "inline rest" (= (rest (list 1 2 3)) (list 2 3)))
(test "inline rest single" (= (rest (list 1)) (list)))
(test "inline nth" (= (nth (list 10 20 30) 1) 20))
(test "inline nth zero" (= (nth (list 10 20 30) 0) 10))
(test "inline nth out of bounds" (= (nth (list 1 2) 5) nil))
(test "inline cons" (= (cons 1 (list 2 3)) (list 1 2 3)))
(test "inline cons to empty" (= (cons 1 (list)) (list 1)))
(test "inline cons to nil" (= (cons 1 nil) (list 1)))
;; --------------------------------------------------------------------------
;; Composition — inline ops in expressions
;; --------------------------------------------------------------------------
(test "nested arithmetic" (= (+ (* 3 4) (- 10 5)) 17))
(test "comparison in if" (if (< 3 5) "yes" "no") (= "yes"))
(test "len in condition" (if (> (len (list 1 2 3)) 2) true false))
(test "inc in loop" (= (let ((x 0)) (for-each (fn (_) (set! x (inc x))) (list 1 2 3)) x) 3))
(test "first + rest roundtrip" (= (cons (first (list 1 2 3)) (rest (list 1 2 3))) (list 1 2 3)))
(test "nested comparison" (= (and (< 1 2) (> 3 0) (= 5 5)) true))
;; --------------------------------------------------------------------------
;; Edge cases
;; --------------------------------------------------------------------------
(test "+ with nil" (= (+ 5 nil) 5))
(test "len of dict" (= (len {:a 1 :b 2}) 2))
(test "= with booleans" (= (= true true) true))
(test "= with keywords" (= (= :foo :foo) true))
(test "not with list" (= (not (list 1)) false))
;; --------------------------------------------------------------------------
;; Recursive mutation — VM closure capture must preserve mutable state
;; --------------------------------------------------------------------------
;;
;; Regression: recursive functions that append! to a shared mutable list
;; lost mutations after the first call under JIT. The VM closure capture
;; was copying the list value instead of sharing the mutable reference.
(test "recursive append! to shared list"
(let ((walk (fn (items result)
(when (not (empty? items))
(append! result (first items))
(walk (rest items) result)))))
(let ((result (list)))
(walk (list "a" "b" "c") result)
(= (len result) 3))))
(test "recursive tree walk with append!"
(let ((walk (fn (expr result)
(cond
(not (list? expr))
(append! result "leaf")
(empty? expr) nil
:else
(do (append! result "open")
(for-each (fn (c) (walk c result)) (rest expr))
(append! result "close"))))))
(let ((tree (first (sx-parse "(div \"a\" (span \"b\") \"c\")")))
(result (list)))
(walk tree result)
(= (len result) 7))))