;; ifTrue: / ifFalse: / ifTrue:ifFalse: / ifFalse:ifTrue: tests. ;; ;; In Smalltalk these are *block sends* on Boolean. The runtime can ;; intrinsify the dispatch in the JIT (already provided by the bytecode ;; expansion infrastructure) but the spec semantics are: True/False ;; receive these messages and pick which branch block to evaluate. (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. ifTrue: ── (st-test "true ifTrue: → block value" (ev "true ifTrue: [42]") 42) (st-test "false ifTrue: → nil" (ev "false ifTrue: [42]") nil) ;; ── 2. ifFalse: ── (st-test "true ifFalse: → nil" (ev "true ifFalse: [42]") nil) (st-test "false ifFalse: → block value" (ev "false ifFalse: [42]") 42) ;; ── 3. ifTrue:ifFalse: ── (st-test "true ifTrue:ifFalse:" (ev "true ifTrue: [1] ifFalse: [2]") 1) (st-test "false ifTrue:ifFalse:" (ev "false ifTrue: [1] ifFalse: [2]") 2) ;; ── 4. ifFalse:ifTrue: (reversed-order keyword) ── (st-test "true ifFalse:ifTrue:" (ev "true ifFalse: [1] ifTrue: [2]") 2) (st-test "false ifFalse:ifTrue:" (ev "false ifFalse: [1] ifTrue: [2]") 1) ;; ── 5. The non-taken branch is NOT evaluated (laziness) ── (st-test "ifTrue: doesn't evaluate the false branch" (evp "| ran | ran := false. true ifTrue: [99] ifFalse: [ran := true. 0]. ^ ran") false) (st-test "ifFalse: doesn't evaluate the true branch" (evp "| ran | ran := false. false ifTrue: [ran := true. 99] ifFalse: [0]. ^ ran") false) ;; ── 6. Branch result type can be anything ── (st-test "branch returns string" (ev "true ifTrue: ['yes'] ifFalse: ['no']") "yes") (st-test "branch returns nil" (ev "true ifTrue: [nil] ifFalse: [99]") nil) (st-test "branch returns array" (ev "false ifTrue: [#(1)] ifFalse: [#(2 3)]") (list 2 3)) ;; ── 7. Nested if ── (st-test "nested ifTrue:ifFalse:" (evp "| x | x := 5. ^ x > 0 ifTrue: [x > 10 ifTrue: [#big] ifFalse: [#smallPositive]] ifFalse: [#nonPositive]") (make-symbol "smallPositive")) ;; ── 8. Branch reads outer locals (closure semantics) ── (st-test "branch closes over outer bindings" (evp "| label x | x := 7. label := x > 0 ifTrue: [#positive] ifFalse: [#nonPositive]. ^ label") (make-symbol "positive")) ;; ── 9. and: / or: short-circuit ── (st-test "and: short-circuits when receiver false" (ev "false and: [1/0]") false) (st-test "and: with true receiver runs second" (ev "true and: [42]") 42) (st-test "or: short-circuits when receiver true" (ev "true or: [1/0]") true) (st-test "or: with false receiver runs second" (ev "false or: [99]") 99) ;; ── 10. & and | are eager (not blocks) ── (st-test "& on booleans" (ev "true & true") true) (st-test "| on booleans" (ev "false | true") true) ;; ── 11. Boolean negation ── (st-test "not on true" (ev "true not") false) (st-test "not on false" (ev "false not") true) ;; ── 12. Real-world idiom: max via ifTrue:ifFalse: in a method ── (st-class-define! "Mathy" "Object" (list)) (st-class-add-method! "Mathy" "myMax:and:" (st-parse-method "myMax: a and: b ^ a > b ifTrue: [a] ifFalse: [b]")) (st-test "method using ifTrue:ifFalse: returns max" (evp "^ Mathy new myMax: 3 and: 7") 7) (st-test "method using ifTrue:ifFalse: returns max sym" (evp "^ Mathy new myMax: 9 and: 4") 9) (list st-test-pass st-test-fail)