2326 lines
107 KiB
Scheme
2326 lines
107 KiB
Scheme
;;; Heavily hacked by Eli Barzilay: Maze is Life! (eli@barzilay.org)
|
|
|
|
;;> This module is the core object system. It is a heavily hacked version
|
|
;;> of the original Tiny-CLOS code from Xerox, but it has been fitted to
|
|
;;> MzScheme, optimized and extended. See the source file for a lot of
|
|
;;> details about how the CLOS magic is created.
|
|
;;>
|
|
;;> [There is one difference between Swindle and Tiny-CLOS: the meta object
|
|
;;> hierarchy is assumed to be using only single inheritance, or if there is
|
|
;;> multiple inheritance then the built in meta objects should come first to
|
|
;;> make the slots allocated in the same place. This should not be a
|
|
;;> problem in realistic situations.]
|
|
|
|
;;; Original copyright:
|
|
;;; ***************************************************************************
|
|
;;; Copyright (c) 1992 Xerox Corporation. All Rights Reserved.
|
|
;;;
|
|
;;; Use, reproduction, and preparation of derivative works are permitted. Any
|
|
;;; copy of this software or of any derivative work must include the above
|
|
;;; copyright notice of Xerox Corporation, this paragraph and the one after it.
|
|
;;; Any distribution of this software or derivative works must comply with all
|
|
;;; applicable United States export control laws.
|
|
;;; This software is made available AS IS, and XEROX CORPORATION DISCLAIMS ALL
|
|
;;; WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
|
|
;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND
|
|
;;; NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY LIABILITY FOR
|
|
;;; DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS EXPRESSLY DISCLAIMED,
|
|
;;; WHETHER ARISING IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT
|
|
;;; LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED OF THE POSSIBILITY OF SUCH
|
|
;;; DAMAGES.
|
|
;;; ***************************************************************************
|
|
|
|
#lang s-exp swindle/base
|
|
|
|
;;; A very simple CLOS-like language, embedded in Scheme, with a simple MOP.
|
|
;;; The features of the default base language are:
|
|
;;; * Classes, with instance slots, but no slot options.
|
|
;;; * Multiple-inheritance.
|
|
;;; * Generic functions with multi-methods and class specializers only.
|
|
;;; * Primary methods and call-next-method; no other method combination.
|
|
;;; * Uses Scheme's lexical scoping facilities as the class and generic
|
|
;;; function naming mechanism. Another way of saying this is that class,
|
|
;;; generic function and methods are first-class (meta)objects.
|
|
;;;
|
|
;;; While the MOP is simple, it is essentially equal in power to both MOPs in
|
|
;;; AMOP. This implementation is not at all optimized, but the MOP is designed
|
|
;;; so that it can be optimized. In fact, this MOP allows better optimization
|
|
;;; of slot access extenstions than those in AMOP.
|
|
;;;
|
|
;;; In addition to calling a generic, the entry points to the default base
|
|
;;; language are:
|
|
;;;
|
|
;;; (MAKE-CLASS list-of-superclasses list-of-slot-names)
|
|
;;; (MAKE-GENERIC-FUNCTION)
|
|
;;; (MAKE-METHOD list-of-specializers procedure)
|
|
;;; (ADD-METHOD generic method)
|
|
;;;
|
|
;;; (MAKE class . initargs)
|
|
;;; (INITIALIZE instance initargs) ; Add methods to this, dont call directly.
|
|
;;;
|
|
;;; (SLOT-REF object slot-name)
|
|
;;; (SLOT-SET! object slot-name new-value)
|
|
;;; (SLOT-BOUND? object slot-name)
|
|
;;;
|
|
;;; So, for example, one might do:
|
|
;;; (define <position> (make-class (list <object>) (list 'x 'y)))
|
|
;;; (add-method initialize
|
|
;;; (make-method (list <position>)
|
|
;;; (lambda (call-next-method pos initargs)
|
|
;;; (for-each (lambda (initarg-name slot-name)
|
|
;;; (slot-set! pos slot-name
|
|
;;; (getarg initargs initarg-name 0)))
|
|
;;; '(x y)
|
|
;;; '(x y)))))
|
|
;;; (set! p1 (make <position> 'x 1 'y 3))
|
|
;;;
|
|
;;; NOTE! Do not use EQUAL? to compare objects! Use EQ? or some hand written
|
|
;;; procedure. Objects have a pointer to their class, and classes are
|
|
;;; circular structures, and...
|
|
;;;
|
|
;;; The introspective part of the MOP looks like the following. Note that
|
|
;;; these are ordinary procedures, not generics.
|
|
;;; * CLASS-OF
|
|
;;; INSTANCE-OF?
|
|
;;; SUBCLASS?
|
|
;;; * CLASS-DIRECT-SUPERS
|
|
;;; CLASS-DIRECT-SLOTS
|
|
;;; CLASS-CPL
|
|
;;; CLASS-SLOTS
|
|
;;; CLASS-NAME
|
|
;;; * GENERIC-METHODS
|
|
;;; GENERIC-ARITY
|
|
;;; GENERIC-NAME
|
|
;;; GENERIC-COMBINATION
|
|
;;; * METHOD-SPECIALIZERS
|
|
;;; METHOD-PROCEDURE
|
|
;;; METHOD-NAME
|
|
;;;
|
|
;;; The intercessory protocol looks like (generics in uppercase):
|
|
;;; ELI: All of these are generic functions now!
|
|
;;; MAKE
|
|
;;; ALLOCATE-INSTANCE
|
|
;;; INITIALIZE (really a base-level generic)
|
|
;;; class initialization
|
|
;;; COMPUTE-CPL
|
|
;;; COMPUTE-SLOTS
|
|
;;; COMPUTE-GETTER-AND-SETTER
|
|
;;; method initialization
|
|
;;; COMPUTE-APPLY-METHOD
|
|
;;; ADD-METHOD (Notice this is not a generic!) [eli: yes!]
|
|
;;; COMPUTE-APPLY-GENERIC
|
|
;;; COMPUTE-METHODS
|
|
;;; COMPUTE-METHOD-MORE-SPECIFIC?
|
|
;;; COMPUTE-APPLY-METHODS
|
|
|
|
;;; OK, now let's get going. But, as usual, before we can do anything
|
|
;;; interesting, we have to muck around for a bit first. First, we need to
|
|
;;; load the support library. [-- replaced with a module.]
|
|
(require swindle/misc)
|
|
|
|
;; This is a convenient function for raising exceptions
|
|
(define (raise* exn-maker fmt . args)
|
|
(let ([sym (and (symbol? fmt)
|
|
(begin0 fmt
|
|
(when (null? args) (error 'raise* "got too few arguments"))
|
|
(set! fmt (car args)) (set! args (cdr args))))]
|
|
[fmt-num (- (length args) (procedure-arity exn-maker) -2)])
|
|
(when (< fmt-num 0)
|
|
(error 'raise* "got too few arguments"))
|
|
(let loop ([fmt-args '()] [args args] [a fmt-num])
|
|
(if (zero? a)
|
|
(raise (exn-maker
|
|
(if sym
|
|
(apply format (concat "~s: " fmt) sym (reverse fmt-args))
|
|
(apply format fmt (reverse fmt-args)))
|
|
(current-continuation-marks) . args))
|
|
(loop (cons (car args) fmt-args) (cdr args) (sub1 a))))))
|
|
|
|
;; A simple topological sort.
|
|
;; It's in this file so that both TinyClos and Objects can use it.
|
|
;; This is a fairly modified version of code I originally got from Anurag
|
|
;; Mendhekar <anurag@moose.cs.indiana.edu>.
|
|
(define (compute-std-cpl c get-direct-supers)
|
|
(top-sort (build-transitive-closure get-direct-supers c)
|
|
(build-constraints get-direct-supers c)
|
|
(std-tie-breaker get-direct-supers)))
|
|
(define (top-sort elements constraints tie-breaker)
|
|
(let loop ([elements elements] [constraints constraints] [result '()])
|
|
(if (null? elements)
|
|
result
|
|
(let ([can-go-in-now
|
|
(filter (lambda (x)
|
|
(every (lambda (constraint)
|
|
(or (not (eq? (cadr constraint) x))
|
|
(memq (car constraint) result)))
|
|
constraints))
|
|
elements)])
|
|
(if (null? can-go-in-now)
|
|
(error 'top-sort "invalid constraints")
|
|
(let ([choice (if (null? (cdr can-go-in-now))
|
|
(car can-go-in-now)
|
|
(tie-breaker result can-go-in-now))])
|
|
(loop (filter (lambda (x) (not (eq? x choice))) elements)
|
|
constraints (append result (list choice)))))))))
|
|
(define (std-tie-breaker get-supers)
|
|
(lambda (partial-cpl min-elts)
|
|
(let loop ([pcpl (reverse partial-cpl)])
|
|
(let* ([current-elt (car pcpl)]
|
|
[ds-of-ce (get-supers current-elt)]
|
|
[common (filter (lambda (x) (memq x ds-of-ce)) min-elts)])
|
|
(if (null? common)
|
|
(if (null? (cdr pcpl))
|
|
(error 'std-tie-breaker "nothing valid") (loop (cdr pcpl)))
|
|
(car common))))))
|
|
(define (build-transitive-closure get-follow-ons x)
|
|
(let track ([result '()] [pending (list x)])
|
|
(if (null? pending)
|
|
result
|
|
(let ([next (car pending)])
|
|
(if (memq next result)
|
|
(track result (cdr pending))
|
|
(track (cons next result)
|
|
(append (get-follow-ons next) (cdr pending))))))))
|
|
(define (build-constraints get-follow-ons x)
|
|
(let loop ([elements (build-transitive-closure get-follow-ons x)]
|
|
[this-one '()]
|
|
[result '()])
|
|
(if (or (null? this-one) (null? (cdr this-one)))
|
|
(if (null? elements)
|
|
result
|
|
(loop (cdr elements)
|
|
(cons (car elements) (get-follow-ons (car elements)))
|
|
result))
|
|
(loop elements
|
|
(cdr this-one)
|
|
(cons (list (car this-one) (cadr this-one)) result)))))
|
|
|
|
;;; Then, we need to build what, in a more real implementation, would be the
|
|
;;; interface to the memory subsystem: instances and entities. The former are
|
|
;;; used for instances of instances of <class>; the latter are used for
|
|
;;; instances of instances of <entity-class>. In this MOP, none of this is
|
|
;;; visible to base- or MOP-level programmers.
|
|
;;; A few things to note, that have influenced the way all this is done:
|
|
;;; - R4RS doesn't provide a mechanism for specializing the
|
|
;;; behavior of the printer for certain objects.
|
|
;;; - Some Scheme implementations bomb when printing circular structures --
|
|
;;; that is, arrays and/or lists that somehow point back to themselves.
|
|
;;; So, the natural implementation of instances -- vectors whose first field
|
|
;;; point to the class -- is straight on out. Instead, we use a procedure to
|
|
;;; `encapsulate' that natural representation.
|
|
;;; Having gone that far, it makes things simpler to unify the way normal
|
|
;;; instances and entities are handled, at least in the lower levels of the
|
|
;;; system. Don't get faked out by this -- the user shouldn't think of normal
|
|
;;; instances as being procedures, they aren't. (At least not in this
|
|
;;; language.) If you are using this to teach, you probably want to hide the
|
|
;;; implementation of instances and entities from people.
|
|
|
|
;;>> ???
|
|
;;> This is MzScheme's `unspecified' value which is used as the default
|
|
;;> value for unbound slots. It is provided so you can check if a slot is
|
|
;;> unbound.
|
|
(define* ??? (letrec ([x x]) x)) ; this is MzScheme's #<undefined> value
|
|
(define unspecified-initializer (lambda args ???))
|
|
(define false-func (lambda args #f))
|
|
|
|
;; Basic allocation follows, all was in a single let, but this is not needed
|
|
;; with MzScheme's modules. Also modified to use simple structs for
|
|
;; everything, including entities since PLT has applicable struct objects.
|
|
|
|
(define-values (struct:instance make-instance instance? inst-ref inst-set!)
|
|
;; slots: applicable, class, function, slots-vector
|
|
(make-struct-type 'swindleobj #f 3 0 #f '() (current-inspector)
|
|
(lambda (o . args) (apply (instance-proc o) args))))
|
|
(defsubst (instance-class x) (inst-ref x 0))
|
|
(defsubst (instance-proc x) (inst-ref x 1))
|
|
(defsubst (instance-slots x) (inst-ref x 2))
|
|
(defsubst (set-instance-class! x c) (inst-set! x 0 c))
|
|
(defsubst (set-instance-proc! x p) (inst-set! x 1 p))
|
|
(defsubst (set-instance-slots! x s) (inst-set! x 2 s))
|
|
|
|
(defsubst (%instance-ref o f) (vector-ref (instance-slots o) f))
|
|
(defsubst (%instance-set! o f n) (vector-set! (instance-slots o) f n))
|
|
|
|
(define (%allocate-instance class nfields)
|
|
(make-instance class
|
|
(lambda args
|
|
(error 'instance
|
|
"an instance isn't a procedure -- can't apply it"))
|
|
(make-vector nfields ???)))
|
|
|
|
(define (%allocate-entity class nfields)
|
|
(letrec ([o (make-instance
|
|
class
|
|
(lambda args
|
|
(error 'entity
|
|
"tried to call an entity before its proc is set"))
|
|
(make-vector nfields ???))])
|
|
o))
|
|
|
|
;; This is used only once as part of bootstrapping the braid.
|
|
(define (set-instance-class-to-self! class)
|
|
(set-instance-class! class class))
|
|
|
|
;;>>...
|
|
;;> *** Low level functionality
|
|
;;> (These functions should be used with caution, since they make shooting
|
|
;;> legs in exotic ways extremely easy.)
|
|
|
|
;;>> (change-class! object new-class initargs ...)
|
|
;;> This operation changes the class of the given `object' to the given
|
|
;;> `new-class'. The way this is done is by creating a fresh instance of
|
|
;;> `new-class', then copying all slot values from `object' to the new
|
|
;;> instance for all shared slot names. Finally, the new instance's set
|
|
;;> of slots is used for the original object with the new class, so it
|
|
;;> preserves its identity.
|
|
(define* (change-class! obj new-class . initargs)
|
|
(let ([new (make new-class . initargs)]
|
|
[new-slots (%class-slots new-class)])
|
|
(dolist [slot (%class-slots (class-of obj))]
|
|
(when (and (not (eq? :class (getarg (cdr slot) :allocation :instance)))
|
|
(assq (car slot) new-slots))
|
|
(slot-set! new (car slot) (slot-ref obj (car slot)))))
|
|
(set-instance-slots! obj (instance-slots new))
|
|
(set-instance-class! obj new-class)))
|
|
|
|
;; This might be cute for some ugly hacks but not needed for now.
|
|
;; Copies the contents of source to target, making it an "alias" object. This
|
|
;; is no re-provided by clos.ss, but maybe it will in the future...
|
|
;; (define* (copy-object-contents! target source)
|
|
;; (set-instance-class! target (instance-class source))
|
|
;; (set-instance-proc! target (instance-proc source))
|
|
;; (set-instance-slots! target (instance-slots source)))
|
|
|
|
;;>> (set-instance-proc! object proc)
|
|
;;> This function sets the procedure of an entity object. It is useful
|
|
;;> only for making new entity classes.
|
|
(provide set-instance-proc!) ; dangerous!
|
|
|
|
;; Basic allocation ends here.
|
|
|
|
;;>>...
|
|
;;> *** Basic functionality
|
|
|
|
;;>> (instance? x)
|
|
;;>> (object? x)
|
|
;;> These two are synonyms: a predicate that returns #t for objects that
|
|
;;> are allocated and managed by Swindle.
|
|
(provide instance?)
|
|
(define* object? instance?)
|
|
|
|
;;>> (class-of x)
|
|
;;> Return the class object of `x'. This will either be a Swindle class
|
|
;;> for objects, or a built-in class for other Scheme values.
|
|
;;; %allocate-instance, %allocate-entity, %instance-ref, %instance-set! and
|
|
;;; class-of are the normal interface, from the rest of the code, to the
|
|
;;; low-level memory system. One thing to take note of is that the protocol
|
|
;;; does not allow the user to add low-level instance representations. I have
|
|
;;; never seen a way to make that work.
|
|
;;; Note that this implementation of class-of assumes the name of a the
|
|
;;; primitive classes that are set up later.
|
|
(define* (class-of x)
|
|
;; This is an early version that will be modified when built-in types are
|
|
;; introduced later.
|
|
(if (instance? x) (instance-class x) <top>))
|
|
|
|
;;; Now we can get down to business. First, we initialize the braid.
|
|
;;; For Bootstrapping, we define an early version of MAKE. It will be changed
|
|
;;; to the real version later on.
|
|
(define* (make class . initargs)
|
|
(cond [(or (eq? class <class>) (eq? class <entity-class>))
|
|
(let* ([new (%allocate-instance class
|
|
(length the-slots-of-a-class))]
|
|
[dsupers (getarg initargs :direct-supers '())]
|
|
[dslots (map list (getarg initargs :direct-slots '()))]
|
|
[cpl (let loop ([sups dsupers] [so-far (list new)])
|
|
(if (null? sups)
|
|
(reverse so-far)
|
|
(loop (append (cdr sups)
|
|
(%class-direct-supers (car sups)))
|
|
(if (memq (car sups) so-far)
|
|
so-far
|
|
(cons (car sups) so-far)))))]
|
|
[slots
|
|
(apply append dslots (map %class-direct-slots (cdr cpl)))]
|
|
[nfields 0]
|
|
[name (or (getarg initargs :name) '-anonymous-)]
|
|
[field-initializers '()]
|
|
;; this is a temporary allocator version, kept as the original
|
|
;; one in tiny-clos. the permanent version below is modified.
|
|
[allocator
|
|
(lambda (init)
|
|
(let ([f nfields])
|
|
(set! nfields (+ nfields 1))
|
|
(set! field-initializers (cons init field-initializers))
|
|
(mcons (lambda (o) (%instance-ref o f))
|
|
(lambda (o n) (%instance-set! o f n)))))]
|
|
[getters-n-setters
|
|
(map (lambda (s)
|
|
(cons (car s) (allocator unspecified-initializer)))
|
|
slots)])
|
|
(%set-class-direct-supers! new dsupers)
|
|
(%set-class-direct-slots! new dslots)
|
|
(%set-class-cpl! new cpl)
|
|
(%set-class-slots! new slots)
|
|
(%set-class-nfields! new nfields)
|
|
(%set-class-field-initializers! new (reverse field-initializers))
|
|
(%set-class-getters-n-setters! new getters-n-setters)
|
|
(%set-class-name! new name)
|
|
(%set-class-initializers! new '()) ; no class inits now
|
|
(%set-class-valid-initargs! new #f) ; no initargs now
|
|
new)]
|
|
[(eq? class <generic>)
|
|
(let ([new (%allocate-entity class (length (%class-slots class)))]
|
|
[arity (getarg initargs :arity #f)]
|
|
[name (or (getarg initargs :name) '-anonymous-)])
|
|
(%set-generic-methods! new '())
|
|
(%set-generic-arity! new arity)
|
|
(%set-generic-name! new name)
|
|
(%set-generic-combination! new #f)
|
|
new)]
|
|
[(eq? class <method>)
|
|
(let ([new (%allocate-entity class (length (%class-slots class)))]
|
|
[name (or (getarg initargs :name) '-anonymous-)])
|
|
(%set-method-specializers! new (getarg initargs :specializers))
|
|
(%set-method-procedure! new (getarg initargs :procedure))
|
|
(%set-method-qualifier! new (or (getarg initargs :qualifier)
|
|
:primary))
|
|
(%set-method-name! new name)
|
|
(set-instance-proc! new (method:compute-apply-method #f new))
|
|
new)]))
|
|
|
|
;;; These are the real versions of slot-ref and slot-set!. Because of the way
|
|
;;; the new slot access protocol works, with no generic call in line, they can
|
|
;;; be defined up front like this. Cool eh?
|
|
|
|
;;>> (slot-ref obj slot)
|
|
;;> Pull out the contents of the slot named `slot' in the given `obj'.
|
|
;;> Note that slot names are usually symbols, but can be other values as
|
|
;;> well.
|
|
(define* (slot-ref object slot-name)
|
|
((lookup-slot-info (class-of object) slot-name mcar) object))
|
|
(defsubst (%slot-ref object slot-name)
|
|
((lookup-slot-info (class-of object) slot-name mcar) object))
|
|
|
|
;;>> (slot-set! obj slot new)
|
|
;;> Change the contents of the `slot' slot of `obj' to the given `new'
|
|
;;> value.
|
|
(define* (slot-set! object slot-name new-value)
|
|
((lookup-slot-info (class-of object) slot-name mcdr) object new-value))
|
|
(defsubst (%slot-set! object slot-name new-value)
|
|
((lookup-slot-info (class-of object) slot-name mcdr) object new-value))
|
|
;;>> (set-slot-ref! obj slot new)
|
|
;;> An alias for `slot-set!', to enable using `setf!' on it.
|
|
(define* set-slot-ref! slot-set!)
|
|
|
|
;; This is a utility that is used to make locked slots
|
|
(define (make-setter-locked! g+s key error)
|
|
(let ([setter (mcdr g+s)])
|
|
(set-mcdr! g+s
|
|
(lambda (o n)
|
|
(cond [(and (pair? n) (eq? key (car n)) (not (eq? key #t)))
|
|
(setter o (cdr n))]
|
|
[(eq? ??? ((mcar g+s) o)) (setter o n)]
|
|
[else (error)])))))
|
|
|
|
;;>> (slot-bound? object slot)
|
|
;;> Checks if the given `slot' is bound in `object'. See also `???'
|
|
;;> above.
|
|
(define* (slot-bound? object slot-name)
|
|
(not (eq? ??? (%slot-ref object slot-name))))
|
|
|
|
(define (lookup-slot-info class slot-name selector)
|
|
(selector (cdr (or (assq slot-name
|
|
;; no need to ground slot-ref any more! -- see below
|
|
;; (if (eq? class <class>)
|
|
;; ;;* This grounds out the slot-ref tower
|
|
;; getters-n-setters-for-class
|
|
;; (%class-getters-n-setters class))
|
|
(%class-getters-n-setters class))
|
|
(raise* make-exn:fail:contract
|
|
"slot-ref: no slot `~e' in ~e" slot-name class)))))
|
|
|
|
;;; These are for optimizations - works only for single inheritance!
|
|
(define (%slot-getter class slot-name)
|
|
(lookup-slot-info class slot-name mcar))
|
|
(define (%slot-setter class slot-name)
|
|
(lookup-slot-info class slot-name mcdr))
|
|
|
|
;;>>... Singleton and Struct Specifiers
|
|
|
|
;;; Singleton class. A hash-table is used so it is still possible to compare
|
|
;;; classes with eq?.
|
|
(define singleton-classes (make-hash-table 'weak))
|
|
;;>> (singleton x)
|
|
;;> Returns a singleton specification. Singletons can be used as type
|
|
;;> specifications that have only one element in them so you can
|
|
;;> specialize methods on unique objects.
|
|
;;>
|
|
;;> This is actually just a list with the symbol `singleton' in its head
|
|
;;> and the value, but this function uses a hash table to always return
|
|
;;> the same object for the same value. For example:
|
|
;;> => (singleton 1)
|
|
;;> (singleton 1)
|
|
;;> => (eq? (singleton 1) (singleton 1))
|
|
;;> #t
|
|
;;> but if the input objects are not `eq?', the result isn't either:
|
|
;;> => (eq? (singleton "1") (singleton "1"))
|
|
;;> #f
|
|
;;> Only `eq?' is used to compare objects.
|
|
(define* (singleton x)
|
|
(or (hash-table-get singleton-classes x false-func)
|
|
(let ([c (list 'singleton x)])
|
|
(hash-table-put! singleton-classes x c)
|
|
c)))
|
|
;;>> (singleton? x)
|
|
;;> Determines if something is a singleton specification (which is any
|
|
;;> list with a head containing the symbol `singleton').
|
|
(define* (singleton? x)
|
|
(and (pair? x) (eq? (car x) 'singleton)))
|
|
(defsubst (%singleton? x)
|
|
(and (pair? x) (eq? (car x) 'singleton)))
|
|
;;>> (singleton-value x)
|
|
;;> Pulls out the value of a singleton specification.
|
|
(define* singleton-value cadr)
|
|
|
|
;;>>...
|
|
;;> Also note that MzScheme struct types are converted to appropriate
|
|
;;> Swindle classes. This way, it is possible to have Swindle generic
|
|
;;> functions that work with struct type specializers.
|
|
|
|
;;>> (struct-type->class struct-type)
|
|
;;> This function is used to convert a struct-type to a corresponding
|
|
;;> Swindle subclass of `<struct>'. See the MzScheme manual for details
|
|
;;> on struct types.
|
|
(define struct-to-class-table (make-hash-table))
|
|
(define* (struct-type->class stype)
|
|
(hash-table-get
|
|
struct-to-class-table stype
|
|
(thunk
|
|
(let-values ([(name init-field-k auto-field-k accessor mutator
|
|
immutable-k-list super skipped?)
|
|
(struct-type-info stype)])
|
|
(let* ([supers (list (cond [super (struct-type->class super)]
|
|
[skipped? <opaque-struct>]
|
|
[else <struct>]))]
|
|
[proc? (procedure-struct-type? stype)]
|
|
[supers (if proc? (cons <primitive-procedure> supers) supers)]
|
|
[this (parameterize ([*default-object-class* #f])
|
|
(make (if proc? <procedure-class> <primitive-class>)
|
|
:name name :direct-supers supers))])
|
|
(hash-table-put! struct-to-class-table stype this)
|
|
this)))))
|
|
|
|
;;>>...
|
|
;;> *** Common accessors
|
|
|
|
;;; Given that the early version of MAKE is allowed to call accessors on class
|
|
;;; metaobjects, the definitions for them come here, before the actual class
|
|
;;; definitions, which are coming up right afterwards.
|
|
;;>> (class-direct-slots class)
|
|
;;>> (class-direct-supers class)
|
|
;;>> (class-slots class)
|
|
;;>> (class-cpl class)
|
|
;;>> (class-name class)
|
|
;;>> (class-initializers class)
|
|
;;> Accessors for class objects (look better than using `slot-ref').
|
|
(define* (class-direct-slots c) (%slot-ref c 'direct-slots))
|
|
(define* (class-direct-supers c) (%slot-ref c 'direct-supers))
|
|
(define* (class-slots c) (%slot-ref c 'slots))
|
|
(define (class-nfields c) (%slot-ref c 'nfields))
|
|
(define (class-field-initializers c) (%slot-ref c 'field-initializers))
|
|
(define (class-getters-n-setters c) (%slot-ref c 'getters-n-setters))
|
|
(define* (class-cpl c) (%slot-ref c 'cpl))
|
|
(define* (class-name c) (%slot-ref c 'name))
|
|
(define* (class-initializers c) (%slot-ref c 'initializers))
|
|
(define (class-valid-initargs c) (%slot-ref c 'valid-initargs))
|
|
;;>> (generic-methods generic)
|
|
;;>> (generic-arity generic)
|
|
;;>> (generic-name generic)
|
|
;;>> (generic-combination generic)
|
|
;;> Accessors for generic function objects.
|
|
(define* (generic-methods g) (%slot-ref g 'methods))
|
|
(define* (generic-arity g) (%slot-ref g 'arity))
|
|
(define* (generic-name g) (%slot-ref g 'name))
|
|
(define* (generic-combination g) (%slot-ref g 'combination))
|
|
;;>> (method-specializers method)
|
|
;;>> (method-procedure method)
|
|
;;>> (method-qualifier method)
|
|
;;>> (method-name method)
|
|
;;>> (method-arity method)
|
|
;;> Accessors for method objects. `method-arity' is not really an
|
|
;;> accessor, it is deduced from the arity of the procedure (minus one for
|
|
;;> the `call-next-method' argument).
|
|
(define* (method-specializers m) (%slot-ref m 'specializers))
|
|
(define* (method-procedure m) (%slot-ref m 'procedure))
|
|
(define* (method-qualifier m) (%slot-ref m 'qualifier))
|
|
(define* (method-name m) (%slot-ref m 'name))
|
|
(define* (method-arity m)
|
|
(let ([a (procedure-arity (%method-procedure m))])
|
|
(cond [(integer? a) (sub1 a)]
|
|
[(arity-at-least? a)
|
|
(make-arity-at-least (sub1 (arity-at-least-value a)))]
|
|
[else (error 'method-arity "the procedure in ~e has bad arity ~e"
|
|
m a)])))
|
|
|
|
;;; These versions will be optimized later.
|
|
(define %class-direct-slots class-direct-slots)
|
|
(define %class-direct-supers class-direct-supers)
|
|
(define %class-slots class-slots)
|
|
(define %class-nfields class-nfields)
|
|
(define %class-field-initializers class-field-initializers)
|
|
(define %class-getters-n-setters class-getters-n-setters)
|
|
(define %class-cpl class-cpl)
|
|
(define %class-name class-name)
|
|
(define %class-initializers class-initializers)
|
|
(define %class-valid-initargs class-valid-initargs)
|
|
(define %generic-methods generic-methods)
|
|
(define %generic-arity generic-arity)
|
|
(define %generic-name generic-name)
|
|
(define %generic-combination generic-combination)
|
|
(define %method-specializers method-specializers)
|
|
(define %method-procedure method-procedure)
|
|
(define %method-qualifier method-qualifier)
|
|
(define %method-name method-name)
|
|
|
|
(define (%set-class-direct-slots! c x) (%slot-set! c 'direct-slots x))
|
|
(define (%set-class-direct-supers! c x) (%slot-set! c 'direct-supers x))
|
|
(define (%set-class-slots! c x) (%slot-set! c 'slots x))
|
|
(define (%set-class-nfields! c x) (%slot-set! c 'nfields x))
|
|
(define (%set-class-field-initializers! c x)
|
|
(%slot-set! c 'field-initializers x))
|
|
(define (%set-class-getters-n-setters! c x)
|
|
(%slot-set! c 'getters-n-setters x))
|
|
(define (%set-class-cpl! c x) (%slot-set! c 'cpl x))
|
|
(define (%set-class-name! c x) (%slot-set! c 'name x))
|
|
(define (%set-class-initializers! c x) (%slot-set! c 'initializers x))
|
|
(define (%set-class-valid-initargs! c x) (%slot-set! c 'valid-initargs x))
|
|
(define (%set-generic-methods! g x) (%slot-set! g 'methods x))
|
|
(define (%set-generic-arity! g x) (%slot-set! g 'arity x))
|
|
(define (%set-generic-name! g x) (%slot-set! g 'name x))
|
|
(define (%set-generic-combination! g x) (%slot-set! g 'combination x))
|
|
(define (%set-method-specializers! m x) (%slot-set! m 'specializers x))
|
|
(define (%set-method-procedure! m x) (%slot-set! m 'procedure x))
|
|
(define (%set-method-qualifier! m x) (%slot-set! m 'qualifier x))
|
|
(define (%set-method-name! m x) (%slot-set! m 'name x))
|
|
|
|
;;; These are used to access the two slots that optimize generic invocations.
|
|
(define (%generic-app-cache g ) (%slot-ref g 'app-cache))
|
|
(define (%generic-singletons-list g ) (%slot-ref g 'singletons-list))
|
|
(define (%set-generic-app-cache! g x) (%slot-set! g 'app-cache x))
|
|
(define (%set-generic-singletons-list! g x) (%slot-set! g 'singletons-list x))
|
|
|
|
;;; The next 7 clusters define the 6 initial classes. It takes 7 to 6 because
|
|
;;; the first and fourth both contribute to <class>.
|
|
|
|
(define the-slots-of-a-class
|
|
'(direct-supers ; (class ...)
|
|
direct-slots ; ((name . options) ...)
|
|
cpl ; (class ...)
|
|
slots ; ((name . options) ...)
|
|
nfields ; an integer
|
|
field-initializers ; (proc ...)
|
|
getters-n-setters ; ((slot-name getter setter) ...)
|
|
name ; a symbol
|
|
initializers ; (proc ...)
|
|
valid-initargs)) ; (initarg ...) or #f
|
|
(define getters-n-setters-for-class ; see lookup-slot-info
|
|
(map (lambda (s)
|
|
(let ([f (position-of s the-slots-of-a-class)])
|
|
(cons s (mcons (lambda (o) (%instance-ref o f))
|
|
(lambda (o n) (%instance-set! o f n))))))
|
|
the-slots-of-a-class))
|
|
|
|
;;>>...
|
|
;;> *** Basic classes
|
|
|
|
;;>> <class>
|
|
;;> This is the "mother of all classes": every Swindle class is an
|
|
;;> instance of `<class>'.
|
|
;;> Slots:
|
|
;;> * direct-supers: direct superclasses
|
|
;;> * direct-slots: direct slots, each a list of a name and options
|
|
;;> * cpl: class precedence list (classes list this to <top>)
|
|
;;> * slots: all slots (like direct slots)
|
|
;;> * nfields: number of fields
|
|
;;> * field-initializers: a list of functions to initialize slots
|
|
;;> * getters-n-setters: an alist of slot-names, getters, and setters
|
|
;;> * name: class name (usually the defined identifier)
|
|
;;> * initializers: procedure list that perform additional initializing
|
|
;;> See the `clos' documentation for available class and slot keyword
|
|
;;> arguments and their effect.
|
|
(define* <class> (%allocate-instance #f (length the-slots-of-a-class)))
|
|
(set-instance-class-to-self! <class>)
|
|
|
|
;; In the original tiny-clos, this block used to just set the getters-n-setters
|
|
;; slot of a class to '() since it wasn't used anyway. In Swindle the MOP
|
|
;; accessors are all optimized to directly get the vector element because the
|
|
;; meta hierarchy is assumed to be single-inheritance only (allocation of more
|
|
;; slots always come after the built in ones), so what I do here is set the
|
|
;; slot value properly, and since `%class-getters-n-setters' accesses the
|
|
;; vector directly it doesn't go through slot-ref, which means that the
|
|
;; slot-ref definition above is fine. So,
|
|
;; (%set-class-getters-n-setters! <class> getters-n-setters-for-class)
|
|
;; translates into this:
|
|
((mcdr (cdr (assq 'getters-n-setters getters-n-setters-for-class)))
|
|
<class> getters-n-setters-for-class)
|
|
;; and now the direct `%class-getters-n-setters' version:
|
|
(set! %class-getters-n-setters
|
|
;; and (lookup-slot-info <class> 'getters-n-setters mcar) translates to:
|
|
(mcar (cdr (assq 'getters-n-setters getters-n-setters-for-class))))
|
|
|
|
;;>> <top>
|
|
;;> This is the "mother of all values": every value is an instance of
|
|
;;> `<top>' (including standard Scheme values).
|
|
(define* <top> (make <class> :direct-supers '()
|
|
:direct-slots '()
|
|
:name '<top>))
|
|
|
|
;;>> <object>
|
|
;;> This is the "mother of all objects": every Swindle object is an
|
|
;;> instance of `<object>'.
|
|
(define* <object> (make <class> :direct-supers (list <top>)
|
|
:direct-slots '()
|
|
:name '<object>))
|
|
|
|
;;; This cluster, together with the first cluster above that defines <class>
|
|
;;; and sets its class, have the effect of:
|
|
;;; (define <class>
|
|
;;; (make <class> :direct-supers (list <object>)
|
|
;;; :direct-slots '(direct-supers ...)
|
|
;;; :name '<class>))
|
|
(%set-class-direct-supers! <class> (list <object>))
|
|
(%set-class-cpl! <class> (list <class> <object> <top>))
|
|
(%set-class-direct-slots! <class> (map list the-slots-of-a-class))
|
|
(%set-class-slots! <class> (map list the-slots-of-a-class))
|
|
(%set-class-nfields! <class> (length the-slots-of-a-class))
|
|
(%set-class-field-initializers! <class> (map (lambda (s)
|
|
unspecified-initializer)
|
|
the-slots-of-a-class))
|
|
(%set-class-name! <class> '<class>)
|
|
(%set-class-initializers! <class> '())
|
|
(%set-class-valid-initargs! <class> #f)
|
|
|
|
;;>> <procedure-class>
|
|
;;> The class of all procedures classes, both standard Scheme procedures
|
|
;;> classes and entity (Swindle procedure objects) classes. (Note that
|
|
;;> this is a class of *classes*).
|
|
(define* <procedure-class>
|
|
(make <class> :direct-supers (list <class>)
|
|
:direct-slots '()
|
|
:name '<procedure-class>))
|
|
|
|
;;>> <entity-class>
|
|
;;> The class of entity classes -- generic functions and methods. An
|
|
;;> entity is a procedural Swindle object, something that you can apply as
|
|
;;> a function but it is still a Swindle object. Note that this is the
|
|
;;> class of entity *classes* not of entities themselves.
|
|
(define* <entity-class>
|
|
(make <class> :direct-supers (list <procedure-class>)
|
|
:direct-slots '()
|
|
:name '<entity-class>))
|
|
|
|
;;>> <function>
|
|
;;> The class of all applicable values: methods, generic functions, and
|
|
;;> standard closures.
|
|
(define* <function>
|
|
(make <class> :direct-supers (list <top>)
|
|
:direct-slots '()
|
|
:name '<function>))
|
|
|
|
;;; The two extra slots below (app-cache and singletons-list) are used to
|
|
;;; optimize generic invocations: app-cache holds an 'equal hash-table that
|
|
;;; maps a list of classes to the lambda expression that holds the method call
|
|
;;; (it used to be an l-hash-table, but 'equal is ok since we can't compare
|
|
;;; swindleobj instances recursively -- which is also why tool.ss needs to
|
|
;;; redefine the `render-value/format' method). The contents of this slot is
|
|
;;; reset whenever a method is added to the generic. Two problems make things
|
|
;;; a little more complicated. First, if add-method is used to modify any of
|
|
;;; the generic-invocation-generics then all of these caches should be flushed,
|
|
;;; this is achieved by setting *generic-app-cache-tag* to a new [list] object
|
|
;;; and the value of app-cache is a cons of that value and the actual hash
|
|
;;; table - if we see that the car is not eq? to the current tag, then we flush
|
|
;;; the cache. Second, singleton values might screw things up, so we hold in
|
|
;;; singletons-list a list that has the same length as all method specializer
|
|
;;; lists, each element contains a hash table with all singleton values that
|
|
;;; appear in that place matched to #t, then when we try to see if we have a
|
|
;;; cached function for a generic application, we scan the argument list
|
|
;;; against this list, and any value that has a singleton with that value at
|
|
;;; some method, is left in place for the app-cache lookup (it is used itself
|
|
;;; rather than its class). This whole thing is a bit complicated but leads to
|
|
;;; dramatic run-time improvement.
|
|
;;>> <generic>
|
|
;;> The class of generic functions: objects that contain method objects
|
|
;;> and calls the appropriate ones when applied.
|
|
;;> Slots:
|
|
;;> * methods: a list of <method> objects
|
|
;;> * arity: the generic arity (same for all of its methods)
|
|
;;> * name: generic name
|
|
;;> * combination: a method combination function or #f, see
|
|
;;> `make-generic-combination' below for details
|
|
(define* <generic>
|
|
(make <entity-class> :direct-supers (list <object> <function>)
|
|
:direct-slots '(methods arity name combination
|
|
app-cache singletons-list) ; see above
|
|
:name '<generic>))
|
|
|
|
;;>> <method>
|
|
;;> The class of methods: objects that are similar to Scheme closures,
|
|
;;> except that they have type specifiers attached. Note that in contrast
|
|
;;> to Tiny CLOS, methods are applicable objects in Swindle -- they check
|
|
;;> supplied argument types when applied.
|
|
;;> Slots:
|
|
;;> * specializers: a list of class (and singleton) specializers
|
|
;;> * procedure: the function (never call directly!)
|
|
;;> * qualifier: some qualifier tag, used when applying a generic
|
|
;;> * name: method name
|
|
(define* <method>
|
|
(make <entity-class> :direct-supers (list <object> <function>)
|
|
:direct-slots '(specializers procedure qualifier name)
|
|
:name '<method>))
|
|
;; Do this since compute-apply-method relies on them not changing, as well as a
|
|
;; zillion other places. A method should be very similar to a lambda.
|
|
(dolist [slot '(specializers procedure qualifier)]
|
|
(make-setter-locked! (lookup-slot-info <method> slot values) #t
|
|
(lambda ()
|
|
(raise* make-exn:fail:contract
|
|
"slot-set!: slot `~e' in <method> is locked" slot))))
|
|
|
|
;;>>...
|
|
;;> *** Convenience functions
|
|
;;>
|
|
;;> These are some convenience functions -- no new syntax, just function
|
|
;;> wrappers for `make' with some class and some slot values. See `clos'
|
|
;;> for a more sophisticated (and convenient) approach.
|
|
|
|
;;; These are the convenient syntax we expose to the base-level user.
|
|
;;>> (make-class direct-supers direct slots)
|
|
;;> Creates a class object -- an instance of <class>.
|
|
(define* (make-class direct-supers direct-slots)
|
|
(make <class> :direct-supers direct-supers
|
|
:direct-slots direct-slots))
|
|
;;>> (make-generic-function [name/arity])
|
|
;;> Creates a generic function object -- an instance of <generic>. The
|
|
;;> argument can specify name and/or arguments number.
|
|
(define* (make-generic-function . name/arity)
|
|
(cond
|
|
[(null? name/arity) (make <generic>)]
|
|
[(null? (cdr name/arity))
|
|
(let ([n/a (car name/arity)])
|
|
(if (integer? n/a)
|
|
(make <generic> :arity n/a) (make <generic> :name n/a)))]
|
|
[else (make <generic> :name (car name/arity) :arity (cadr name/arity))]))
|
|
;;>> (make-method specializers procedure)
|
|
;;> Creates a method object -- an instance of <method>, using the given
|
|
;;> specializer list and procedure. The procedure should have a first
|
|
;;> argument which is being used to access a `call-next-method' call.
|
|
(define* (make-method specializers procedure)
|
|
(make <method> :specializers specializers
|
|
:procedure procedure))
|
|
|
|
;;>> (no-next-method generic method [args ...])
|
|
;;>> (no-applicable-method generic [args ...])
|
|
;;> These two generic functions are equivalents to the ones in CL. The
|
|
;;> first one is applied on a generic and a method in case there was no
|
|
;;> next method and `call-next-method' was used. The second is used when
|
|
;;> a generic was called but no matching primary methods were found. The
|
|
;;> only difference is that in Swindle methods can be applied directly,
|
|
;;> and if `call-next-method' is used, then `no-next-method' gets `#f' for
|
|
;;> the generic argument.
|
|
(define* no-next-method (make-generic-function 'no-next-method))
|
|
(define* no-applicable-method (make-generic-function 'no-applicable-method))
|
|
|
|
;;; Add possibility of generic-independent method application - this is the
|
|
;;; instance-proc of methods, which is activated when you apply the object (in
|
|
;;; the original, methods could not be applied). This is defined using this
|
|
;;; name and arguments because it is later used directly by the generic
|
|
;;; function (cannot use the generic in the inital make since methods need to
|
|
;;; be created when the generics are constructed).
|
|
(define (method:compute-apply-method call-next-method method)
|
|
(let* ([specializers (%method-specializers method)]
|
|
[*no-next-method* ; see the *no-next-method* trick below
|
|
(lambda args (no-next-method #f method . args))]
|
|
[proc (%method-procedure method)]
|
|
[arity (method-arity method)]
|
|
[exact? (integer? arity)]
|
|
[required ((if exact? identity arity-at-least-value) arity)])
|
|
(when (and exact? (> (length specializers) required))
|
|
(error 'compute-apply-method
|
|
"got ~e specializers for ~s - too much for procedure arity ~a"
|
|
(length specializers) (%method-name method) required))
|
|
(lambda args
|
|
(cond [(if exact?
|
|
(not (= (length args) required)) (< (length args) required))
|
|
(raise* make-exn:fail:contract:arity
|
|
"method ~a: expects ~a~e argument~a, given ~e~a"
|
|
(%method-name method)
|
|
(if exact? "" "at least ") required
|
|
(if (= 1 required) "" "s") (length args)
|
|
(if (null? args) "" (format ": ~e" args)))]
|
|
[(not (every instance-of? args specializers))
|
|
(let loop ([args args] [specs specializers])
|
|
(if (instance-of? (car args) (car specs))
|
|
(loop (cdr args) (cdr specs))
|
|
(raise* make-exn:fail:contract
|
|
"method ~a: expects argument of type ~a; given ~e"
|
|
(%method-name method) (%class-name (car specs))
|
|
(car args))))]
|
|
[else (proc *no-next-method* . args)]))))
|
|
|
|
;;>>... Generics in the instance initialization protocol
|
|
;;> The following generic functions are used as part of the protocol of
|
|
;;> instantiating an instance, and some are used specifically to instantiate
|
|
;;> class objects.
|
|
|
|
;;; The instance structure protocol.
|
|
;;>> (allocate-instance class initargs)
|
|
;;> This generic function is called to allocate an instance of a class.
|
|
;;> It is applied on the class object, and is expected to return the new
|
|
;;> instance object of that class.
|
|
(define* allocate-instance
|
|
(make-generic-function 'allocate-instance))
|
|
;;>> (initialize instance initargs)
|
|
;;> This generic is called to initialize an instance. It is applied on
|
|
;;> the newly allocated object and the given initargs, and is not expected
|
|
;;> to return any meaningful value -- only do some side effects on the
|
|
;;> instance to initialize it. When overriding this for a some class, it
|
|
;;> is not a good idea to skip `call-next-method' since it is responsible
|
|
;;> for initializing slot values.
|
|
(define* initialize
|
|
(make-generic-function 'initialize))
|
|
;;>> (compute-getter-and-setter class slot allocator)
|
|
;;> This generic is used to get a getter and setter functions for a given
|
|
;;> slot. It is passed the class object, the slot information (a list of
|
|
;;> a slot name and options), and an allocator function. The allocator is
|
|
;;> a function that gets an initializer function and returns an index
|
|
;;> position for the new slot. The return value should be a list of two
|
|
;;> elements -- a getter and a setter functions.
|
|
(define* compute-getter-and-setter
|
|
(make-generic-function 'compute-getter-and-setter))
|
|
;;; The class initialization protocol.
|
|
;;>> (compute-cpl class)
|
|
;;> This generic is used to get the class-precedence-list for a class
|
|
;;> object. The standard <class> object uses the `compute-std-cpl' (see
|
|
;;> in the code) which flattens the class ancestors using a topological
|
|
;;> sort that resolve ambiguities left-to-right.
|
|
(define* compute-cpl
|
|
(make-generic-function 'compute-cpl))
|
|
;;>> (compute-slots class)
|
|
;;> This generic is used to compute all slot information for a given
|
|
;;> class, after its precedence list has been computed. The standard
|
|
;;> <class> collects information from all preceding classes.
|
|
(define* compute-slots
|
|
(make-generic-function 'compute-slots))
|
|
|
|
;;>> (compute-apply-method method)
|
|
;;> This generic is used to compute the procedure that will get executed
|
|
;;> when a method is applied directly.
|
|
(define* compute-apply-method
|
|
(make-generic-function 'compute-apply-method))
|
|
|
|
;;>>... Generics in the generic invocation protocol
|
|
;;> These generics are used for invocation of generic functions. See the
|
|
;;> code to see how this circularity is achieved.
|
|
|
|
;;>> ((compute-apply-generic generic) args ...)
|
|
;;> This generic is used to compute the object (a closure) that is
|
|
;;> actually applied to execute the generic call. The standard version
|
|
;;> uses `compute-method' and `compute-apply-methods' below, and caches
|
|
;;> the result.
|
|
(define* compute-apply-generic
|
|
(make-generic-function 'compute-apply-generic))
|
|
;;>> (compute-methods generic args)
|
|
;;> Computes the methods that should be applied for this generic
|
|
;;> invocation with args. The standard code filters applicable methods
|
|
;;> and sorts them according to their specificness. The return value is
|
|
;;> expected to depend only on the types of the arguments (and values if
|
|
;;> there are singleton specializers).
|
|
(define* compute-methods
|
|
(make-generic-function 'compute-methods))
|
|
;;>> ((compute-method-more-specific? generic) mthd1 mthd2 args)
|
|
;;> Get a generic and return a function that gets two methods and a list
|
|
;;> of arguments and decide which of the two methods is more specific.
|
|
;;> This decision should only be based on the argument types, or values
|
|
;;> only in case of singletons.
|
|
(define* compute-method-more-specific?
|
|
(make-generic-function 'compute-method-more-specific?))
|
|
;;>> ((compute-apply-methods generic methods) args ...)
|
|
;;> Gets a generic and returns a function that gets the given arguments
|
|
;;> for this call. This function which it returns is the combination of
|
|
;;> all given methods. The standard one arranges them by default using
|
|
;;> the `call-next-method' argument that methods have. Swindle extends
|
|
;;> this with qualified methods and applies `before', `after', and
|
|
;;> `around' methods in a similar way to CLOS: first the `around' methods
|
|
;;> are applied (and they usually call their `call-next-method' to
|
|
;;> continue but can return a different value), then all the `before'
|
|
;;> methods are applied (with no `call-next-method'), then all `primary'
|
|
;;> methods as usual (remembering the return value), and finally the
|
|
;;> `after' methods (similar to the `before', but in reverse specificness
|
|
;;> order). If the generic has a `combination' slot value, then it is a
|
|
;;> procedure that is used to combine the primary methods, but the
|
|
;;> auxiliary ones are still applied in the same way. This is unlike CLOS
|
|
;;> where the standard combinations run only `around' methods, and there
|
|
;;> is generally more control with method combinations, but in Swindle
|
|
;;> `compute-apply-methods' should be overridden for this. See
|
|
;;> `make-generic-combination' for details about method combinations.
|
|
(define* compute-apply-methods
|
|
(make-generic-function 'compute-apply-methods))
|
|
|
|
;;; The next thing to do is bootstrap generic functions.
|
|
|
|
(define generic-invocation-generics
|
|
(list compute-apply-generic compute-methods
|
|
compute-method-more-specific? compute-apply-methods))
|
|
|
|
;;; This is used to signal whenever all method caches are to be reset - so when
|
|
;;; a method is added to generic-invocation-generics, this is set to some value
|
|
;;; which is not eq? to the current one.
|
|
(define *generic-app-cache-tag* #t)
|
|
|
|
;;>> (add-method generic method)
|
|
;;> This generic function is called to add a method to a generic function
|
|
;;> object. This is an other change from the original Tiny CLOS where it
|
|
;;> was a normal function.
|
|
(define* (add-method generic method)
|
|
;; add singleton specializer value (if any) to the corresponding hash table
|
|
;; in singletons-list.
|
|
(define (add-to-singletons-list specs tables)
|
|
(cond
|
|
[(null? specs) null]
|
|
[(%singleton? (car specs))
|
|
(let ([ht (or (car tables)
|
|
(make-hash-table 'weak))])
|
|
(hash-table-put! ht (singleton-value (car specs)) #t)
|
|
(cons ht (add-to-singletons-list (cdr specs) (cdr tables))))]
|
|
[else
|
|
(cons (car tables)
|
|
(add-to-singletons-list (cdr specs) (cdr tables)))]))
|
|
(define (n-falses n)
|
|
(let loop ([n n] [r '()]) (if (zero? n) r (loop (sub1 n) (cons #f r)))))
|
|
(let ([tables (%generic-singletons-list generic)]
|
|
[specs (%method-specializers method)]
|
|
[qualifier (%method-qualifier method)])
|
|
;; make sure that tables always contain enough hash tables (or #f's)
|
|
(cond [(eq? tables ???)
|
|
(set! tables (n-falses (length specs)))]
|
|
[(< (length tables) (length specs))
|
|
(set! tables (append
|
|
tables
|
|
(n-falses (- (length specs) (length tables)))))])
|
|
(set! tables (add-to-singletons-list specs tables))
|
|
(%set-generic-singletons-list! generic tables)
|
|
(if (memq generic generic-invocation-generics)
|
|
;; reset all caches by changing the value of *generic-app-cache-tag*
|
|
(set! *generic-app-cache-tag* (list #f))
|
|
;; reset this generic app-cache
|
|
(%set-generic-app-cache! generic ???))
|
|
(%set-generic-methods!
|
|
generic
|
|
(cons method
|
|
(filter (lambda (m)
|
|
(not (and (every eq? (method-specializers m) specs)
|
|
(eq? (%method-qualifier m) qualifier))))
|
|
(%generic-methods generic))))
|
|
(set-instance-proc! generic (compute-apply-generic generic))))
|
|
|
|
;;; Adding a method calls COMPUTE-APPLY-GENERIC, the result of which calls the
|
|
;;; other generics in the generic invocation protocol. Two, related, problems
|
|
;;; come up. A chicken and egg problem and a infinite regress problem.
|
|
;;; In order to add our first method to COMPUTE-APPLY-GENERIC, we need
|
|
;;; something sitting there, so it can be called. The first definition below
|
|
;;; does that.
|
|
;;; Then, the second definition solves both the infinite regress and the not
|
|
;;; having enough of the protocol around to build itself problem the same way:
|
|
;;; it special cases invocation of generics in the invocation protocol.
|
|
|
|
(set-instance-proc! compute-apply-generic
|
|
(lambda (generic)
|
|
((%method-procedure (car (%generic-methods generic))) '() generic)))
|
|
|
|
(add-method compute-apply-generic
|
|
(make-method (list <generic>)
|
|
(named-lambda method:compute-apply-generic (call-next-method generic)
|
|
#| The code below is the original, then comes the optimized version below
|
|
;; see the definition of the <generic> class above.
|
|
(lambda args
|
|
(if (and (memq generic generic-invocation-generics) ;* Ground case
|
|
(memq (car args) generic-invocation-generics))
|
|
(apply (%method-procedure (last (%generic-methods generic))) #f args)
|
|
((compute-apply-methods generic)
|
|
(compute-methods generic args) . args)))
|
|
|#
|
|
;; This function converts the list of arguments to a list of keys to look
|
|
;; for in the cache - use the argument's class except when there is a
|
|
;; corresponding singleton with the same value at the same position.
|
|
(define (get-keys args tables)
|
|
(let loop ([args args] [tables tables] [ks '()])
|
|
(if (or (null? tables) (null? args))
|
|
(reverse ks)
|
|
(loop (cdr args) (cdr tables)
|
|
(cons (if (and (car tables)
|
|
(hash-table-get
|
|
(car tables) (car args) false-func))
|
|
(car args)
|
|
(class-of (car args)))
|
|
ks)))))
|
|
;; This is the main function that brings the correct value from the
|
|
;; cache, or generates one and store it if there is no entry, or the
|
|
;; cache was reset. Finally, it is applied to the arguments as usual.
|
|
;; NOTE: This code is delicate! Handle with extreme care!
|
|
(lambda args
|
|
(let ([app-cache (%generic-app-cache generic)]
|
|
[arity (%generic-arity generic)]
|
|
[keys (get-keys args (%generic-singletons-list generic))]
|
|
[ground? (and ;* Ground case
|
|
(memq generic generic-invocation-generics)
|
|
(pair? args)
|
|
(memq (car args) generic-invocation-generics))])
|
|
;; This function creates the cached closure -- the assumption is that
|
|
;; `keys' contain a specification that will identify all calls that
|
|
;; will have this exact same list.
|
|
(define (compute-callable)
|
|
(let ([c (if ground?
|
|
(let ([m (%method-procedure
|
|
(last (%generic-methods generic)))])
|
|
(lambda args (apply m #f args)))
|
|
(compute-apply-methods
|
|
generic (compute-methods generic args)))])
|
|
(hash-table-put! (cdr app-cache) keys c)
|
|
c))
|
|
(when (cond [(not arity) #f]
|
|
[(integer? arity) (not (= (length args) arity))]
|
|
[else (< (length args) (arity-at-least-value arity))])
|
|
(let ([least (and (arity-at-least? arity)
|
|
(arity-at-least-value arity))])
|
|
(raise* make-exn:fail:contract:arity
|
|
"generic ~a: expects ~a~e argument~a, given ~e~a"
|
|
(%generic-name generic)
|
|
(if least "at least " "") (or least arity)
|
|
(if (= 1 (or least arity)) "" "s") (length args)
|
|
(if (null? args) "" (format ": ~e" args)))))
|
|
(when (or (eq? app-cache ???)
|
|
(not (eq? (car app-cache) *generic-app-cache-tag*)))
|
|
(set! app-cache (cons *generic-app-cache-tag*
|
|
(make-hash-table 'weak 'equal)))
|
|
(%set-generic-app-cache! generic app-cache))
|
|
((hash-table-get (cdr app-cache) keys compute-callable)
|
|
. args))))))
|
|
|
|
(add-method compute-methods
|
|
(make-method (list <generic>)
|
|
(named-lambda method:compute-methods (call-next-method generic args)
|
|
(let ([more-specific? (compute-method-more-specific? generic)])
|
|
(sort (filter
|
|
(lambda (m)
|
|
;; Note that every only goes as far as the shortest list
|
|
(every instance-of? args (%method-specializers m)))
|
|
(%generic-methods generic))
|
|
(lambda (m1 m2) (more-specific? m1 m2 args)))))))
|
|
|
|
(add-method compute-method-more-specific?
|
|
(make-method (list <generic>)
|
|
(named-lambda method:compute-method-more-specific?
|
|
(call-next-method generic)
|
|
(lambda (m1 m2 args)
|
|
(let loop ([specls1 (%method-specializers m1)]
|
|
[specls2 (%method-specializers m2)]
|
|
[args args])
|
|
(cond [(and (null? specls1) (null? specls2))
|
|
(if (eq? (%method-qualifier m1) (%method-qualifier m2))
|
|
(error 'generic
|
|
"two methods are equally specific in ~e" generic)
|
|
#f)]
|
|
;; some methods in this file have less specializers than
|
|
;; others, for things like args -- so remove this, leave the
|
|
;; args check but treat the missing as if it's <top>
|
|
;; ((or (null? specls1) (null? specls2))
|
|
;; (error 'generic
|
|
;; "two methods have different number of ~
|
|
;; specializers in ~e" generic))
|
|
[(null? args) ; shouldn't happen
|
|
(error 'generic
|
|
"fewer arguments than specializers for ~e" generic)]
|
|
[(null? specls1) ; see above -> treat this like <top>
|
|
(if (eq? <top> (car specls2))
|
|
(loop specls1 (cdr specls2) (cdr args))
|
|
#f)]
|
|
[(null? specls2) ; see above -> treat this like <top>
|
|
(if (eq? <top> (car specls1))
|
|
(loop (cdr specls1) specls2 (cdr args))
|
|
#t)]
|
|
[else (let ([c1 (car specls1)] [c2 (car specls2)])
|
|
(if (eq? c1 c2)
|
|
(loop (cdr specls1) (cdr specls2) (cdr args))
|
|
(more-specific? c1 c2 (car args))))]))))))
|
|
|
|
(add-method compute-apply-methods
|
|
(make-method (list <generic>)
|
|
(named-lambda method:compute-apply-methods
|
|
(call-next-method generic methods)
|
|
(let ([primaries '()] [arounds '()] [befores '()] [afters '()]
|
|
[combination (%generic-combination generic)])
|
|
;; *** Trick: this (and in <method> above) is the only code that is
|
|
;; supposed to ever apply a method procedure. So, the closure that
|
|
;; will invoke `no-next-method' is named `*no-next-method*' so it is
|
|
;; identifiable. The only way to break this would be to call the
|
|
;; method-procedure directly on an object with such a name.
|
|
(define one-step
|
|
(if combination
|
|
(combination generic)
|
|
(lambda (tail args)
|
|
(lambda newargs
|
|
;; tail is never null: (null? (cdr tail)) below, and the fact
|
|
;; that this function is applied on the primaries which are
|
|
;; never null
|
|
(let ([args (if (null? newargs) args newargs)])
|
|
((cdar tail)
|
|
(if (null? (cdr tail))
|
|
(named-lambda *no-next-method* args
|
|
(no-next-method generic (caar tail) . args))
|
|
(one-step (cdr tail) args))
|
|
. args))))))
|
|
(define ((apply-before/after-method args) method)
|
|
((cdr method)
|
|
(named-lambda *no-next-method* args
|
|
(no-next-method generic (car method) . args))
|
|
. args))
|
|
(define ((call-before-primary-after args) . newargs)
|
|
;; could supply newargs below, but change before calling befores
|
|
(let ([args (if (null? newargs) args newargs)])
|
|
(for-each (apply-before/after-method args) befores)
|
|
(begin0 ((one-step primaries args))
|
|
(for-each (apply-before/after-method args) afters))))
|
|
(define (one-around-step tail args)
|
|
(if (null? tail)
|
|
(call-before-primary-after args)
|
|
(lambda newargs
|
|
(let ([args (if (null? newargs) args newargs)])
|
|
((cdar tail) (one-around-step (cdr tail) args) . args)))))
|
|
;; first sort by qualifier and pull out method-procedures
|
|
(let loop ([ms methods])
|
|
(unless (null? ms)
|
|
(letsubst ([(push! p)
|
|
(set! p (cons (cons (car ms)
|
|
(%method-procedure (car ms)))
|
|
p))])
|
|
(case (%method-qualifier (car ms))
|
|
[(:primary) (push! primaries)]
|
|
[(:around) (push! arounds)]
|
|
[(:before) (push! befores)]
|
|
[(:after) (push! afters)]
|
|
;; ignore other qualifiers
|
|
;; [else (error 'compute-apply-methods
|
|
;; "a method ~e has an unexpected qualifier `~e'"
|
|
;; (car methods)
|
|
;; (%method-qualifier (car methods)))]
|
|
)
|
|
(loop (cdr ms)))))
|
|
(set! primaries (reverse primaries))
|
|
(set! arounds (reverse arounds))
|
|
(set! befores (reverse befores))
|
|
;; no reverse for afters
|
|
(cond [(null? primaries)
|
|
(lambda args (no-applicable-method generic . args))]
|
|
;; optimize common case of only primaries
|
|
[(and (null? befores) (null? afters) (null? arounds))
|
|
;; args is initialized to () since if it is a generic of no
|
|
;; arguments then it will always stay so, otherwise, the first
|
|
;; call will have the real arguments anyway
|
|
(one-step primaries '())]
|
|
[else (one-around-step arounds '())])))))
|
|
|
|
;;>> (((make-generic-combination keys...) generic) tail args)
|
|
;;> This function can be used to construct simple method combinations that
|
|
;;> can be used with the `combination' slot of generic functions. The
|
|
;;> combination itself is a function that gets a generic and returns a
|
|
;;> function that gets a list of method/procedure pairs (for optimization
|
|
;;> the method-procedures are pre taken) and the arguments and performs
|
|
;;> the call -- but this is only interesting if there's any need to
|
|
;;> implement a method combination directly, otherwise, the
|
|
;;> `make-generic-combination' interface should allow enough freedom.
|
|
;;> Note that when a method combination is used, `around', `before', and
|
|
;;> `after' are called around the primary call as usual, but the primaries
|
|
;;> are never called with a valid `call-next-method' argument.
|
|
;;>
|
|
;;> The keyword arguments that can be taken determine the behavior of this
|
|
;;> combination. Overall, it is roughly like a customizable version of a
|
|
;;> fold operation on the method calls.
|
|
;;> * :init
|
|
;;> - The initial value for this computation. Defaults to null.
|
|
;;> * :combine
|
|
;;> - A function to be called on a method call result and the old value,
|
|
;;> and produces a new value. The default is `cons', which with an
|
|
;;> initial null value will collect the results into a reversed list.
|
|
;;> * :process-methods
|
|
;;> - A function that can be called on the initial list of
|
|
;;> method/procedure pairs to change it -- for example, it can be
|
|
;;> reversed to apply the methods from the least specific to the most
|
|
;;> specific. No default.
|
|
;;> * :process-result
|
|
;;> - A function that can be called on the final resulting value to
|
|
;;> produce the actual return value. For example, it can reverse back
|
|
;;> a list of accumulated values. No default.
|
|
;;> * :control
|
|
;;> - If this parameter is specified, then the `:combine' argument is
|
|
;;> ignored. The value given to `:control' should be a function of
|
|
;;> four arguments:
|
|
;;> 1. a `loop' function that should be called on some new value and
|
|
;;> some new tail;
|
|
;;> 2. a `val' argument that gets the current accumulated value;
|
|
;;> 3. a `this' thunk that can be called to apply the current method
|
|
;;> and return its result;
|
|
;;> 4. a `tail' value that holds the rest of the method/procedure list
|
|
;;> which can be sent to `loop'.
|
|
;;> It should be clear now, that a `:control' argument can have a lot
|
|
;;> of control on the computation, it can abort, change arbitrary
|
|
;;> values and skip calling methods. Note that if it won't call
|
|
;;> `loop' with an empty list, then a `:process-result' function will
|
|
;;> not be used as well. See the pre-defined combinations in the
|
|
;;> source code to see examples of using this function.
|
|
(define* (make-generic-combination
|
|
&key [init '()] [combine cons]
|
|
process-methods process-result control)
|
|
(lambda (generic)
|
|
(lambda (tail dummy-args)
|
|
(let ([tail (if process-methods (process-methods tail) tail)])
|
|
(lambda args
|
|
(let loop ([res init] [tail tail])
|
|
;; see *no-next-method* trick above
|
|
(let ([*no-next-method*
|
|
(lambda args (no-next-method generic (caar tail) . args))])
|
|
(if (null? tail)
|
|
(if process-result (process-result res) res)
|
|
(if control
|
|
(control loop res
|
|
(lambda () ((cdar tail) *no-next-method* . args))
|
|
(cdr tail))
|
|
(loop (combine ((cdar tail) *no-next-method* . args) res)
|
|
(cdr tail)))))))))))
|
|
|
|
;;>> generic-+-combination
|
|
;;>> generic-list-combination
|
|
;;>> generic-min-combination
|
|
;;>> generic-max-combination
|
|
;;>> generic-append-combination
|
|
;;>> generic-append!-combination
|
|
;;>> generic-begin-combination
|
|
;;>> generic-and-combination
|
|
;;>> generic-or-combination
|
|
;;> These are all functions that can be used as a `combination' value for
|
|
;;> a generic function. They work in the same way as the standard method
|
|
;;> combinations of CL. Most of them do the obvious thing based on some
|
|
;;> function to combine the result. The `begin' combination simply
|
|
;;> executes all methods one by one and returns the last value, the `and'
|
|
;;> and `or' combinations will call them one by one until a false or true
|
|
;;> result is returned. The source of these can be used as templates for
|
|
;;> defining more combinations.
|
|
(define* generic-+-combination
|
|
(make-generic-combination :init 0 :combine +))
|
|
(define* generic-list-combination
|
|
(make-generic-combination :process-result reverse))
|
|
(define* generic-min-combination
|
|
(make-generic-combination :process-result (lambda (r) (apply min r))))
|
|
(define* generic-max-combination
|
|
(make-generic-combination :process-result (lambda (r) (apply max r))))
|
|
(define* generic-append-combination
|
|
(make-generic-combination
|
|
:process-result (lambda (r) (apply append (reverse r)))))
|
|
(define* generic-append!-combination
|
|
(make-generic-combination
|
|
:process-result (lambda (r) (apply append (reverse r)))))
|
|
(define* generic-begin-combination
|
|
(make-generic-combination :init #f :combine (lambda (x y) x)))
|
|
(define* generic-and-combination
|
|
(make-generic-combination
|
|
:init #t
|
|
:control (lambda (loop val this tail) (and val (loop (this) tail)))))
|
|
(define* generic-or-combination
|
|
(make-generic-combination
|
|
:init #f
|
|
:control (lambda (loop val this tail) (or (this) (loop #f tail)))))
|
|
|
|
;;>>...
|
|
;;> *** More class functionality
|
|
;;> (In the following, a `class' can be a class, a singleton specifier, or a
|
|
;;> struct type.)
|
|
|
|
;; optimized helper
|
|
(defsubst (%struct->class c)
|
|
(if (struct-type? c) (struct-type->class c) c))
|
|
|
|
;;>> (subclass? class1 class2)
|
|
;;> Is `class1' a subclass of `class2'?
|
|
(define* (subclass? c1 c2)
|
|
(if (%singleton? c1)
|
|
(if (%singleton? c2)
|
|
(eq? (singleton-value c1) (singleton-value c2))
|
|
(instance-of? (singleton-value c1) (%struct->class c2)))
|
|
(memq (%struct->class c2) (%class-cpl (%struct->class c1)))))
|
|
|
|
;;>> (instance-of? x class)
|
|
;;> Checks if `x' is an instance of `class' (or one of its subclasses).
|
|
(define* (instance-of? x c)
|
|
;; efficiency: many cases use <top> (all untyped arguments)
|
|
(or (eq? c <top>)
|
|
(if (%singleton? c)
|
|
;; efficiency: similar to `subclass?' above
|
|
(eq? (singleton-value c) x)
|
|
(memq (%struct->class c) (%class-cpl (%struct->class (class-of x)))))))
|
|
|
|
;;>> (class? x)
|
|
;;> Determines whether `x' is a class.
|
|
(define* (class? x) (instance-of? x <class>))
|
|
(defsubst (%class? x) (instance-of? x <class>))
|
|
|
|
;;>> (specializer? x)
|
|
;;> Determines whether `x' is a class, a singleton, or a struct-type.
|
|
(define* (specializer? x) (or (class? x) (%singleton? x) (struct-type? x)))
|
|
|
|
;;>> (more-specific? class1 class2 x)
|
|
;;> Is `class1' more specific than `class2' for the given value?
|
|
(define* (more-specific? c1 c2 arg)
|
|
(if (%singleton? c1)
|
|
(and (eq? (singleton-value c1) arg)
|
|
(not (and (%singleton? c2) (eq? (singleton-value c1) arg))))
|
|
(let ([cc1 (memq (%struct->class c1) (%class-cpl (class-of arg)))])
|
|
(and cc1 (memq (%struct->class c2) (cdr cc1))))))
|
|
|
|
(add-method initialize
|
|
(make-method (list <top>)
|
|
(named-lambda method:initialize (call-next-method object initargs)
|
|
(error 'initialize "can't initialize an instance of ~e"
|
|
(class-of object)))))
|
|
|
|
(add-method initialize
|
|
(make-method (list <object>)
|
|
(named-lambda method:initialize (call-next-method object initargs)
|
|
(let* ([class (class-of object)]
|
|
[field-initializers (%class-field-initializers class)])
|
|
(for-each (lambda (init) (init . initargs))
|
|
(%class-initializers class))
|
|
(let loop ([n 0] [inits field-initializers])
|
|
(when (pair? inits)
|
|
(%instance-set! object n ((car inits) . initargs))
|
|
(loop (+ n 1) (cdr inits))))))))
|
|
|
|
(add-method initialize
|
|
(make-method (list <class>)
|
|
(named-lambda method:initialize (call-next-method class initargs)
|
|
(call-next-method)
|
|
(%set-class-direct-supers!
|
|
class
|
|
(let ([default (*default-object-class*)]
|
|
[supers (getarg initargs :direct-supers)])
|
|
;; check valid supers, and always have an object class
|
|
(cond
|
|
[(not default) supers] ; check disabled
|
|
[(or (not supers) (null? supers)) (list default)]
|
|
[(not (list? supers)) (error 'class "bad superclasses: ~e" supers)]
|
|
[else (let ([c (find-if
|
|
(lambda (c)
|
|
(not (and (%class? c) (subclass? c default))))
|
|
supers)])
|
|
(if c
|
|
(error 'class "cannot inherit from a ~a, ~e"
|
|
(if (%class? c) "non-object class" "non-class") c)
|
|
supers))])))
|
|
(%set-class-direct-slots!
|
|
class
|
|
(let ([autoinitargs (getarg initargs :autoinitargs)])
|
|
(map (lambda (s)
|
|
(if (pair? s)
|
|
(if (or (not autoinitargs)
|
|
(getarg (cdr s) :initarg)
|
|
(not (symbol? (car s))))
|
|
s
|
|
(list* (car s) :initarg (string->symbol
|
|
(string-append
|
|
":" (symbol->string (car s))))
|
|
(cdr s)))
|
|
(list s)))
|
|
(getarg initargs :direct-slots '()))))
|
|
(%set-class-cpl! class (compute-cpl class))
|
|
(%set-class-slots! class (compute-slots class))
|
|
(%set-class-name! class (or (getarg initargs :name) '-anonymous-))
|
|
(let* ([nfields 0]
|
|
[field-initializers '()]
|
|
;; allocator: give me an initializer function, get a slot number
|
|
[allocator (lambda (init)
|
|
(let ([f nfields])
|
|
(set! nfields (+ nfields 1))
|
|
(set! field-initializers
|
|
(cons init field-initializers))
|
|
f))]
|
|
[getters-n-setters (map (lambda (slot)
|
|
(cons (car slot)
|
|
(compute-getter-and-setter
|
|
class slot allocator)))
|
|
(%class-slots class))])
|
|
(%set-class-nfields! class nfields)
|
|
(%set-class-field-initializers! class (reverse field-initializers))
|
|
(%set-class-getters-n-setters! class getters-n-setters))
|
|
(%set-class-initializers!
|
|
class (reverse
|
|
(mappend
|
|
(lambda (c)
|
|
(if (instance-of? c <class>) (%class-initializers c) '()))
|
|
(cdr (%class-cpl class)))))
|
|
(%set-class-valid-initargs! ; for sanity checks
|
|
class (getarg initargs :valid-initargs
|
|
(thunk (mappend (lambda (slot)
|
|
(getargs (cdr slot) :initarg))
|
|
(%class-slots class))))))))
|
|
|
|
(add-method initialize
|
|
(make-method (list <generic>)
|
|
(named-lambda method:initialize (call-next-method generic initargs)
|
|
(call-next-method)
|
|
(%set-generic-methods! generic '())
|
|
(%set-generic-arity! generic (getarg initargs :arity #f))
|
|
(%set-generic-name! generic (or (getarg initargs :name) '-anonymous-))
|
|
(%set-generic-combination! generic (getarg initargs :combination))
|
|
(set-instance-proc! generic
|
|
(lambda args
|
|
(raise* make-exn:fail:contract
|
|
"~s: no methods added yet"
|
|
(%generic-name generic)))))))
|
|
|
|
(add-method initialize
|
|
(make-method (list <method>)
|
|
(named-lambda method:initialize (call-next-method method initargs)
|
|
(call-next-method)
|
|
(%set-method-specializers! method
|
|
(map (lambda (c) (%struct->class c))
|
|
(getarg initargs :specializers)))
|
|
(%set-method-procedure! method (getarg initargs :procedure))
|
|
(%set-method-qualifier! method (or (getarg initargs :qualifier)
|
|
:primary))
|
|
(%set-method-name! method (or (getarg initargs :name)
|
|
'-anonymous-))
|
|
(set-instance-proc! method (compute-apply-method method)))))
|
|
|
|
(add-method allocate-instance
|
|
(make-method (list <class>)
|
|
(named-lambda method:allocate-instance (call-next-method class initargs)
|
|
(%allocate-instance class (length (%class-field-initializers class))))))
|
|
|
|
(add-method allocate-instance
|
|
(make-method (list <entity-class>)
|
|
(named-lambda method:allocate-instance (call-next-method class initargs)
|
|
(%allocate-entity class (length (%class-field-initializers class))))))
|
|
|
|
(add-method compute-cpl
|
|
(make-method (list <class>)
|
|
(named-lambda method:compute-cpl (call-next-method class)
|
|
(compute-std-cpl class %class-direct-supers))))
|
|
|
|
(add-method compute-slots
|
|
(make-method (list <class>)
|
|
(named-lambda method:compute-slots (call-next-method class)
|
|
(let ([all-slots (map %class-direct-slots (%class-cpl class))]
|
|
[final-slots #f])
|
|
(let collect ([to-process (apply append all-slots)]
|
|
[result '()])
|
|
(if (null? to-process)
|
|
(set! final-slots result)
|
|
(let* ([name (caar to-process)]
|
|
[others '()]
|
|
[remaining-to-process
|
|
(filter (lambda (o)
|
|
(if (eq? (car o) name)
|
|
(begin (set! others (cons (cdr o) others)) #f)
|
|
#t))
|
|
to-process)])
|
|
(collect remaining-to-process
|
|
(cons (cons name (apply append (reverse others)))
|
|
result)))))
|
|
;; Sort the slots by order of appearance in cpl, makes them stay in the
|
|
;; same index, allowing optimizations for single-inheritance
|
|
(let collect ([to-process (apply append (reverse all-slots))]
|
|
[result '()])
|
|
(cond [(null? to-process) (reverse result)]
|
|
[(assq (caar to-process) result)
|
|
(collect (cdr to-process) result)]
|
|
[else (collect (cdr to-process)
|
|
(cons (assq (caar to-process) final-slots)
|
|
result))]))))))
|
|
|
|
(add-method compute-getter-and-setter
|
|
(make-method (list <class>)
|
|
(letrec ([nothing "nothing"]
|
|
[l-getarg
|
|
;; apply getarg on a list of names until get a value
|
|
(lambda (args initargs)
|
|
;; give priority to first initargs
|
|
(if (null? initargs)
|
|
nothing
|
|
(let ([x (getarg args (car initargs) nothing)])
|
|
(if (eq? x nothing) (l-getarg args (cdr initargs)) x))))])
|
|
(named-lambda method:compute-getter-and-setter
|
|
(call-next-method class slot allocator)
|
|
(let ([initargs (getargs (cdr slot) :initarg)]
|
|
[initializer (getarg (cdr slot) :initializer)]
|
|
[initvalue (getarg (cdr slot) :initvalue ???)]
|
|
[type (getarg (cdr slot) :type #f)]
|
|
[allocation (getarg (cdr slot) :allocation :instance)]
|
|
[lock (getarg (cdr slot) :lock #f)])
|
|
(define init
|
|
(if initializer
|
|
(if (eq? 0 (procedure-arity initializer))
|
|
(lambda args (initializer)) initializer)
|
|
(lambda args initvalue)))
|
|
(define (init-slot . args)
|
|
(let ([result (l-getarg args initargs)])
|
|
(when (eq? result nothing)
|
|
(set! result (apply init args)))
|
|
(when (and type (not (eq? result ???))
|
|
(not (instance-of? result type)))
|
|
(error 'class
|
|
"bad initial value type for slot ~e in ~e (~e not a ~e)"
|
|
(car slot) class result type))
|
|
result))
|
|
(when (and type (not (specializer? type)))
|
|
(error 'class "bad type specifier for ~e: ~e" (car slot) type))
|
|
(case allocation
|
|
[(:instance)
|
|
(let* ([f (allocator init-slot)]
|
|
[g+s (mcons (lambda (o) (%instance-ref o f))
|
|
(if (and type (not (eq? <top> type)))
|
|
(lambda (o n)
|
|
(if (instance-of? n type)
|
|
(%instance-set! o f n)
|
|
(raise* make-exn:fail:contract
|
|
"slot-set!: wrong type for slot ~
|
|
~e in ~e (~e not in ~e)"
|
|
(car slot) class n type)))
|
|
(lambda (o n) (%instance-set! o f n))))])
|
|
(when lock
|
|
(make-setter-locked! g+s lock
|
|
(lambda ()
|
|
(raise* make-exn:fail:contract
|
|
"slot-set!: slot `~e' in ~e is locked"
|
|
(car slot) (%class-name class)))))
|
|
g+s)]
|
|
[(:class)
|
|
(unless (null? initargs)
|
|
(let ([setter #f])
|
|
(%set-class-initializers!
|
|
class
|
|
(cons (lambda args
|
|
(let ([result (l-getarg args initargs)])
|
|
;; cache the setter
|
|
(unless setter
|
|
(set! setter
|
|
(mcdr (cdr (assq (car slot)
|
|
(%class-getters-n-setters
|
|
class))))))
|
|
(unless (eq? result nothing)
|
|
(setter #f result))))
|
|
(%class-initializers class)))))
|
|
(if (and (assq (car slot) (%class-direct-slots class))
|
|
(getarg (cdr (assq (car slot)
|
|
(%class-direct-slots class)))
|
|
:allocation #f))
|
|
;; the slot was declared as :class here
|
|
(let* ([cell (init)] ; default value - no arguments
|
|
[g+s (mcons (lambda (o) cell)
|
|
(lambda (o n)
|
|
(if (and type (not (instance-of? n type)))
|
|
(raise*
|
|
make-exn:fail:contract
|
|
"slot-set!: wrong type for shared slot ~
|
|
~e in ~e (~e not in ~e)"
|
|
(car slot) class n type)
|
|
(set! cell n))))])
|
|
(when lock
|
|
(make-setter-locked! (car slot) g+s lock
|
|
(lambda ()
|
|
(raise* make-exn:fail:contract
|
|
"slot-set!: slot `~e' in ~e is locked"
|
|
(car slot) (%class-name class)))))
|
|
g+s)
|
|
;; the slot was inherited as :class - fetch its getters/setters
|
|
(let loop ([cpl (cdr (%class-cpl class))])
|
|
(cond [(assq (car slot) (%class-getters-n-setters (car cpl)))
|
|
=> cdr]
|
|
[else (loop (cdr cpl))])))]
|
|
[else
|
|
(error 'class
|
|
"allocation for ~e must be :class or :instance, got ~e"
|
|
(car slot) allocation)]))))))
|
|
|
|
;;; Use the previous function when populating this generic.
|
|
(add-method compute-apply-method
|
|
(make-method (list <method>) method:compute-apply-method))
|
|
|
|
(add-method no-next-method
|
|
(make-method (list <generic> <method>)
|
|
(lambda (call-next-method generic method . args)
|
|
(raise* make-exn:fail:contract
|
|
(concat "~s: no applicable next method to call"
|
|
(case (%method-qualifier method)
|
|
[(:before) " in a `before' method"]
|
|
[(:after) " in an `after' method"]
|
|
[else ""])
|
|
" with arguments: ~e")
|
|
(%generic-name generic) args))))
|
|
(add-method no-next-method
|
|
(make-method (list (singleton #f) <method>)
|
|
(lambda (call-next-method generic method . args)
|
|
(raise* make-exn:fail:contract
|
|
(concat "~s: no applicable next method in a direct method call"
|
|
" with arguments: ~e")
|
|
(%method-name method) args))))
|
|
|
|
(add-method no-applicable-method
|
|
(make-method (list <generic>)
|
|
(lambda (call-next-method generic . args)
|
|
(raise* make-exn:fail:contract
|
|
"~s: no applicable primary methods for arguments ~e, of types ~e"
|
|
(%generic-name generic) args (map class-of args)))))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;; Customization variables
|
|
|
|
;;>>... Swindle Customization Parameters
|
|
|
|
;;>> *default-method-class*
|
|
;;>> *default-generic-class*
|
|
;;>> *default-class-class*
|
|
;;>> *default-entityclass-class*
|
|
;;> These parameters specify default classes for the many constructor
|
|
;;> macros in `clos'.
|
|
(define* *default-method-class* (make-parameter <method>))
|
|
(define* *default-generic-class* (make-parameter <generic>))
|
|
(define* *default-class-class* (make-parameter <class>))
|
|
(define* *default-entityclass-class* (make-parameter <entity-class>))
|
|
|
|
;; an automatic superclass for all classes -- turned off for the builtins below
|
|
;;>> *default-object-class*
|
|
;;> This parameter contains a value which is automatically made a
|
|
;;> superclass for all classes. Defaults to `<object>'.
|
|
(define* *default-object-class* (make-parameter #f))
|
|
|
|
;;>> *make-safely*
|
|
;;> Setting this parameter to #t will make Swindle perform sanity checks
|
|
;;> on given initargs for creating an object. This will make things
|
|
;;> easier for debugging, but also slower. Defaults to `#f'. Note that
|
|
;;> the sanity checks are done in `initialize'.
|
|
;; This could be in `make', but `defclass' will call that with no slots to make
|
|
;; the object and then call `initialize' with all arguments to actually create
|
|
;; the class.
|
|
(define* *make-safely* (make-parameter #f))
|
|
|
|
(define (check-initargs class initargs)
|
|
;; sanity check - verify sensible keywords given
|
|
(let ([valid-initargs (%class-valid-initargs class)])
|
|
(or (not valid-initargs)
|
|
(let loop ([args initargs])
|
|
(cond [(null? args) #t]
|
|
[(not (and (pair? args) (pair? (cdr args))))
|
|
(error 'make "error in initargs for ~e; arg list not balanced"
|
|
class)]
|
|
[(not (symbol? (car args)))
|
|
(error 'make "error in initargs for ~e; ~e is not a keyword"
|
|
class (car args))]
|
|
[(not (memq (car args) valid-initargs))
|
|
(error 'make "error in initargs for ~e; unknown keyword: ~e"
|
|
class (car args))]
|
|
[else (loop (cddr args))])))))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;; Make `make' a generic function
|
|
|
|
;;>>... Creating Instances
|
|
|
|
;;; Now everything works, both generic functions and classes, so we can turn on
|
|
;;; the real MAKE.
|
|
;;; ELI: This is turned into a generic function - do this carefully - first
|
|
;;; create the generic function and the method instances, then change make.
|
|
|
|
;;>> (make class initargs ...)
|
|
;;> Create an instance of `class', which can be any Swindle class (except
|
|
;;> for some special top-level classes and built-in classes).
|
|
;;>
|
|
;;> See the `Object Initialization Protocol' below for a description of
|
|
;;> generic functions that are involved in creating a Swindle object.
|
|
(let ([m (make-method (list <class>)
|
|
(named-lambda method:make (call-next-method class . initargs)
|
|
(let ([instance (allocate-instance class initargs)])
|
|
(when (*make-safely*) (check-initargs class initargs))
|
|
(initialize instance initargs)
|
|
instance)))]
|
|
[g (make-generic-function 'make)])
|
|
(add-method g m)
|
|
(set! make g))
|
|
|
|
;; The clean concept behind this is due to Joe Marshall.
|
|
|
|
;;>> (rec-make (name class arg ...) ...)
|
|
;;> This is similar to:
|
|
;;>
|
|
;;> (letrec ([name (make class arg ...)] ...)
|
|
;;> (values name ...))
|
|
;;>
|
|
;;> except that the names are first bound to allocated instances with no
|
|
;;> initargs, and then they are initialized with all these bindings. This
|
|
;;> is useful for situations where creating some instances needs other
|
|
;;> instances as values. One sample usage is the way `defclass' makes the
|
|
;;> class binding available for slot specifications like `:type'. Note
|
|
;;> that this is a special form, which invokes `allocate-instance' and
|
|
;;> `initialize' directly, so specializing `make' on some input will not
|
|
;;> change the way `rec-make' works.
|
|
(defsubst* (rec-make (name class arg ...) ...)
|
|
(let ([name (allocate-instance class (list arg ...))] ...)
|
|
(when (*make-safely*) (check-initargs class (list arg ...)) ...)
|
|
(initialize name (list arg ...)) ...
|
|
(values name ...)))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;; Make `add-method' a generic function
|
|
|
|
;;; Use this to compute a name for the method. specs is a list of classes or
|
|
;;; class-names (in case of unnamed-methods in clos.ss).
|
|
(define (compute-method-name specs generic-name)
|
|
(define (spec-string spec)
|
|
(cond [(%singleton? spec) (format "{~e}" (singleton-value spec))]
|
|
[(%class? spec) (symbol->string
|
|
(%class-name (%struct->class spec)))]
|
|
[else "???"]))
|
|
(string->symbol
|
|
(apply string-append
|
|
(symbol->string generic-name) ":"
|
|
(if (null? specs)
|
|
'("()")
|
|
(cons (spec-string (car specs))
|
|
(map (lambda (c) (string-append "," (spec-string c)))
|
|
(cdr specs)))))))
|
|
|
|
(let ([old-add-method add-method])
|
|
(set! add-method (make <generic> :name 'add-method :arity 2))
|
|
(old-add-method add-method
|
|
(make-method (list <generic> <method>)
|
|
(named-lambda method:add-method (call-next-method generic method)
|
|
(let ([method-arity (method-arity method)]
|
|
[generic-arity (%generic-arity generic)])
|
|
(cond
|
|
[(not generic-arity)
|
|
(%set-generic-arity! generic method-arity)]
|
|
;; note: equal? works on arity-at-least structs
|
|
[(not (equal? generic-arity method-arity))
|
|
(error 'add-method
|
|
"wrong arity for `~e', expects ~a; given a method with ~a"
|
|
(%generic-name generic)
|
|
(if (integer? generic-arity)
|
|
generic-arity
|
|
(format "at-least-~a"
|
|
(arity-at-least-value generic-arity)))
|
|
(if (integer? method-arity)
|
|
method-arity
|
|
(format "at-least-~a"
|
|
(arity-at-least-value method-arity))))])
|
|
;; set a name for the method if none (when attached to a generic)
|
|
(let ([n (%method-name method)])
|
|
(unless (and n (not (eq? n '-anonymous-)))
|
|
(%set-method-name!
|
|
method
|
|
(let* ([psym (object-name (%method-procedure method))]
|
|
[pstr (and psym (symbol->string psym))])
|
|
(if (or (not pstr) (regexp-match? #rx":[0-9]*:[0-9]*$" pstr))
|
|
(compute-method-name (%method-specializers method)
|
|
(%generic-name generic))
|
|
psym)))))
|
|
(old-add-method generic method))))))
|
|
|
|
;;; Optimized frequently used accessors:
|
|
;;; This is possible because of the ordering of the slots in compute-slots,
|
|
;;; works only for single-inheritance. Note that there is no type checking -
|
|
;;; it is unsafe, but makes things around 5-6 times faster!
|
|
(set! %class-direct-slots (%slot-getter <class> 'direct-slots))
|
|
(set! %class-direct-supers (%slot-getter <class> 'direct-supers))
|
|
(set! %class-slots (%slot-getter <class> 'slots))
|
|
(set! %class-nfields (%slot-getter <class> 'nfields))
|
|
(set! %class-field-initializers (%slot-getter <class> 'field-initializers))
|
|
(set! %class-getters-n-setters (%slot-getter <class> 'getters-n-setters))
|
|
(set! %class-cpl (%slot-getter <class> 'cpl))
|
|
(set! %class-name (%slot-getter <class> 'name))
|
|
(set! %class-initializers (%slot-getter <class> 'initializers))
|
|
(set! %class-valid-initargs (%slot-getter <class> 'valid-initargs))
|
|
(set! %generic-methods (%slot-getter <generic> 'methods))
|
|
(set! %generic-arity (%slot-getter <generic> 'arity))
|
|
(set! %generic-name (%slot-getter <generic> 'name))
|
|
(set! %generic-combination (%slot-getter <generic> 'combination))
|
|
(set! %method-specializers (%slot-getter <method> 'specializers))
|
|
(set! %method-procedure (%slot-getter <method> 'procedure))
|
|
(set! %method-qualifier (%slot-getter <method> 'qualifier))
|
|
(set! %method-name (%slot-getter <method> 'name))
|
|
(set! %set-class-direct-slots! (%slot-setter <class> 'direct-slots))
|
|
(set! %set-class-direct-supers! (%slot-setter <class> 'direct-supers))
|
|
(set! %set-class-slots! (%slot-setter <class> 'slots))
|
|
(set! %set-class-nfields! (%slot-setter <class> 'nfields))
|
|
(set! %set-class-field-initializers!(%slot-setter <class> 'field-initializers))
|
|
(set! %set-class-getters-n-setters! (%slot-setter <class> 'getters-n-setters))
|
|
(set! %set-class-cpl! (%slot-setter <class> 'cpl))
|
|
(set! %set-class-name! (%slot-setter <class> 'name))
|
|
(set! %set-class-initializers! (%slot-setter <class> 'initializers))
|
|
(set! %set-class-valid-initargs! (%slot-setter <class> 'valid-initargs))
|
|
(set! %set-generic-methods! (%slot-setter <generic> 'methods))
|
|
(set! %set-generic-arity! (%slot-setter <generic> 'arity))
|
|
(set! %set-generic-name! (%slot-setter <generic> 'name))
|
|
(set! %set-generic-combination! (%slot-setter <generic> 'combination))
|
|
(set! %set-method-specializers! (%slot-setter <method> 'specializers))
|
|
(set! %set-method-procedure! (%slot-setter <method> 'procedure))
|
|
(set! %set-method-qualifier! (%slot-setter <method> 'qualifier))
|
|
(set! %set-method-name! (%slot-setter <method> 'name))
|
|
;; Optimize these internal ones as well.
|
|
(set! %generic-app-cache (%slot-getter <generic> 'app-cache))
|
|
(set! %generic-singletons-list (%slot-getter <generic> 'singletons-list))
|
|
(set! %set-generic-app-cache! (%slot-setter <generic> 'app-cache))
|
|
(set! %set-generic-singletons-list! (%slot-setter <generic> 'singletons-list))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;; Built-in classes.
|
|
|
|
;;>>... Built-in Classes
|
|
|
|
;;>> <primitive-class>
|
|
;;> The class of all built-on classes.
|
|
(define* <primitive-class>
|
|
(make <class> :direct-supers (list <class>)
|
|
:direct-slots '()
|
|
:name '<primitive-class>
|
|
;; needed so structs can turn to classes even if *make-safely*
|
|
:valid-initargs #f))
|
|
;; Normally, can't allocate these.
|
|
(add-method allocate-instance
|
|
(make-method (list <primitive-class>)
|
|
(named-lambda method:allocate-instance (call-next-method class initargs)
|
|
(error 'allocate-instance "can't instantiate a primitive class ~e"
|
|
class))))
|
|
|
|
;;>> <builtin>
|
|
;;> The superclass of all built-in classes.
|
|
(define* <builtin>
|
|
(make <class> :direct-supers (list <top>)
|
|
:direct-slots '()
|
|
:name '<builtin>))
|
|
(defsubst (defprimclass primclass) (defprimclass primclass <builtin>)
|
|
(_ primclass supers ...) (define* primclass
|
|
(make <primitive-class>
|
|
:name 'primclass
|
|
:direct-supers (list supers ...)
|
|
:direct-slots '())))
|
|
;;>> <sequence>
|
|
;;>> <mutable>
|
|
;;>> <immutable>
|
|
;;>> <pair>
|
|
;;>> <mutable-pair>
|
|
;;>> <mpair>
|
|
;;>> <immutable-pair>
|
|
;;>> <list>
|
|
;;>> <nonempty-list>
|
|
;;>> <null>
|
|
;;>> <vector>
|
|
;;>> <char>
|
|
;;>> <string-like>
|
|
;;>> <mutable-string-like>
|
|
;;>> <immutable-string-like>
|
|
;;>> <string>
|
|
;;>> <mutable-string>
|
|
;;>> <immutable-string>
|
|
;;>> <bytes>
|
|
;;>> <mutable-bytes>
|
|
;;>> <immutable-bytes>
|
|
;;>> <path>
|
|
;;>> <symbol>
|
|
;;>> <keyword>
|
|
;;>> <real-keyword>
|
|
;;>> <boolean>
|
|
;;>> <number>
|
|
;;>> <exact>
|
|
;;>> <inexact>
|
|
;;>> <complex>
|
|
;;>> <real>
|
|
;;>> <rational>
|
|
;;>> <integer>
|
|
;;>> <exact-complex>
|
|
;;>> <inexact-complex>
|
|
;;>> <exact-real>
|
|
;;>> <inexact-real>
|
|
;;>> <exact-rational>
|
|
;;>> <inexact-rational>
|
|
;;>> <exact-integer>
|
|
;;>> <inexact-integer>
|
|
;;>> <end-of-file>
|
|
;;>> <port>
|
|
;;>> <input-port>
|
|
;;>> <output-port>
|
|
;;>> <stream-port>
|
|
;;>> <input-stream-port>
|
|
;;>> <output-stream-port>
|
|
;;>> <void>
|
|
;;>> <box>
|
|
;;>> <weak-box>
|
|
;;>> <regexp>
|
|
;;>> <byte-regexp>
|
|
;;>> <parameter>
|
|
;;>> <promise>
|
|
;;>> <exn>
|
|
;;>> <exn:fail>
|
|
;;>> <exn:break>
|
|
;;>> <semaphore>
|
|
;;>> <hash-table>
|
|
;;>> <subprocess>
|
|
;;>> <thread>
|
|
;;>> <syntax>
|
|
;;>> <identifier-syntax>
|
|
;;>> <namespace>
|
|
;;>> <custodian>
|
|
;;>> <tcp-listener>
|
|
;;>> <security-guard>
|
|
;;>> <will-executor>
|
|
;;>> <struct-type>
|
|
;;>> <inspector>
|
|
;;>> <pseudo-random-generator>
|
|
;;>> <compiled-expression>
|
|
;;>> <unknown-primitive>
|
|
;;> These classes represent built-in objects. See the class hierarchy
|
|
;;> below for a complete description of the relations between these
|
|
;;> classes.
|
|
;;>> <struct>
|
|
;;>> <opaque-struct>
|
|
;;> These are also classes for built-in objects, but they are classes for
|
|
;;> MzScheme structs -- which can be used like Swindle classes since they
|
|
;;> will get converted to appropriate Swindle subclasses of `<struct>'.
|
|
;;> `<opaque-struct>' is a class of structs that are hidden -- see the
|
|
;;> documentation for `struct-info' and the `skipped?' result. Note that
|
|
;;> structs can be used as long as they can be inspected -- otherwise, we
|
|
;;> can't even know that they are structs with `struct?' (this means that
|
|
;;> <opaque-struct> can only appear in the cpl of a struct class that
|
|
;;> inherits from a struct which is not under the current inspector).
|
|
(defprimclass <sequence>)
|
|
(defprimclass <mutable>)
|
|
(defprimclass <immutable>)
|
|
(defprimclass <pair> <sequence>)
|
|
(defprimclass <mutable-pair> <pair> <mutable>)
|
|
(define* <mpair> <mutable-pair>) ; alias
|
|
(defprimclass <immutable-pair> <pair> <immutable>)
|
|
(defprimclass <list> <sequence>)
|
|
(defprimclass <nonempty-list> <pair> <list> <immutable>)
|
|
(defprimclass <null> <list>)
|
|
(defprimclass <vector> <sequence> <mutable>)
|
|
(defprimclass <char>)
|
|
(defprimclass <string-like> <sequence>)
|
|
(defprimclass <mutable-string-like> <string-like> <mutable>)
|
|
(defprimclass <immutable-string-like> <string-like> <immutable>)
|
|
(defprimclass <string> <string-like>)
|
|
(defprimclass <mutable-string> <string> <mutable-string-like>)
|
|
(defprimclass <immutable-string> <string> <immutable-string-like>)
|
|
(defprimclass <bytes> <string-like>)
|
|
(defprimclass <mutable-bytes> <bytes> <mutable-string-like>)
|
|
(defprimclass <immutable-bytes> <bytes> <immutable-string-like>)
|
|
(defprimclass <path> <immutable-string-like>)
|
|
(defprimclass <symbol>)
|
|
(defprimclass <keyword> <symbol>)
|
|
(defprimclass <real-keyword>)
|
|
(defprimclass <boolean>)
|
|
;; Have all possible number combinations in any case
|
|
(defprimclass <number>)
|
|
(defprimclass <exact> <number>)
|
|
(defprimclass <inexact> <number>)
|
|
(defprimclass <complex> <number>)
|
|
(defprimclass <real> <complex>)
|
|
(defprimclass <rational> <real>)
|
|
(defprimclass <integer> <rational>)
|
|
(defprimclass <exact-complex> <complex> <exact>)
|
|
(defprimclass <inexact-complex> <complex> <inexact>)
|
|
(defprimclass <exact-real> <real> <exact-complex>)
|
|
(defprimclass <inexact-real> <real> <inexact-complex>)
|
|
(defprimclass <exact-rational> <rational> <exact-real>)
|
|
(defprimclass <inexact-rational> <rational> <inexact-real>)
|
|
(defprimclass <exact-integer> <integer> <exact-rational>)
|
|
(defprimclass <inexact-integer> <integer> <inexact-rational>)
|
|
(defprimclass <end-of-file>)
|
|
(defprimclass <port>)
|
|
(defprimclass <input-port> <port>)
|
|
(defprimclass <output-port> <port>)
|
|
(defprimclass <stream-port> <port>)
|
|
;; MzScheme stuff
|
|
(defprimclass <input-stream-port> <input-port> <stream-port>)
|
|
(defprimclass <output-stream-port> <output-port> <stream-port>)
|
|
(defprimclass <void>)
|
|
(defprimclass <box> <mutable>)
|
|
(defprimclass <weak-box> <box>)
|
|
(defprimclass <regexp>)
|
|
(defprimclass <byte-regexp>)
|
|
(defprimclass <parameter>)
|
|
(defprimclass <promise>)
|
|
(defprimclass <exn>)
|
|
(defprimclass <exn:fail> <exn>)
|
|
(defprimclass <exn:break> <exn>)
|
|
;; make these classes used when we see exn structs
|
|
(let ([set-exn-class
|
|
(lambda (class make-exn . xs)
|
|
(hash-table-put! struct-to-class-table
|
|
(let-values ([(e _)
|
|
(struct-info
|
|
(apply make-exn "foo"
|
|
(current-continuation-marks)
|
|
xs))])
|
|
e)
|
|
class))])
|
|
(set-exn-class <exn> make-exn)
|
|
(set-exn-class <exn:fail> make-exn:fail)
|
|
(set-exn-class <exn:break> make-exn:break (let/ec e e)))
|
|
(defprimclass <semaphore>)
|
|
(defprimclass <hash-table>)
|
|
(defprimclass <subprocess>)
|
|
(defprimclass <thread>)
|
|
(defprimclass <syntax>)
|
|
(defprimclass <identifier-syntax> <syntax>)
|
|
(defprimclass <namespace>)
|
|
(defprimclass <custodian>)
|
|
(defprimclass <tcp-listener>)
|
|
(defprimclass <security-guard>)
|
|
(defprimclass <will-executor>)
|
|
(defprimclass <struct-type>)
|
|
(defprimclass <inspector>)
|
|
(defprimclass <pseudo-random-generator>)
|
|
(defprimclass <compiled-expression>)
|
|
(defprimclass <unknown-primitive>)
|
|
(defprimclass <struct>)
|
|
(defprimclass <opaque-struct> <struct>)
|
|
;;>> <procedure>
|
|
;;> The class of all Scheme procedures.
|
|
(define* <procedure>
|
|
(make <procedure-class> :name '<procedure>
|
|
:direct-supers (list <builtin> <function>)
|
|
:direct-slots '()))
|
|
;;>> <primitive-procedure>
|
|
;;> The class of all primitive MzScheme procedures.
|
|
(define* <primitive-procedure>
|
|
(make <procedure-class>
|
|
:name '<primitive-procedure>
|
|
:direct-supers (list <procedure>)
|
|
:direct-slots '()))
|
|
|
|
(*default-object-class* <object>) ; turn auto-superclass back on
|
|
|
|
(set! class-of
|
|
(lambda (x)
|
|
;; If all Schemes were IEEE compliant, the order of these wouldn't
|
|
;; matter?
|
|
;; ELI: changed the order so it fits better the expected results.
|
|
(cond [(instance? x) (instance-class x)]
|
|
[(struct? x)
|
|
(let-values ([(type _) (struct-info x)])
|
|
(if type (struct-type->class type) <opaque-struct>))]
|
|
[(procedure? x) (cond [(parameter? x) <parameter>]
|
|
[(primitive? x) <primitive-procedure>]
|
|
[else <procedure>])]
|
|
[(string? x) (if (immutable? x) <immutable-string> <string>)]
|
|
[(pair? x) (if (list? x) <nonempty-list> <immutable-pair>)]
|
|
[(null? x) <null>]
|
|
[(symbol? x) (if (keyword? x) <keyword> <symbol>)]
|
|
[(number? x)
|
|
(if (exact? x)
|
|
(cond [(integer? x) <exact-integer>]
|
|
[(rational? x) <exact-rational>]
|
|
[(real? x) <exact-real>]
|
|
[(complex? x) <exact-complex>]
|
|
[else <exact>]) ; should not happen
|
|
(cond [(integer? x) <inexact-integer>]
|
|
[(rational? x) <inexact-rational>]
|
|
[(real? x) <inexact-real>]
|
|
[(complex? x) <inexact-complex>]
|
|
[else <inexact>]))] ; should not happen
|
|
[(boolean? x) <boolean>]
|
|
[(char? x) <char>]
|
|
[(bytes? x) (if (immutable? x) <immutable-bytes> <bytes>)]
|
|
[(path? x) <path>]
|
|
[(vector? x) <vector>]
|
|
[(mpair? x) <mutable-pair>]
|
|
[(eof-object? x) <end-of-file>]
|
|
[(input-port? x)
|
|
(if (file-stream-port? x) <input-stream-port> <input-port>)]
|
|
[(output-port? x)
|
|
(if (file-stream-port? x) <output-stream-port> <output-port>)]
|
|
[(void? x) <void>]
|
|
[(box? x) <box>]
|
|
[(weak-box? x) <weak-box>]
|
|
[(regexp? x) <regexp>]
|
|
[(byte-regexp? x) <byte-regexp>]
|
|
[(promise? x) <promise>]
|
|
[(real-keyword? x) <real-keyword>]
|
|
[(semaphore? x) <semaphore>]
|
|
[(hash-table? x) <hash-table>]
|
|
[(thread? x) <thread>]
|
|
[(subprocess? x) <subprocess>]
|
|
[(syntax? x)
|
|
(if (identifier? x) <identifier-syntax> <syntax>)]
|
|
[(namespace? x) <namespace>]
|
|
[(custodian? x) <custodian>]
|
|
[(tcp-listener? x) <tcp-listener>]
|
|
[(security-guard? x) <security-guard>]
|
|
[(will-executor? x) <will-executor>]
|
|
[(struct-type? x) <struct-type>]
|
|
[(inspector? x) <inspector>]
|
|
[(pseudo-random-generator? x) <pseudo-random-generator>]
|
|
[(compiled-expression? x) <compiled-expression>]
|
|
[else <unknown-primitive>])))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;; Some useful predicates.
|
|
|
|
;;>> (builtin? x)
|
|
;;>> (function? x)
|
|
;;>> (generic? x)
|
|
;;>> (method? x)
|
|
;;> Predicates for instances of <builtin>, <function>, <generic>, and
|
|
;;> <method>.
|
|
(define* (builtin? x) (instance-of? x <builtin>))
|
|
(define* (function? x) (instance-of? x <function>))
|
|
(define* (generic? x) (instance-of? x <generic>))
|
|
(define* (method? x) (instance-of? x <method>))
|
|
|
|
;;; ---------------------------------------------------------------------------
|
|
;;>>... Class Hierarchy
|
|
;;>
|
|
;;> In the following, every class's class is specified after a colon. Also,
|
|
;;> some classes appear in more than once place due to multiple-inheritance.
|
|
;;>
|
|
;;> <top> : <class>
|
|
;;> <object> : <class>
|
|
;;> <class> : <class>
|
|
;;> <procedure-class> : <class>
|
|
;;> <entity-class> : <class>
|
|
;;> <primitive-class> : <class>
|
|
;;> <generic> : <entity-class>
|
|
;;> <method> : <entity-class>
|
|
;;> <function> : <class>
|
|
;;> <generic> : <entity-class>
|
|
;;> <method> : <entity-class>
|
|
;;> <procedure> : <procedure-class>
|
|
;;> <primitive-procedure> : <procedure-class>
|
|
;;> <builtin> : <class>
|
|
;;> <sequence> : <primitive-class>
|
|
;;> <pair> : <primitive-class>
|
|
;;> <mutable-pair> : <primitive-class>
|
|
;;> <mpair> : <primitive-class> ; alias for <mutable-pair>
|
|
;;> <immutable-pair> : <primitive-class>
|
|
;;> <nonempty-list> : <primitive-class>
|
|
;;> <list> : <primitive-class>
|
|
;;> <nonempty-list> : <primitive-class>
|
|
;;> <null> : <primitive-class>
|
|
;;> <vector> : <primitive-class>
|
|
;;> <string-like> : <primitive-class>
|
|
;;> <string> : <primitive-class>
|
|
;;> <mutable-string> : <primitive-class>
|
|
;;> <immutable-string> : <primitive-class>
|
|
;;> <bytes> : <primitive-class>
|
|
;;> <mutable-bytes> : <primitive-class>
|
|
;;> <immutable-bytes> : <primitive-class>
|
|
;;> <path> : <primitive-class>
|
|
;;> <mutable> : <primitive-class>
|
|
;;> <mutable-pair> : <primitive-class>
|
|
;;> <mpair> : <primitive-class> ; alias for <mutable-pair>
|
|
;;> <mutable-string-like> : <primitive-class>
|
|
;;> <mutable-string> : <primitive-class>
|
|
;;> <mutable-bytes> : <primitive-class>
|
|
;;> <vector>
|
|
;;> <box>
|
|
;;> <immutable> : <primitive-class>
|
|
;;> <list> : <primitive-class>
|
|
;;> <pair> : <primitive-class>
|
|
;;> <immutable-string-like> : <primitive-class>
|
|
;;> <immutable-string> : <primitive-class>
|
|
;;> <immutable-bytes> : <primitive-class>
|
|
;;> <path> : <primitive-class>
|
|
;;> <char> : <primitive-class>
|
|
;;> <symbol> : <primitive-class>
|
|
;;> <keyword> : <primitive-class>
|
|
;;> <real-keyword> : <primitive-class>
|
|
;;> <boolean> : <primitive-class>
|
|
;;> <number> : <primitive-class>
|
|
;;> <complex> : <primitive-class>
|
|
;;> <exact-complex> : <primitive-class>
|
|
;;> <inexact-complex> : <primitive-class>
|
|
;;> <real> : <primitive-class>
|
|
;;> <exact-real> : <primitive-class>
|
|
;;> <inexact-real> : <primitive-class>
|
|
;;> <rational> : <primitive-class>
|
|
;;> <integer> : <primitive-class>
|
|
;;> <exact-rational> : <primitive-class>
|
|
;;> <inexact-rational> : <primitive-class>
|
|
;;> <exact-integer> : <primitive-class>
|
|
;;> <inexact-integer> : <primitive-class>
|
|
;;> <exact> : <primitive-class>
|
|
;;> <exact-complex> : <primitive-class>
|
|
;;> <exact-real> : <primitive-class>
|
|
;;> <exact-rational> : <primitive-class>
|
|
;;> <exact-integer> : <primitive-class>
|
|
;;> <inexact> : <primitive-class>
|
|
;;> <inexact-complex> : <primitive-class>
|
|
;;> <inexact-real> : <primitive-class>
|
|
;;> <inexact-rational> : <primitive-class>
|
|
;;> <inexact-integer> : <primitive-class>
|
|
;;> <end-of-file> : <primitive-class>
|
|
;;> <port> : <primitive-class>
|
|
;;> <input-port> : <primitive-class>
|
|
;;> <input-stream-port> : <primitive-class>
|
|
;;> <output-port> : <primitive-class>
|
|
;;> <output-stream-port> : <primitive-class>
|
|
;;> <stream-port> : <primitive-class>
|
|
;;> <input-stream-port> : <primitive-class>
|
|
;;> <output-stream-port> : <primitive-class>
|
|
;;> <void> : <primitive-class>
|
|
;;> <box> : <primitive-class>
|
|
;;> <weak-box> : <primitive-class>
|
|
;;> <regexp> : <primitive-class>
|
|
;;> <byte-regexp> : <primitive-class>
|
|
;;> <parameter> : <primitive-class>
|
|
;;> <promise> : <primitive-class>
|
|
;;> <exn> : <primitive-class>
|
|
;;> <exn:fail> : <primitive-class>
|
|
;;> <exn:break> : <primitive-class>
|
|
;;> <semaphore> : <primitive-class>
|
|
;;> <hash-table> : <primitive-class>
|
|
;;> <subprocess> : <primitive-class>
|
|
;;> <thread> : <primitive-class>
|
|
;;> <syntax> : <primitive-class>
|
|
;;> <identifier-syntax> : <primitive-class>
|
|
;;> <namespace> : <primitive-class>
|
|
;;> <custodian> : <primitive-class>
|
|
;;> <tcp-listener> : <primitive-class>
|
|
;;> <security-guard> : <primitive-class>
|
|
;;> <will-executor> : <primitive-class>
|
|
;;> <inspector> : <primitive-class>
|
|
;;> <pseudo-random-generator> : <primitive-class>
|
|
;;> <compiled-expression> : <primitive-class>
|
|
;;> <unknown-primitive> : <primitive-class>
|
|
;;> <procedure> : <procedure-class>
|
|
;;> <primitive-procedure> : <procedure-class>
|
|
;;> <struct> : <primitive-class>
|
|
;;> <opaque-struct> : <primitive-class>
|
|
;;> ... struct type classes ...
|
|
|
|
;;>>... Object Initialization Protocol
|
|
;;> This is the initialization protocol. All of these are generic
|
|
;;> functions (unlike the original Tiny CLOS). See the individual
|
|
;;> descriptions above for more details.
|
|
;;>
|
|
;;> make
|
|
;;> allocate-instance
|
|
;;> initialize
|
|
;;> class initialization only:
|
|
;;> compute-cpl
|
|
;;> compute-slots
|
|
;;> compute-getter-and-setter
|
|
;;> method initialization only:
|
|
;;> compute-apply-method
|
|
;;> add-method
|
|
;;> compute-apply-generic
|
|
;;> compute-methods
|
|
;;> compute-method-more-specific?
|
|
;;> compute-apply-methods
|