- lib/common-lisp/clos.sx (27 forms): class registry (8 built-in classes), defclass/make-instance/slot-value/slot-boundp/change-class, defgeneric/defmethod with :before/:after/:around, clos-call-generic (standard combination: sort by specificity, fire befores, call primary chain, fire afters reversed), call-next-method/next-method-p, with-slots, deferred accessor installation - lib/common-lisp/tests/clos.sx: 41 tests (class-of, subclass-of?, defclass, make-instance, slot ops, inheritance, method specificity, qualifiers, accessors, with-slots, change-class) - lib/common-lisp/tests/programs/geometry.sx: 12 tests — intersect generic dispatching on geo-point×geo-point, geo-point×geo-line, geo-line×geo-line, geo-line×geo-plane (multi-dispatch by class precedence) - lib/common-lisp/tests/programs/mop-trace.sx: 13 tests — :before/:after tracing on area and describe-shape generics, call-next-method in circle/rect - eval.sx: dynamic variables — cl-apply-dyn saves/restores global slot for specials; cl-mark-special!/cl-special?/cl-dyn-unbound; defvar now marks specials; let/let* rebind via cl-apply-dyn; 8 new tests (182 eval total) - conformance.sh + test.sh: Phase 4 suites wired in - plans/common-lisp-on-sx.md: Phase 4 + dynamic variable boxes ticked Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
334 lines
10 KiB
Plaintext
334 lines
10 KiB
Plaintext
;; lib/common-lisp/tests/clos.sx — CLOS test suite
|
|
;;
|
|
;; Loaded after: spec/stdlib.sx, lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
|
|
|
(define passed 0)
|
|
(define failed 0)
|
|
(define failures (list))
|
|
|
|
(define
|
|
assert-equal
|
|
(fn
|
|
(label got expected)
|
|
(if
|
|
(= got expected)
|
|
(set! passed (+ passed 1))
|
|
(begin
|
|
(set! failed (+ failed 1))
|
|
(set!
|
|
failures
|
|
(append
|
|
failures
|
|
(list
|
|
(str
|
|
"FAIL ["
|
|
label
|
|
"]: got="
|
|
(inspect got)
|
|
" expected="
|
|
(inspect expected)))))))))
|
|
|
|
(define
|
|
assert-true
|
|
(fn
|
|
(label got)
|
|
(if
|
|
got
|
|
(set! passed (+ passed 1))
|
|
(begin
|
|
(set! failed (+ failed 1))
|
|
(set!
|
|
failures
|
|
(append
|
|
failures
|
|
(list
|
|
(str "FAIL [" label "]: expected true, got " (inspect got)))))))))
|
|
|
|
(define
|
|
assert-nil
|
|
(fn
|
|
(label got)
|
|
(if
|
|
(nil? got)
|
|
(set! passed (+ passed 1))
|
|
(begin
|
|
(set! failed (+ failed 1))
|
|
(set!
|
|
failures
|
|
(append
|
|
failures
|
|
(list (str "FAIL [" label "]: expected nil, got " (inspect got)))))))))
|
|
|
|
;; ── 1. class-of for built-in types ────────────────────────────────────────
|
|
|
|
(assert-equal "class-of integer" (clos-class-of 42) "integer")
|
|
(assert-equal "class-of float" (clos-class-of 3.14) "float")
|
|
(assert-equal "class-of string" (clos-class-of "hi") "string")
|
|
(assert-equal "class-of nil" (clos-class-of nil) "null")
|
|
(assert-equal "class-of list" (clos-class-of (list 1)) "cons")
|
|
(assert-equal "class-of empty" (clos-class-of (list)) "null")
|
|
|
|
;; ── 2. subclass-of? ───────────────────────────────────────────────────────
|
|
|
|
(assert-true "integer subclass-of t" (clos-subclass-of? "integer" "t"))
|
|
(assert-true "float subclass-of t" (clos-subclass-of? "float" "t"))
|
|
(assert-true "t subclass-of t" (clos-subclass-of? "t" "t"))
|
|
(assert-equal
|
|
"integer not subclass-of float"
|
|
(clos-subclass-of? "integer" "float")
|
|
false)
|
|
|
|
;; ── 3. defclass + make-instance ───────────────────────────────────────────
|
|
|
|
(clos-defclass "point" (list "t") (list {:initform 0 :initarg ":x" :reader nil :writer nil :accessor "point-x" :name "x"} {:initform 0 :initarg ":y" :reader nil :writer nil :accessor "point-y" :name "y"}))
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
|
(begin
|
|
(assert-equal "make-instance slot x" (clos-slot-value p "x") 3)
|
|
(assert-equal "make-instance slot y" (clos-slot-value p "y") 4)
|
|
(assert-equal "class-of instance" (clos-class-of p) "point")
|
|
(assert-true "instance-of? point" (clos-instance-of? p "point"))
|
|
(assert-true "instance-of? t" (clos-instance-of? p "t"))
|
|
(assert-equal "instance-of? string" (clos-instance-of? p "string") false)))
|
|
|
|
;; initform defaults
|
|
(let
|
|
((p0 (clos-make-instance "point")))
|
|
(begin
|
|
(assert-equal "initform default x=0" (clos-slot-value p0 "x") 0)
|
|
(assert-equal "initform default y=0" (clos-slot-value p0 "y") 0)))
|
|
|
|
;; ── 4. slot-value / set-slot-value! ──────────────────────────────────────
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 10 ":y" 20)))
|
|
(begin
|
|
(clos-set-slot-value! p "x" 99)
|
|
(assert-equal "set-slot-value! x" (clos-slot-value p "x") 99)
|
|
(assert-equal "slot-value y unchanged" (clos-slot-value p "y") 20)))
|
|
|
|
;; ── 5. slot-boundp ────────────────────────────────────────────────────────
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 5)))
|
|
(begin
|
|
(assert-true "slot-boundp x" (clos-slot-boundp p "x"))
|
|
(assert-true "slot-boundp y (initform 0)" (clos-slot-boundp p "y"))))
|
|
|
|
;; ── 6. find-class ─────────────────────────────────────────────────────────
|
|
|
|
(assert-equal
|
|
"find-class point"
|
|
(get (clos-find-class "point") "name")
|
|
"point")
|
|
(assert-nil "find-class missing" (clos-find-class "no-such-class"))
|
|
|
|
;; ── 7. inheritance ────────────────────────────────────────────────────────
|
|
|
|
(clos-defclass "colored-point" (list "point") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))
|
|
|
|
(let
|
|
((cp (clos-make-instance "colored-point" ":x" 1 ":y" 2 ":color" "red")))
|
|
(begin
|
|
(assert-equal "inherited slot x" (clos-slot-value cp "x") 1)
|
|
(assert-equal "inherited slot y" (clos-slot-value cp "y") 2)
|
|
(assert-equal "own slot color" (clos-slot-value cp "color") "red")
|
|
(assert-true
|
|
"instance-of? colored-point"
|
|
(clos-instance-of? cp "colored-point"))
|
|
(assert-true "instance-of? point (parent)" (clos-instance-of? cp "point"))
|
|
(assert-true "instance-of? t (root)" (clos-instance-of? cp "t"))))
|
|
|
|
;; ── 8. defgeneric + primary method ───────────────────────────────────────
|
|
|
|
(clos-defgeneric "describe-obj" {})
|
|
|
|
(clos-defmethod
|
|
"describe-obj"
|
|
(list)
|
|
(list "point")
|
|
(fn
|
|
(args next-fn)
|
|
(let
|
|
((p (first args)))
|
|
(str "(" (clos-slot-value p "x") "," (clos-slot-value p "y") ")"))))
|
|
|
|
(clos-defmethod
|
|
"describe-obj"
|
|
(list)
|
|
(list "t")
|
|
(fn (args next-fn) (str "object:" (inspect (first args)))))
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
|
(begin
|
|
(assert-equal
|
|
"primary method for point"
|
|
(clos-call-generic "describe-obj" (list p))
|
|
"(3,4)")
|
|
(assert-equal
|
|
"fallback t method"
|
|
(clos-call-generic "describe-obj" (list 42))
|
|
"object:42")))
|
|
|
|
;; ── 9. method inheritance + specificity ───────────────────────────────────
|
|
|
|
(clos-defmethod
|
|
"describe-obj"
|
|
(list)
|
|
(list "colored-point")
|
|
(fn
|
|
(args next-fn)
|
|
(let
|
|
((cp (first args)))
|
|
(str
|
|
(clos-slot-value cp "color")
|
|
"@("
|
|
(clos-slot-value cp "x")
|
|
","
|
|
(clos-slot-value cp "y")
|
|
")"))))
|
|
|
|
(let
|
|
((cp (clos-make-instance "colored-point" ":x" 5 ":y" 6 ":color" "blue")))
|
|
(assert-equal
|
|
"most specific method wins"
|
|
(clos-call-generic "describe-obj" (list cp))
|
|
"blue@(5,6)"))
|
|
|
|
;; ── 10. :before / :after / :around qualifiers ─────────────────────────────
|
|
|
|
(clos-defgeneric "logged-action" {})
|
|
|
|
(clos-defmethod
|
|
"logged-action"
|
|
(list "before")
|
|
(list "t")
|
|
(fn (args next-fn) (set! action-log (append action-log (list "before")))))
|
|
|
|
(clos-defmethod
|
|
"logged-action"
|
|
(list)
|
|
(list "t")
|
|
(fn
|
|
(args next-fn)
|
|
(set! action-log (append action-log (list "primary")))
|
|
"result"))
|
|
|
|
(clos-defmethod
|
|
"logged-action"
|
|
(list "after")
|
|
(list "t")
|
|
(fn (args next-fn) (set! action-log (append action-log (list "after")))))
|
|
|
|
(define action-log (list))
|
|
(clos-call-generic "logged-action" (list 1))
|
|
(assert-equal
|
|
":before/:after order"
|
|
action-log
|
|
(list "before" "primary" "after"))
|
|
|
|
;; :around
|
|
(define around-log (list))
|
|
|
|
(clos-defgeneric "wrapped-action" {})
|
|
|
|
(clos-defmethod
|
|
"wrapped-action"
|
|
(list "around")
|
|
(list "t")
|
|
(fn
|
|
(args next-fn)
|
|
(set! around-log (append around-log (list "around-enter")))
|
|
(let
|
|
((r (next-fn)))
|
|
(set! around-log (append around-log (list "around-exit")))
|
|
r)))
|
|
|
|
(clos-defmethod
|
|
"wrapped-action"
|
|
(list)
|
|
(list "t")
|
|
(fn
|
|
(args next-fn)
|
|
(set! around-log (append around-log (list "primary")))
|
|
42))
|
|
|
|
(let
|
|
((r (clos-call-generic "wrapped-action" (list nil))))
|
|
(begin
|
|
(assert-equal ":around result" r 42)
|
|
(assert-equal
|
|
":around log"
|
|
around-log
|
|
(list "around-enter" "primary" "around-exit"))))
|
|
|
|
;; ── 11. call-next-method ─────────────────────────────────────────────────
|
|
|
|
(clos-defgeneric "chain-test" {})
|
|
|
|
(clos-defmethod
|
|
"chain-test"
|
|
(list)
|
|
(list "colored-point")
|
|
(fn (args next-fn) (str "colored:" (clos-call-next-method next-fn))))
|
|
|
|
(clos-defmethod
|
|
"chain-test"
|
|
(list)
|
|
(list "point")
|
|
(fn (args next-fn) "point-base"))
|
|
|
|
(let
|
|
((cp (clos-make-instance "colored-point" ":x" 0 ":y" 0 ":color" "green")))
|
|
(assert-equal
|
|
"call-next-method chains"
|
|
(clos-call-generic "chain-test" (list cp))
|
|
"colored:point-base"))
|
|
|
|
;; ── 12. accessor methods ──────────────────────────────────────────────────
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 7 ":y" 8)))
|
|
(begin
|
|
(assert-equal
|
|
"accessor point-x"
|
|
(clos-call-generic "point-x" (list p))
|
|
7)
|
|
(assert-equal
|
|
"accessor point-y"
|
|
(clos-call-generic "point-y" (list p))
|
|
8)))
|
|
|
|
;; ── 13. with-slots ────────────────────────────────────────────────────────
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
|
(assert-equal
|
|
"with-slots"
|
|
(clos-with-slots p (list "x" "y") (fn (x y) (* x y)))
|
|
12))
|
|
|
|
;; ── 14. change-class ─────────────────────────────────────────────────────
|
|
|
|
(clos-defclass "special-point" (list "point") (list {:initform "" :initarg ":label" :reader nil :writer nil :accessor nil :name "label"}))
|
|
|
|
(let
|
|
((p (clos-make-instance "point" ":x" 1 ":y" 2)))
|
|
(begin
|
|
(clos-change-class! p "special-point")
|
|
(assert-equal
|
|
"change-class updates class"
|
|
(clos-class-of p)
|
|
"special-point")))
|
|
|
|
;; ── summary ────────────────────────────────────────────────────────────────
|
|
|
|
(if
|
|
(= failed 0)
|
|
(print (str "ok " passed "/" (+ passed failed) " CLOS tests passed"))
|
|
(begin
|
|
(for-each (fn (f) (print f)) failures)
|
|
(print
|
|
(str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed")))) |