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