;; Block-intrinsifier tests.
;;
;; AST-level recognition of `ifTrue:`, `ifFalse:`, `ifTrue:ifFalse:`,
;; `ifFalse:ifTrue:`, `whileTrue:`, `whileFalse:`, `and:`, `or:`
;; short-circuits dispatch when the block argument is simple
;; (no params, no temps).

(set! st-test-pass 0)
(set! st-test-fail 0)
(set! st-test-fails (list))

(st-bootstrap-classes!)
(define ev (fn (src) (smalltalk-eval src)))
(define evp (fn (src) (smalltalk-eval-program src)))

;; ── 1. Each intrinsic increments the hit counter ──
(st-intrinsic-reset!)

(ev "true ifTrue: [1]")
(st-test "ifTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(ev "false ifFalse: [2]")
(st-test "ifFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(ev "true ifTrue: [1] ifFalse: [2]")
(st-test "ifTrue:ifFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(ev "false ifFalse: [1] ifTrue: [2]")
(st-test "ifFalse:ifTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(ev "true and: [42]")
(st-test "and: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(ev "false or: [99]")
(st-test "or: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(evp "| n | n := 5. [n > 0] whileTrue: [n := n - 1]. ^ n")
(st-test "whileTrue: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

(st-intrinsic-reset!)
(evp "| n | n := 0. [n >= 3] whileFalse: [n := n + 1]. ^ n")
(st-test "whileFalse: hit" (>= (get (st-intrinsic-stats) :hits) 1) true)

;; ── 2. Intrinsified results match the dispatched ones ──
(st-test "ifTrue: with true branch"  (ev "true ifTrue: [42]") 42)
(st-test "ifTrue: with false branch" (ev "false ifTrue: [42]") nil)
(st-test "ifFalse: with false branch"(ev "false ifFalse: [42]") 42)
(st-test "ifFalse: with true branch" (ev "true ifFalse: [42]") nil)
(st-test "ifTrue:ifFalse: t"  (ev "true ifTrue: [1] ifFalse: [2]") 1)
(st-test "ifTrue:ifFalse: f"  (ev "false ifTrue: [1] ifFalse: [2]") 2)
(st-test "ifFalse:ifTrue: t"  (ev "true ifFalse: [1] ifTrue: [2]") 2)
(st-test "ifFalse:ifTrue: f"  (ev "false ifFalse: [1] ifTrue: [2]") 1)
(st-test "and: short-circuits" (ev "false and: [1/0]") false)
(st-test "or: short-circuits"  (ev "true or: [1/0]") true)

(st-test "whileTrue: completes counting"
  (evp "| n | n := 5. [n > 0] whileTrue: [n := n - 1]. ^ n") 0)
(st-test "whileFalse: completes counting"
  (evp "| n | n := 0. [n >= 3] whileFalse: [n := n + 1]. ^ n") 3)

;; ── 3. Blocks with params or temps fall through to dispatch ──
(st-intrinsic-reset!)
(ev "true ifTrue: [| t | t := 1. t]")
(st-test "block-with-temps falls through (no intrinsic hit)"
  (get (st-intrinsic-stats) :hits) 0)

;; ── 4. ^ inside an intrinsified block still escapes the method ──
(st-class-define! "EarlyOut" "Object" (list))
(st-class-add-method! "EarlyOut" "search:in:"
  (st-parse-method
    "search: target in: arr
       arr do: [:e | e = target ifTrue: [^ e]].
       ^ nil"))

(st-test "^ from intrinsified ifTrue: still returns from method"
  (evp "^ EarlyOut new search: 3 in: #(1 2 3 4 5)") 3)
(st-test "^ falls through when no match"
  (evp "^ EarlyOut new search: 99 in: #(1 2 3)") nil)

;; ── 5. Intrinsics don't break under repeated invocation ──
(st-intrinsic-reset!)
(evp "| n | n := 0. 1 to: 100 do: [:i | n := n + 1]. ^ n")
(st-test "intrinsified to:do: ran (counter reflects ifTrue:s inside)"
  (>= (get (st-intrinsic-stats) :hits) 0) true)

(list st-test-pass st-test-fail)
