;; Reflection accessors: Object>>class, class>>name, class>>superclass, ;; class>>methodDict, class>>selectors. Phase 4 starting point. (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. Object>>class on native receivers ── (st-test "42 class name" (ev "42 class name") "SmallInteger") (st-test "3.14 class name" (ev "3.14 class name") "Float") (st-test "'hi' class name" (ev "'hi' class name") "String") (st-test "#foo class name" (ev "#foo class name") "Symbol") (st-test "true class name" (ev "true class name") "True") (st-test "false class name" (ev "false class name") "False") (st-test "nil class name" (ev "nil class name") "UndefinedObject") (st-test "$a class name" (ev "$a class name") "String") (st-test "#(1 2 3) class name" (ev "#(1 2 3) class name") "Array") (st-test "[42] class name" (ev "[42] class name") "BlockClosure") ;; ── 2. Object>>class on user instances ── (st-class-define! "Cat" "Object" (list "name")) (st-test "user instance class name" (evp "^ Cat new class name") "Cat") (st-test "user instance class superclass name" (evp "^ Cat new class superclass name") "Object") ;; ── 3. class>>name / class>>superclass ── (st-test "class>>name on Object" (ev "Object name") "Object") (st-test "class>>superclass on Object" (ev "Object superclass") nil) (st-test "class>>superclass on Symbol" (ev "Symbol superclass name") "String") (st-test "class>>superclass on String" (ev "String superclass name") "ArrayedCollection") ;; ── 4. class>>class returns Metaclass ── (st-test "Cat class is Metaclass" (ev "Cat class name") "Metaclass") ;; ── 5. class>>methodDict ── (st-class-add-method! "Cat" "miaow" (st-parse-method "miaow ^ #miaow")) (st-class-add-method! "Cat" "purr" (st-parse-method "purr ^ #purr")) (st-test "methodDict has expected keys" (sort (keys (ev "Cat methodDict"))) (sort (list "miaow" "purr"))) (st-test "methodDict size after two adds" (len (keys (ev "Cat methodDict"))) 2) ;; ── 6. class>>selectors ── (st-test "selectors returns Array of symbols" (sort (map (fn (s) (str s)) (ev "Cat selectors"))) (sort (list "miaow" "purr"))) ;; ── 7. class>>instanceVariableNames ── (st-test "instance variable names" (ev "Cat instanceVariableNames") (list "name")) (st-class-define! "Kitten" "Cat" (list "age")) (st-test "subclass own ivars" (ev "Kitten instanceVariableNames") (list "age")) (st-test "subclass allInstVarNames includes inherited" (ev "Kitten allInstVarNames") (list "name" "age")) ;; ── 8. methodDict reflects new methods ── (st-class-add-method! "Cat" "scratch" (st-parse-method "scratch ^ #scratch")) (st-test "methodDict updated after add" (len (keys (ev "Cat methodDict"))) 3) ;; ── 9. classMethodDict / classSelectors ── (st-class-add-class-method! "Cat" "named:" (st-parse-method "named: aName ^ self new")) (st-test "classSelectors" (map (fn (s) (str s)) (ev "Cat classSelectors")) (list "named:")) ;; ── 10. Method records are usable values ── (st-test "methodDict at: returns method record dict" (dict? (get (ev "Cat methodDict") "miaow")) true) ;; ── 11. Object>>perform: ── (st-test "perform: a unary selector" (str (evp "^ Cat new perform: #miaow")) "miaow") (st-test "perform: works on native receiver" (ev "42 perform: #printString") "42") (st-test "perform: with no method falls back to DNU" ;; With no Object DNU defined here, perform: a missing selector raises. ;; Wrap in guard to catch. (let ((caught false)) (begin (guard (c (true (set! caught true))) (evp "^ Cat new perform: #nonexistent")) caught)) true) ;; ── 12. Object>>perform:with: ── (st-class-add-method! "Cat" "say:" (st-parse-method "say: aMsg ^ aMsg")) (st-test "perform:with: passes arg through" (evp "^ Cat new perform: #say: with: 'hi'") "hi") (st-test "perform:with: on native" (ev "10 perform: #+ with: 5") 15) ;; ── 13. Object>>perform:with:with: (multi-arg form) ── (st-class-add-method! "Cat" "describe:and:" (st-parse-method "describe: a and: b ^ a , b")) (st-test "perform:with:with: keyword selector" (evp "^ Cat new perform: #describe:and: with: 'foo' with: 'bar'") "foobar") ;; ── 14. Object>>perform:withArguments: ── (st-test "perform:withArguments: empty array" (str (evp "^ Cat new perform: #miaow withArguments: #()")) "miaow") (st-test "perform:withArguments: 1 element" (evp "^ Cat new perform: #say: withArguments: #('hello')") "hello") (st-test "perform:withArguments: 2 elements" (evp "^ Cat new perform: #describe:and: withArguments: #('a' 'b')") "ab") (st-test "perform:withArguments: on native receiver" (ev "20 perform: #+ withArguments: #(5)") 25) ;; perform: routes through ordinary dispatch, so super, DNU, primitives ;; all still apply naturally. No special test for that — it's free. ;; ── 15. isKindOf: walks the class chain ── (st-test "42 isKindOf: SmallInteger" (ev "42 isKindOf: SmallInteger") true) (st-test "42 isKindOf: Integer" (ev "42 isKindOf: Integer") true) (st-test "42 isKindOf: Number" (ev "42 isKindOf: Number") true) (st-test "42 isKindOf: Magnitude" (ev "42 isKindOf: Magnitude") true) (st-test "42 isKindOf: Object" (ev "42 isKindOf: Object") true) (st-test "42 isKindOf: String" (ev "42 isKindOf: String") false) (st-test "3.14 isKindOf: Float" (ev "3.14 isKindOf: Float") true) (st-test "3.14 isKindOf: Number" (ev "3.14 isKindOf: Number") true) (st-test "'hi' isKindOf: String" (ev "'hi' isKindOf: String") true) (st-test "'hi' isKindOf: ArrayedCollection" (ev "'hi' isKindOf: ArrayedCollection") true) (st-test "true isKindOf: Boolean" (ev "true isKindOf: Boolean") true) (st-test "nil isKindOf: UndefinedObject" (ev "nil isKindOf: UndefinedObject") true) ;; User-class chain. (st-test "Cat new isKindOf: Cat" (evp "^ Cat new isKindOf: Cat") true) (st-test "Cat new isKindOf: Object" (evp "^ Cat new isKindOf: Object") true) (st-test "Cat new isKindOf: Boolean" (evp "^ Cat new isKindOf: Boolean") false) (st-test "Kitten new isKindOf: Cat" (evp "^ Kitten new isKindOf: Cat") true) ;; ── 16. isMemberOf: requires exact class match ── (st-test "42 isMemberOf: SmallInteger" (ev "42 isMemberOf: SmallInteger") true) (st-test "42 isMemberOf: Integer" (ev "42 isMemberOf: Integer") false) (st-test "42 isMemberOf: Number" (ev "42 isMemberOf: Number") false) (st-test "Cat new isMemberOf: Cat" (evp "^ Cat new isMemberOf: Cat") true) (st-test "Cat new isMemberOf: Kitten" (evp "^ Cat new isMemberOf: Kitten") false) ;; ── 17. respondsTo: — user method dictionary search ── (st-test "Cat respondsTo: #miaow" (evp "^ Cat new respondsTo: #miaow") true) (st-test "Cat respondsTo: inherited (only own/super in dict)" (evp "^ Kitten new respondsTo: #miaow") true) (st-test "Cat respondsTo: missing" (evp "^ Cat new respondsTo: #noSuchSelector") false) (st-test "respondsTo: on class-ref searches class side" (evp "^ Cat respondsTo: #named:") true) ;; Non-symbol arg coerces via str — also accepts strings. (st-test "respondsTo: with string arg" (evp "^ Cat new respondsTo: 'miaow'") true) ;; ── 18. Behavior>>compile: — runtime method addition ── (st-test "compile: a unary method" (begin (evp "Cat compile: 'whisker ^ 99'") (evp "^ Cat new whisker")) 99) (st-test "compile: returns the selector as a symbol" (str (evp "^ Cat compile: 'twitch ^ #twitch'")) "twitch") (st-test "compile: a keyword method" (begin (evp "Cat compile: 'doubled: x ^ x * 2'") (evp "^ Cat new doubled: 21")) 42) (st-test "compile: a method with temps and blocks" (begin (evp "Cat compile: 'sumTo: n | s | s := 0. 1 to: n do: [:i | s := s + i]. ^ s'") (evp "^ Cat new sumTo: 10")) 55) (st-test "recompile overrides existing method" (begin (evp "Cat compile: 'miaow ^ #ahem'") (str (evp "^ Cat new miaow"))) "ahem") ;; methodDict reflects the new method. (st-test "compile: registers in methodDict" (has-key? (ev "Cat methodDict") "whisker") true) ;; respondsTo: notices the new method. (st-test "respondsTo: sees compiled method" (evp "^ Cat new respondsTo: #whisker") true) ;; Behavior>>removeSelector: takes a method back out. (st-test "removeSelector: drops the method" (begin (evp "Cat removeSelector: #whisker") (evp "^ Cat new respondsTo: #whisker")) false) ;; compile:classified: ignores the extra arg. (st-test "compile:classified: works" (begin (evp "Cat compile: 'taggedMethod ^ #yes' classified: 'demo'") (str (evp "^ Cat new taggedMethod"))) "yes") ;; ── 19. Object>>becomeForward: ── (st-class-define! "Box" "Object" (list "value")) (st-class-add-method! "Box" "value" (st-parse-method "value ^ value")) (st-class-add-method! "Box" "value:" (st-parse-method "value: v value := v. ^ self")) (st-class-add-method! "Box" "kind" (st-parse-method "kind ^ #box")) (st-class-define! "Crate" "Object" (list "value")) (st-class-add-method! "Crate" "value" (st-parse-method "value ^ value")) (st-class-add-method! "Crate" "value:" (st-parse-method "value: v value := v. ^ self")) (st-class-add-method! "Crate" "kind" (st-parse-method "kind ^ #crate")) (st-test "before becomeForward: instance reports its class" (str (evp "^ (Box new value: 1) class name")) "Box") (st-test "becomeForward: changes the receiver's class" (evp "| a b | a := Box new value: 1. b := Crate new value: 99. a becomeForward: b. ^ a class name") "Crate") (st-test "becomeForward: routes future sends through new class" (evp "| a b | a := Box new value: 1. b := Crate new value: 99. a becomeForward: b. ^ a kind") (make-symbol "crate")) (st-test "becomeForward: takes target's ivars" (evp "| a b | a := Box new value: 1. b := Crate new value: 99. a becomeForward: b. ^ a value") 99) (st-test "becomeForward: leaves the *target* instance unchanged" (evp "| a b | a := Box new value: 1. b := Crate new value: 99. a becomeForward: b. ^ b kind") (make-symbol "crate")) (st-test "every reference to the receiver sees the new identity" (evp "| a alias b | a := Box new value: 1. alias := a. b := Crate new value: 99. a becomeForward: b. ^ alias kind") (make-symbol "crate")) (list st-test-pass st-test-fail)