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>
118 lines
4.9 KiB
Plaintext
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))))
|