summaryrefslogtreecommitdiff
path: root/cgen/cos.scm
diff options
context:
space:
mode:
Diffstat (limited to 'cgen/cos.scm')
-rw-r--r--cgen/cos.scm1336
1 files changed, 1336 insertions, 0 deletions
diff --git a/cgen/cos.scm b/cgen/cos.scm
new file mode 100644
index 00000000000..7bb2a6e8630
--- /dev/null
+++ b/cgen/cos.scm
@@ -0,0 +1,1336 @@
+; Cgen's Object System.
+; Copyright (C) 2000 Red Hat, Inc.
+; This file is part of CGEN.
+; See file COPYING.CGEN for details.
+;
+; When Guile has an official object implementation that is stable, things will
+; be switched over then. Until such time, there's no point in getting hyper
+; (although doing so is certainly fun, but only to a point).
+; If the Guile team decides there won't be any official object system
+; (which isn't unreasonable) then we'll pick the final object system then.
+; Until such time, there are better things to do than trying to build a
+; better object system. If this is important enough to you, help the Guile
+; team finish the module(/object?) system.
+;
+; Classes look like:
+;
+; #(class-tag
+; class-name
+; parent-name-list
+; elm-alist
+; method-alist
+; full-elm-initial-list
+; full-method-alist ; ??? not currently used
+; class-descriptor)
+;
+; PARENT-NAME-LIST is a list of the names of parent classes (the inheritance
+; tree).
+;
+; ELM-ALIST is an alist of (symbol private? vector-index . initial-value)
+; for this class only.
+; Values can be looked up by name, via elm-make-[gs]etter routines, or
+; methods can use elm-get/set! for speed.
+; Various Lisp (or Lisp-like) OOP systems (e.g. CLOS, Dylan) call these
+; "slots". Maybe for consistency "slot" would be a better name. Some might
+; confuse that with intentions at directions. Given that something better
+; will eventually happen, being deliberately different is useful.
+;
+; METHOD-ALIST is an alist of (symbol . (virtual? . procedure)) for this
+; class only.
+;
+; FULL-ELM-INITIAL-LIST is the elements of the flattened inheritance tree.
+; Initially it is #f meaning it hasn't been computed yet.
+; It is computed when the class is first instantiated. During development,
+; it can be reset to #f after some module has been reloaded (requires all
+; object instantiation happens later of course).
+;
+; FULL-METHOD-ALIST is an alist of the methods of the flattened inheritance
+; tree. Each element is (symbol . (parent-list-entry . method)).
+; Initially it is #f meaning it hasn't been computed yet.
+; It is computed when the class is first instantiated. During development,
+; it can be reset to #f after some module has been reloaded (requires all
+; object instantiation happens later of course).
+;
+; CLASS-DESCRIPTOR is the processed form of parent-name-list.
+; There is an entry for the class and one for each parent (recursively):
+; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...).
+; mi? is #t if the class or any parent class has multiple inheritance.
+; This is used by the element access routines.
+; base-offset is the offset in the element vector of the baseclass (or first
+; baseclass in the mi case).
+; delta is the offset from base-offset of the class's own elements
+; (as opposed to elements in any parent class).
+; child-backpointer is #f in the top level object.
+; ??? child->subclass, parent->superclass?
+; Initially the class-descriptor is #f meaning it hasn't been computed yet.
+; It is computed when the class is first instantiated. During development,
+; it can be reset to #f after some module has been reloaded (requires all
+; object instantiation to happen later of course).
+;
+; An object is a vector of 2 elements: #(object-elements class-descriptor).
+; ??? Things would be simpler if objects were a pair but that makes eval'ing
+; them trickier. Vectors are nice in that they're self-evaluating, though
+; due to the self-referencing, which Guile 1.2 can't handle, apps have to
+; be careful.
+; ??? We could use smobs/records/whatever but the difference isn't big enough
+; for me to care at this point in time.
+;
+; `object-elements' looks like:
+;
+; #(object-tag
+; class
+; element1
+; element2
+; ...)
+;
+; CLASS is the class the object is an instance of.
+;
+; User visible procs:
+;
+; (class-make name parents elements methods) -> class
+;
+; Create a class. The result is then passed back by procedures requiring
+; a class argument. Note however that PARENTS is a list of class names,
+; not the class data type. This allows reloading the definition of a
+; parent class without having to reload any subclasses. To implement this
+; classes are recorded internally, and `object-init!' must be called if any
+; class has been redefined.
+;
+; (class-list) -> list of all defined classes
+;
+; (class-name class) -> name of CLASS
+;
+; (class-lookup class-name) -> class
+;
+; (class-instance? class object) -> #t if OBJECT is an instance of CLASS
+;
+; (object-class object) -> class of OBJECT
+;
+; (object-class-name object) -> class name of OBJECT
+;
+; (send object method-name . args) -> result of invoking METHOD-NAME
+;
+; (send-next object method-name . args) -> result of invoking next METHOD-NAME
+;
+; (new class) -> instantiate CLASS
+;
+; The object is initialized with values specified when CLASS
+; (and its parent classes) was defined.
+;
+; (vmake class . args) -> instantiate class and initialize it with 'vmake!
+;
+; This is shorthand for (send (new class) 'vmake! args).
+; ARGS is a list of option names and arguments (a la CLOS).
+; ??? Not implemented yet.
+;
+; (method-vmake! object . args) -> modify OBJECT from ARGS
+;
+; This is the standard 'vmake! method, available for use by user-written
+; 'vmake! methods.
+; ??? Not implemented yet.
+;
+; (make class . args) -> instantiate CLASS and initialize it with 'make!
+;
+; This is shorthand for (send (new class) 'make! arg1 ...).
+; This is a positional form of `new'.
+;
+; (method-make-make! class elm1-name elm2-name ...) -> unspecified
+;
+; Create a 'make! method that sets the specified elements.
+;
+; (object-copy object) -> copy of OBJ
+;
+; ??? Whether to discard the parent or keep it and retain specialization
+; is undecided.
+;
+; (object-copy-top object) -> copy of OBJECT with spec'n discarded
+;
+; (object-parent object parent-path) -> parent object in OBJECT via PARENT-PATH
+;
+; (class? foo) -> return #t if FOO is a class
+;
+; (object? foo) -> return #t if FOO is an object
+;
+; (method-make! class name lambda) -> unspecified
+;
+; Add method NAME to CLASS.
+;
+; (method-make-virtual! class name lambda) -> unspecified
+;
+; Add virtual method NAME to CLASS.
+;
+; (method-make-forward! class elm-name methods) -> unspecified
+;
+; Add METHODS to CLASS that pass the "message" onto the object in element
+; ELM-NAME.
+;
+; (method-make-virtual-forward! class elm-name methods) -> unspecified
+;
+; Add virtual METHODS to CLASS that pass the "message" onto the object in
+; element ELM-NAME.
+;
+; (elm-get object elm-name) -> value of element ELM-NAME in OBJ
+;
+; Can only be used in methods.
+;
+; (elm-set! object elm-name new-value) -> unspecified
+;
+; Set element ELM-NAME in OBJECT to NEW-VALUE.
+; Can only be used in methods.
+;
+; (elm-make-getter class elm-name) -> lambda
+;
+; Return lambda to get the value of ELM-NAME in CLASS.
+;
+; (elm-make-setter class elm-name) -> lambda
+;
+; Return lambda to set the value of ELM-NAME in CLASS.
+;
+; Conventions used in this file:
+; - procs/vars internal to this file are prefixed with "-"
+; [Of course this could all be put in a module; later if ever since
+; once Guile has its own official object system we'll convert. Note that
+; it currently does not.]
+; - except for a few exceptions, public procs begin with one of
+; class-, object-, elm-, method-.
+; The exceptions are make, new, parent, send.
+
+(define -class-tag "class")
+(define -object-tag "object")
+
+; List of all classes.
+
+(define -class-list ())
+
+; ??? Were written as a procedures for Hobbit's sake (I think).
+(define -object-unspecified #:unspecified)
+(define -object-unbound #:unbound)
+
+; Associative list of classes to be traced.
+
+(define -object-debug-classes #f)
+
+; Associative list of elements to be traced.
+
+(define -object-debug-elements #f)
+
+; Associative list of messages to be traced.
+
+(define -object-debug-methods #f)
+
+; True if error messages are verbose and debugging messages are printed.
+
+(define -object-verbose? #f)
+
+; Cover fn to set verbosity.
+
+(define (object-set-verbose! verbose?)
+ (set! -object-verbose? verbose?)
+)
+
+; Signal error if not class/object.
+
+(define (-class-check maybe-class proc-name . extra-text)
+ (if (not (class? maybe-class))
+ (apply -object-error
+ (append! (list proc-name maybe-class "not a class")
+ extra-text)))
+ -object-unspecified
+)
+(define (-object-check-name maybe-name proc-name . extra-text)
+ (if (not (symbol? maybe-name))
+ (apply -object-error
+ (append! (list proc-name maybe-name) extra-text)))
+ -object-unspecified
+)
+(define (-object-check maybe-object proc-name . extra-text)
+ (if (not (object? maybe-object))
+ (apply -object-error
+ (append! (list proc-name maybe-object "not an object")
+ extra-text)))
+ -object-unspecified
+)
+
+; X is any arbitrary Scheme data.
+(define (-object-error proc-name x . text)
+ (error (string-append proc-name ": " (apply string-append text)
+ (if (object? x)
+ (string-append
+ " (class: " (-object-class-name x)
+ (if (method-present? x 'get-name)
+ (string-append ", name: "
+ (send x 'get-name))
+ "")
+ ")")
+ "")
+ "")
+ x)
+)
+
+; Low level class operations.
+
+; Return boolean indicating if X is a class.
+
+(define (class? class)
+ (and (vector? class) (eq? -class-tag (vector-ref class 0)))
+)
+
+; Accessors.
+
+(define (-class-name class) (vector-ref class 1))
+(define (-class-parents class) (vector-ref class 2))
+(define (-class-elements class) (vector-ref class 3))
+(define (-class-methods class) (vector-ref class 4))
+(define (-class-all-initial-values class) (vector-ref class 5))
+(define (-class-all-methods class) (vector-ref class 6))
+(define (-class-class-desc class) (vector-ref class 7))
+
+(define (-class-set-parents! class parents)
+ (vector-set! class 2 parents)
+)
+
+(define (-class-set-elements! class elm-alist)
+ (vector-set! class 3 elm-alist)
+)
+
+(define (-class-set-methods! class method-alist)
+ (vector-set! class 4 method-alist)
+)
+
+(define (-class-set-all-initial-values! class init-list)
+ (vector-set! class 5 init-list)
+)
+
+(define (-class-set-all-methods! class all-meth-list)
+ (vector-set! class 6 all-meth-list)
+)
+
+(define (-class-set-class-desc! class parent-list)
+ (vector-set! class 7 parent-list)
+)
+
+; Make a class.
+; The new definition overrides any existing definition.
+
+(define (-class-make! name parents elements methods)
+ (let ((class (vector -class-tag name parents elements methods #f #f #f))
+ (list-entry (assq name -class-list)))
+ (if list-entry
+ (set-cdr! list-entry class)
+ (set! -class-list (acons name class -class-list)))
+ class)
+)
+
+; Lookup a class given its name.
+; The result is the class or #f if not found.
+
+(define (class-lookup name) (assq-ref -class-list name))
+
+; Return a list of all direct parent classes of CLASS.
+
+(define (-class-parent-classes class)
+ ; -class-parents returns the names, we want the actual classes.
+ (let loop ((parents (-class-parents class))
+ (result ()))
+ (if (null? parents)
+ (reverse! result)
+ (let ((parent (class-lookup (car parents))))
+ (if (not parent)
+ ; The proc name we pass here is made up as we don't
+ ; want it to be the name of an internal proc.
+ (-object-error "class" (car parents) "not a class"))
+ (loop (cdr parents) (cons parent result)))))
+)
+
+; Cover proc of -class-name for the outside world to use.
+; The result is the name of the class or #f if CLASS is not a class.
+; We could issue an error here, but to be consistent with object-class-name
+; we don't.
+
+(define (class-name class)
+ (if (class? class)
+ (-class-name class)
+ #f)
+)
+
+; Return a boolean indicating if CLASS or any parent class has
+; multiple inheritance.
+
+(define (-class-mi? class)
+ (-class-desc-mi? (-class-class-desc class))
+)
+
+; Class descriptor utilities.
+; A class-descriptor is:
+; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...)
+
+;(define (-class-desc-make class offset bkptr parents)
+; (append (list class offset bkptr) parents)
+;)
+(define (-class-desc? maybe-class-desc)
+ (and (pair? maybe-class-desc)
+ (class? (car maybe-class-desc)))
+)
+(define -class-desc-class car)
+(define -class-desc-mi? cadr)
+(define -class-desc-offset caddr)
+(define -class-desc-offset-base caaddr)
+(define -class-desc-offset-delta cdaddr)
+(define -class-desc-child cadddr)
+(define -class-desc-parents cddddr)
+; Note that this is an assq on the classes themselves, not their names.
+; The result is the parent's class-descriptor.
+(define -class-desc-lookup-parent assq)
+
+; Compute the class descriptor of CLASS.
+; OFFSET is the beginning offset in the element vector.
+; We can assume the parents of CLASS have already been initialized.
+;
+; A class-descriptor is:
+; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...)
+; MI? is a boolean indicating if multiple inheritance is present.
+; BASE-OFFSET is the offset into the object vector of the baseclass's elements
+; (or first baseclass in the mi case).
+; DELTA is the offset from BASE-OFFSET of the class's own elements.
+; CHILD is the backlink to the direct child class or #f for the top class.
+; ??? Is the use of `top' backwards from traditional usage?
+
+(define (-class-compute-class-desc class offset child)
+
+ ; OFFSET must be global to the calculation because it is continually
+ ; incremented as we recurse down through the hierarchy (actually, as we
+ ; traverse back up). At any point in time it is the offset from the start
+ ; of the element vector of the next class's elements.
+ ; Object elements are laid out using a depth first traversal of the
+ ; inheritance tree.
+
+ (define (compute1 class child base-offset)
+
+ ; Build the result first, then build our parents so that our parents have
+ ; the right value for the CHILD-BACKPOINTER field.
+ ; Use a bogus value for mi? and offset for the moment.
+ ; The correct values are set later.
+
+ (let ((result (list class #f (cons 999 999) child))
+ (mi? (> (length (-class-parents class)) 1)))
+
+ ; Recurse on the parents.
+ ; We use `append!' here as the location of `result' is now fixed so
+ ; that our parent's child-backpointer remains stable.
+
+ (append! result
+ (let loop ((parents (-class-parents class))
+ (parent-descs ())
+ (base-offset base-offset))
+ (if (null? parents)
+ (reverse! parent-descs)
+ (let ((parent (class-lookup (car parents))))
+ (if (not parent)
+ ; The proc name we pass here is made up as we don't
+ ; want it to be the name of an internal proc.
+ (-object-error "class" (car parents) "not a class"))
+ (if (and (not mi?)
+ (-class-mi? parent))
+ (set! mi? #t))
+ (let ((parent-desc (compute1 parent result base-offset)))
+ (loop (cdr parents)
+ (cons parent-desc parent-descs)
+ offset))))))
+
+ (list-set! result 1 mi?)
+ (list-set! result 2 (cons base-offset (- offset base-offset)))
+ (set! offset (+ offset (length (-class-elements class))))
+ result))
+
+ (compute1 class child offset)
+)
+
+; Return the top level class-descriptor of CLASS-DESC.
+
+(define (-class-desc-top class-desc)
+ (if (-class-desc-child class-desc)
+ (-class-desc-top (-class-desc-child class-desc))
+ class-desc)
+)
+
+; Pretty print a class descriptor.
+
+(define (class-desc-dump class-desc)
+ (let* ((cep (current-error-port))
+ (top-desc (-class-desc-top class-desc))
+ (spaces (lambda (n port)
+ (display (make-string n #\space) port)))
+ (writeln (lambda (indent port . args)
+ (spaces indent port)
+ (for-each (lambda (arg) (display arg port))
+ args)
+ (newline port)))
+ )
+ (letrec ((dump (lambda (cd indent)
+ (writeln indent cep "Class: "
+ (-class-name (-class-desc-class cd)))
+ (writeln indent cep " mi?: "
+ (-class-desc-mi? cd))
+ (writeln indent cep " base offset: "
+ (-class-desc-offset-base cd))
+ (writeln indent cep " delta: "
+ (-class-desc-offset-delta cd))
+ (writeln indent cep " child: "
+ (if (-class-desc-child cd)
+ (-class-name (-class-desc-class
+ (-class-desc-child cd)))
+ "-top-"))
+ (for-each (lambda (parent-cd) (dump parent-cd (+ indent 4)))
+ (-class-desc-parents cd))
+ )))
+ (display "Top level class: " cep)
+ (display (-class-name (-class-desc-class top-desc)) cep)
+ (newline cep)
+ (dump class-desc 0)
+ ))
+)
+
+; Low level object utilities.
+
+; Make an object.
+; All elements get initial (or unbound) values.
+
+(define (-object-make! class)
+ (-class-check-init! class)
+ (vector (apply vector (append! (list -object-tag class)
+ (-class-all-initial-values class)))
+ (-class-class-desc class))
+)
+
+; Make an object using VALUES.
+; VALUES must specify all elements in the class (and parent classes).
+
+(define (-object-make-with-values! class class-desc values)
+ (-class-check-init! class)
+ (vector (apply vector (append! (list -object-tag class) values))
+ class-desc)
+)
+
+; Copy an object.
+; If TOP?, the copy is of the top level object with any specialization
+; discarded.
+; WARNING: A shallow copy is currently done on the elements!
+
+(define (-object-copy obj top?)
+ (if top?
+ (vector (-object-vector-copy (-object-elements obj))
+ (-class-class-desc (-object-top-class obj)))
+ (vector (-object-vector-copy (-object-elements obj))
+ (-object-class-desc obj)))
+)
+
+; Specialize an object to be one from a parent class.
+; The result is the same object, but with a different view (confined to
+; a particular parent class).
+
+(define (-object-specialize obj class-desc)
+ (vector (-object-elements obj) class-desc)
+)
+
+; Accessors.
+
+(define (-object-elements obj) (vector-ref obj 0))
+(define (-object-class-desc obj) (vector-ref obj 1))
+(define (-object-class obj) (-class-desc-class (-object-class-desc obj)))
+(define (-object-class-name obj) (-class-name (-object-class obj)))
+(define (-object-top-class obj) (vector-ref (-object-elements obj) 1))
+
+(define (-object-elm-get obj class-desc elm-base-offset)
+ (vector-ref (-object-elements obj)
+ (+ (-class-desc-offset-base class-desc) elm-base-offset))
+)
+
+(define (-object-elm-set! obj class-desc elm-base-offset new-val)
+ (vector-set! (-object-elements obj)
+ (+ (-class-desc-offset-base class-desc) elm-base-offset)
+ new-val)
+ -object-unspecified
+)
+
+; Return a boolean indicating of OBJ has multiple-inheritance.
+
+(define (-object-mi? obj)
+ (-class-mi? (-object-top-class obj))
+)
+
+; Return boolean indicating if X is an object.
+
+(define (object? obj)
+ (and (vector? obj)
+ (= (vector-length obj) 2)
+ (vector? (vector-ref obj 0))
+ (eq? -object-tag (vector-ref (vector-ref obj 0) 0))
+ (-class-desc? (vector-ref obj 1)))
+)
+
+; Return the class of an object.
+
+(define (object-class obj)
+ (-object-check obj "object-class")
+ (-object-class obj)
+)
+
+; Cover proc of -object-class-name for the outside world to use.
+; The result is the name of the class or #f if OBJ is not an object.
+
+(define (object-class-name obj)
+ (if (object? obj)
+ (-object-class-name obj)
+ #f)
+)
+
+; Class operations.
+
+; Return the list of initial values for CLASS.
+; The result does not include parent classes.
+
+(define (-class-my-initial-values class)
+ (map cadr (-class-elements class))
+)
+
+; Initialize class if not already done.
+; FIXME: Need circularity check. Later.
+
+(define (-class-check-init! class)
+ ; This should be fast the second time through, so don't do any
+ ; computation until we know it's necessary.
+
+ (if (not (-class-all-initial-values class))
+
+ (begin
+
+ ; First pass ensures all parents are initialized.
+ (for-each -class-check-init!
+ (-class-parent-classes class))
+
+ ; Next pass initializes the initial value list.
+ (letrec ((get-inits
+ (lambda (class)
+ (let ((parents (-class-parent-classes class)))
+ (append (apply append (map get-inits parents))
+ (-class-my-initial-values class))))))
+
+ (let* ((parents (-class-parent-classes class))
+ (inits (append (apply append (map get-inits parents))
+ (-class-my-initial-values class))))
+ (-class-set-all-initial-values! class inits)))
+
+ ; Next pass initializes the class's class-descriptor.
+ ; Object elements begin at offset 2 in the element vector.
+ (-class-set-class-desc! class
+ (-class-compute-class-desc class 2 #f))
+ ))
+
+ -object-unspecified
+)
+
+; Make a class.
+;
+; PARENTS is a list of names of parent classes. The parents need not
+; exist yet, though they must exist when the class is first instantiated.
+; ELMS is a either a list of either element names or name/value pairs.
+; Elements without initial values are marked as "unbound".
+; METHODS is an initial alist of methods. More methods can be added with
+; method-make!.
+
+(define (class-make name parents elms methods)
+ (let ((elm-list #f))
+
+ ; Mark elements without initial values as unbound, and
+ ; compute indices into the element vector (relative to the class's
+ ; offset).
+ ; Elements are recorded as (symbol initial-value private? . vector-index)
+ ; FIXME: For now all elements are marked as "public".
+ (let loop ((elm-list-tmp ()) (index 0) (elms elms))
+ (if (null? elms)
+ (set! elm-list (reverse! elm-list-tmp)) ; done
+ (if (pair? (car elms))
+ (loop (acons (caar elms)
+ (cons (cdar elms) (cons #f index))
+ elm-list-tmp)
+ (+ index 1)
+ (cdr elms))
+ (loop (acons (car elms)
+ (cons -object-unbound (cons #f index))
+ elm-list-tmp)
+ (+ index 1)
+ (cdr elms)))))
+
+ (let ((result (-class-make! name parents elm-list methods)))
+
+ ; Create the standard `make!' method.
+ ; The caller can override afterwards if desired.
+ ; Note that if there are any parent classes then we don't know the names
+ ; of all of the elements yet, that is only known after the class has been
+ ; initialized which only happens when the class is first instantiated.
+ ; This method won't be called until that happens though so we're safe.
+ ; This is written without knowledge of the names, it just initializes
+ ; all elements.
+ (method-make! result 'make!
+ (lambda args
+ (let ((self (car args)))
+ ; Ensure exactly all of the elements are provided.
+ (if (not (= (length args)
+ (- (vector-length (-object-elements self)) 1)))
+ (-object-error "make!" "" "wrong number of arguments to method `make!'"))
+ (-object-make-with-values! (-object-top-class self)
+ (-object-class-desc self)
+ (cdr args)))))
+
+ result))
+)
+
+; Create an object of a class CLASS.
+
+(define (new class)
+ (-class-check class "new")
+
+ (if -object-verbose?
+ (display (string-append "Instantiating class " (-class-name class) ".\n")
+ (current-error-port)))
+
+ (-object-make! class)
+)
+
+; Make a copy of OBJ.
+; WARNING: A shallow copy is done on the elements!
+
+(define (object-copy obj)
+ (-object-check obj "object-copy")
+ (-object-copy obj #f)
+)
+
+; Make a copy of OBJ.
+; This makes a copy of top level object, with any specialization discarded.
+; WARNING: A shallow copy is done on the elements!
+
+(define (object-copy-top obj)
+ (-object-check obj "object-copy-top")
+ (-object-copy obj #t)
+)
+
+; Utility to define a standard `make!' method.
+; A standard make! method is one in which all it does is initialize
+; fields from args.
+
+(define (method-make-make! class args)
+ (let ((lambda-expr
+ (append (list 'lambda (cons 'self args))
+ (map (lambda (elm) (list 'elm-set! 'self
+ (list 'quote elm) elm))
+ args)
+ '(self))))
+ (method-make! class 'make! (eval lambda-expr))
+ )
+)
+
+; The "standard" way to invoke `make!' is (send (new class) 'make! ...).
+; This puts all that in a cover function.
+
+(define (make class . operands)
+ (apply send (append (cons (new class) ()) '(make!) operands))
+)
+
+; Return #t if class X is a subclass of BASE-NAME.
+
+(define (-class-subclass? base-name x)
+ (if (eq? base-name (-class-name x))
+ #t
+ (let loop ((parents (-class-parents x)))
+ (if (null? parents)
+ #f
+ (if (-class-subclass? base-name (class-lookup (car parents)))
+ #t
+ (loop (cdr parents))))))
+)
+
+; Return #t if OBJECT is an instance of CLASS.
+; This does not signal an error if OBJECT is not an object as this is
+; intended to be used in class predicates.
+
+(define (class-instance? class object)
+ (-class-check class "class-instance?")
+ (if (object? object)
+ (-class-subclass? (-class-name class) (-object-class object))
+ #f)
+)
+
+; Element operations.
+
+; Lookup an element in a class-desc.
+; The result is (class-desc . (private? . elm-offset)) or #f if not found.
+; ??? We could define accessors of the result but knowledge of its format
+; is restricted to this section of the source.
+
+(define (-class-lookup-element class-desc elm-name)
+ (let* ((class (-class-desc-class class-desc))
+ (elm (assq elm-name (-class-elements class))))
+ (if elm
+ (cons class-desc (cddr elm))
+ (let loop ((parents (-class-desc-parents class-desc)))
+ (if (null? parents)
+ #f
+ (let ((elm (-class-lookup-element (car parents) elm-name)))
+ (if elm
+ elm
+ (loop (cdr parents)))))
+ ))
+ )
+)
+
+; Given the result of -class-lookup-element, return the element's delta
+; from base-offset.
+
+(define (-elm-delta index)
+ (+ (-class-desc-offset-delta (car index))
+ (cddr index))
+)
+
+; Return a boolean indicating if ELM is bound in OBJ.
+
+(define (elm-bound? obj elm)
+ (-object-check obj "elm-bound?")
+ (let* ((index (-class-lookup-element (-object-class-desc obj) elm))
+ (val (-object-elm-get obj (car index) (-elm-delta index))))
+ (not (eq? val -object-unbound)))
+)
+
+; Subroutine of elm-get.
+
+(define (-elm-make-method-getter self name)
+ (-object-check self "elm-get")
+ (let ((index (-class-lookup-element (-object-class-desc self) name)))
+ (if index
+ (procedure->memoizing-macro
+ (lambda (exp env)
+ `(lambda (obj)
+ (-object-elm-get obj (-object-class-desc obj)
+ ,(-elm-delta index)))))
+ (-object-error "elm-get" self "element not present: " name)))
+)
+
+; Get an element from an object.
+; If OBJ is `self' then the caller is required to be a method and we emit
+; memoized code. Otherwise we do things the slow way.
+; ??? There must be a better way.
+; What this does is turn
+; (elm-get self 'foo)
+; into
+; ((-elm-make-method-get self 'foo) self)
+; Note the extra set of parens. -elm-make-method-get then does the lookup of
+; foo and returns a memoizing macro that returns the code to perform the
+; operation with O(1). Cute, but I'm hoping there's an easier/better way.
+
+(defmacro elm-get (self name)
+ (if (eq? self 'self)
+ `(((-elm-make-method-getter ,self ,name)) ,self)
+ `(elm-xget ,self ,name))
+)
+
+; Subroutine of elm-set!.
+
+(define (-elm-make-method-setter self name)
+ (-object-check self "elm-set!")
+ (let ((index (-class-lookup-element (-object-class-desc self) name)))
+ (if index
+ (procedure->memoizing-macro
+ (lambda (exp env)
+ `(lambda (obj new-val)
+ (-object-elm-set! obj (-object-class-desc obj)
+ ,(-elm-delta index) new-val))))
+ (-object-error "elm-set!" self "element not present: " name)))
+)
+
+; Set an element in an object.
+; This can only be used by methods.
+; See the comments for `elm-get'!
+
+(defmacro elm-set! (self name new-val)
+ (if (eq? self 'self)
+ `(((-elm-make-method-setter ,self ,name)) ,self ,new-val)
+ `(elm-xset! ,self ,name ,new-val))
+)
+
+; Get an element from an object.
+; This is for invoking from outside a method, and without having to
+; use elm-make-getter. It should be used sparingly.
+
+(define (elm-xget obj name)
+ (-object-check obj "elm-xget")
+ (let ((index (-class-lookup-element (-object-class-desc obj) name)))
+ ; FIXME: check private?
+ (if index
+ (-object-elm-get obj (car index) (-elm-delta index))
+ (-object-error "elm-xget" obj "element not present: " name)))
+)
+
+; Set an element in an object.
+; This is for invoking from outside a method, and without having to
+; use elm-make-setter. It should be used sparingly.
+
+(define (elm-xset! obj name new-val)
+ (-object-check obj "elm-xset!")
+ (let ((index (-class-lookup-element (-object-class-desc obj) name)))
+ ; FIXME: check private?
+ (if index
+ (-object-elm-set! obj (car index) (-elm-delta index) new-val)
+ (-object-error "elm-xset!" obj "element not present: " name)))
+)
+
+; Return a boolean indicating if object OBJ has element NAME.
+
+(define (elm-present? obj name)
+ (-object-check obj "elm-present?")
+ (->bool (-class-lookup-element (-object-class-desc obj) name))
+)
+
+; Return lambda to get element NAME in CLASS.
+; FIXME: validate name.
+
+(define (elm-make-getter class name)
+ (-class-check class "elm-make-getter")
+ ; We use delay here as we can't assume parent classes have been
+ ; initialized yet.
+ (let ((fast-index (delay (-class-lookup-element
+ (-class-class-desc class) name))))
+ (lambda (obj)
+ ; ??? Should be able to use fast-index in mi case.
+ ; ??? Need to involve CLASS in lookup.
+ (let ((index (if (-object-mi? obj)
+ (-class-lookup-element (-object-class-desc obj) name)
+ (force fast-index))))
+ (-object-elm-get obj (car index) (-elm-delta index)))))
+)
+
+; Return lambda to set element NAME in CLASS.
+; FIXME: validate name.
+
+(define (elm-make-setter class name)
+ (-class-check class "elm-make-setter")
+ ; We use delay here as we can't assume parent classes have been
+ ; initialized yet.
+ (let ((fast-index (delay (-class-lookup-element
+ (-class-class-desc class) name))))
+ (lambda (obj newval)
+ ; ??? Should be able to use fast-index in mi case.
+ ; ??? Need to involve CLASS in lookup.
+ (let ((index (if (-object-mi? obj)
+ (-class-lookup-element (-object-class-desc obj) name)
+ (force fast-index))))
+ (-object-elm-set! obj (car index) (-elm-delta index) newval))))
+)
+
+; Return a list of all elements in OBJ.
+
+(define (elm-list obj)
+ (cddr (vector->list (-object-elements obj)))
+)
+
+; Method operations.
+
+; Lookup the next method in a class.
+; This means begin the search in the parents.
+; ??? What should this do for virtual methods. At present we treat them as
+; non-virtual.
+
+(define (-method-lookup-next class-desc method-name)
+ (let loop ((parents (-class-desc-parents class-desc)))
+ (if (null? parents)
+ #f
+ (let ((meth (-method-lookup (car parents) method-name #f)))
+ (if meth
+ meth
+ (loop (cdr parents))))))
+)
+
+; Lookup a method in a class.
+; The result is (class-desc . method). If the method is found in a parent
+; class, the associated parent class descriptor is returned. If the method is
+; a virtual method, the appropriate subclass's class descriptor is returned.
+; VIRTUAL? is #t if virtual methods are to be treated as such.
+; Otherwise they're treated as normal methods.
+;
+; FIXME: We don't yet implement the method cache.
+
+(define (-method-lookup class-desc method-name virtual?)
+ (if -object-verbose?
+ (display (string-append "Looking up method " method-name " in "
+ (-class-name (-class-desc-class class-desc)) ".\n")
+ (current-error-port)))
+
+ (let ((meth (assq method-name (-class-methods (-class-desc-class class-desc)))))
+ (if meth
+ (if (and virtual? (cadr meth)) ; virtual?
+ ; Traverse back up the inheritance chain looking for overriding
+ ; methods. The closest one to the top is the one to use.
+ (let loop ((child (-class-desc-child class-desc))
+ (goal-class-desc class-desc)
+ (goal-meth meth))
+ (if child
+ (begin
+ (if -object-verbose?
+ (display (string-append "Looking up virtual method "
+ method-name " in "
+ (-class-name (-class-desc-class child))
+ ".\n")
+ (current-error-port)))
+ (let ((meth (assq method-name (-class-methods (-class-desc-class child)))))
+ (if meth
+ ; Method found, update goal object and method.
+ (loop (-class-desc-child child) child meth)
+ ; Method not found at this level.
+ (loop (-class-desc-child child) goal-class-desc goal-meth))))
+ ; Went all the way up to the top.
+ (cons goal-class-desc (cddr goal-meth))))
+ ; Non-virtual, done.
+ (cons class-desc (cddr meth)))
+ ; Method not found, search parents.
+ (-method-lookup-next class-desc method-name)))
+)
+
+; Return a boolean indicating if object OBJ has method NAME.
+
+(define (method-present? obj name)
+ (-object-check obj "method-present?")
+ (->bool (-method-lookup (-object-class-desc obj) name #f))
+)
+
+; Return method NAME of CLASS or #f if not present.
+; ??? Assumes CLASS has been initialized.
+
+(define (method-proc class name)
+ (-class-check class "method-proc")
+ (let ((meth (-method-lookup (-class-class-desc class) name #t)))
+ (if meth
+ (cdr meth)
+ #f))
+)
+
+; Add a method to a class.
+; FIXME: ensure method-name is a symbol
+
+(define (method-make! class method-name method)
+ (-class-check class "method-make!")
+ (if (not (procedure? method))
+ (-object-error "method-make!" method "method must be a procedure"))
+ (-class-set-methods! class (acons method-name
+ (cons #f method)
+ (-class-methods class)))
+ -object-unspecified
+)
+
+; Add a virtual method to a class.
+; FIXME: ensure method-name is a symbol
+
+(define (method-make-virtual! class method-name method)
+ (-class-check class "method-make-virtual!")
+ (if (not (procedure? method))
+ (-object-error "method-make-virtual!" method "method must be a procedure"))
+ (-class-set-methods! class (acons method-name
+ (cons #t method)
+ (-class-methods class)))
+ -object-unspecified
+)
+
+; Utility to create "forwarding" methods.
+; METHODS are forwarded to class member ELM-NAME, assumed to be an object.
+; The created methods take a variable number of arguments.
+; Argument length checking will be done by the receiving method.
+; FIXME: ensure elm-name is a symbol
+
+(define (method-make-forward! class elm-name methods)
+ (for-each (lambda (method-name)
+ (method-make!
+ class method-name
+ (eval `(lambda args
+ (apply send
+ (cons (elm-get (car args)
+ (quote ,elm-name))
+ (cons (quote ,method-name)
+ (cdr args))))))))
+ methods)
+ -object-unspecified
+)
+
+; Same as method-make-forward! but creates virtual methods.
+; FIXME: ensure elm-name is a symbol
+
+(define (method-make-virtual-forward! class elm-name methods)
+ (for-each (lambda (method-name)
+ (method-make-virtual!
+ class method-name
+ (eval `(lambda args
+ (apply send
+ (cons (elm-get (car args)
+ (quote ,elm-name))
+ (cons (quote ,method-name)
+ (cdr args))))))))
+ methods)
+ -object-unspecified
+)
+
+; Utility of send, send-next.
+
+(define (-object-method-notify obj method-name maybe-next)
+ (set! -object-verbose? #f)
+ (display (string-append "Sending " maybe-next method-name " to"
+ (if (method-present? obj 'get-name)
+ (let ((name (send obj 'get-name)))
+ (if (or (symbol? name) (string? name))
+ (string-append " object " name)
+ ""))
+ "")
+ " class " (object-class-name obj) ".\n")
+ (current-error-port))
+ (set! -object-verbose? #t)
+)
+
+; Invoke a method in an object.
+; When the method is invoked, the (possible parent class) object in which the
+; method is found is passed to the method.
+; ??? The word `send' comes from "sending messages". Perhaps should pick
+; a better name for this operation.
+
+(define (send obj method-name . args)
+ (-object-check obj "send")
+ (-object-check-name method-name "send" "not a method name")
+ (if -object-verbose? (-object-method-notify obj method-name ""))
+
+ (let ((class-desc.meth (-method-lookup (-object-class-desc obj)
+ method-name #t)))
+ (if class-desc.meth
+ (apply (cdr class-desc.meth)
+ (cons (-object-specialize obj (car class-desc.meth))
+ args))
+ (-object-error "send" obj "method not supported: " method-name)))
+)
+
+; Invoke the next method named METHOD-NAME in the heirarchy of OBJ.
+; i.e. the method that would have been invoked if the calling method
+; didn't exist.
+; This may only be called by a method.
+; ??? Ideally we shouldn't need the METHOD-NAME argument. It could be
+; removed with a bit of effort, but is it worth it?
+
+(define (send-next obj method-name . args)
+ (-object-check obj "send-next")
+ (-object-check-name method-name "send-next" "not a method name")
+ (if -object-verbose? (-object-method-notify obj method-name "next "))
+
+ (let ((class-desc.meth (-method-lookup-next (-object-class-desc obj)
+ method-name)))
+ (if class-desc.meth
+ (apply (cdr class-desc.meth)
+ (cons (-object-specialize obj (car class-desc.meth))
+ args))
+ (-object-error "send-next" obj "method not supported: " method-name)))
+)
+
+; Parent operations.
+
+; Subroutine of `parent' to lookup a (potentially nested) parent class.
+; The result is the parent's class-descriptor or #f if not found.
+
+(define (-class-parent class-desc parent)
+ (let* ((parent-descs (-class-desc-parents class-desc))
+ (desc (-class-desc-lookup-parent parent parent-descs)))
+ (if desc
+ desc
+ (let loop ((parents parent-descs))
+ (if (null? parents)
+ #f
+ (let ((desc (-class-parent (car parents) parent)))
+ (if desc
+ desc
+ (loop (cdr parents))))))))
+)
+
+; Subroutine of `parent' to lookup a parent via a path.
+; PARENT-PATH, a list, is the exact path to the parent class.
+; The result is the parent's class-descriptor or #f if not found.
+; For completeness' sake, if PARENT-PATH is empty, CLASS-DESC is returned.
+
+(define (-class-parent-via-path class-desc parent-path)
+ (if (null? parent-path)
+ class-desc
+ (let ((desc (-class-desc-lookup-parent (car parent-path)
+ (-class-desc-parents class-desc))))
+ (if desc
+ (if (null? (cdr parent-path))
+ desc
+ (-class-parent-via-path (car desc) (cdr parent-path)))
+ #f)))
+)
+
+; Lookup a parent class of object OBJ.
+; CLASS is either a class or a list of classes.
+; If CLASS is a list, it is a (possibly empty) "path" to the parent.
+; Otherwise it is any parent and is searched for breadth-first.
+; ??? Methinks this should be depth-first.
+; The result is OBJ, specialized to the found parent.
+
+(define (object-parent obj class)
+ (-object-check obj "object-parent")
+ (cond ((class? class) #t)
+ ((list? class) (for-each (lambda (class) (-class-check class
+ "object-parent"))
+ class))
+ (else (-object-error "object-parent" class "invalid parent path")))
+
+ ; Hobbit generates C code that passes the function
+ ; -class-parent-via-path or -class-parent, not the appropriate
+ ; SCM object.
+; (let ((result ((if (or (null? class) (pair? class))
+; -class-parent-via-path
+; -class-parent)
+; obj class)))
+ ; So it's rewritten like this.
+ (let ((result (if (class? class)
+ (-class-parent (-object-class-desc obj) class)
+ (-class-parent-via-path (-object-class-desc obj) class))))
+ (if result
+ (-object-specialize obj result)
+ (-object-error "object-parent" obj "parent not present")))
+ ; FIXME: should print path in error message.
+)
+
+; Make PARENT-NAME a parent of CLASS, cons'd unto the front of the search
+; order. This is used to add a parent class to a class after it has already
+; been created. Obviously this isn't something one does willy-nilly.
+; The parent is added to the front of the current parent list (affects
+; method lookup).
+
+(define (class-cons-parent! class parent-name)
+ (-class-check class "class-cons-parent!")
+ (-object-check-name parent-name "class-cons-parent!" "not a class name")
+ (-class-set-parents! class (cons parent-name (-class-parents class)))
+ -object-unspecified
+)
+
+; Make PARENT-NAME a parent of CLASS, cons'd unto the end of the search order.
+; This is used to add a parent class to a class after it has already been
+; created. Obviously this isn't something one does willy-nilly.
+; The parent is added to the end of the current parent list (affects
+; method lookup).
+
+(define (class-append-parent! class parent-name)
+ (-class-check class "class-append-parent!")
+ (-object-check-name parent-name "class-append-parent!" "not a class name")
+ (-class-set-parents! obj (append (-class-parents obj) (list parent-name)))
+ -object-unspecified
+)
+
+; Miscellaneous publically accessible utilities.
+
+; Reset the object system (delete all classes).
+
+(define (object-reset!)
+ (set! -class-list ())
+ -object-unspecified
+)
+
+; Call once to initialize the object system.
+; Only necessary if classes have been modified after objects have been
+; instantiated. This usually happens during development only.
+
+(define (object-init!)
+ (for-each (lambda (class)
+ (-class-set-all-initial-values! class #f)
+ (-class-set-all-methods! class #f)
+ (-class-set-class-desc! class #f))
+ (class-list))
+ (for-each (lambda (class)
+ (-class-check-init! class))
+ (class-list))
+ -object-unspecified
+)
+
+; Return list of all classes.
+
+(define (class-list) (map cdr -class-list))
+
+; Utility to map over a class and all its parent classes, recursively.
+
+(define (class-map-over-class proc class)
+ (cons (proc class)
+ (map (lambda (class) (class-map-over-class proc class))
+ (-class-parent-classes class)))
+)
+
+; Return class tree of a class or object.
+
+(define (class-tree class-or-object)
+ (cond ((class? class-or-object)
+ (class-map-over-class class-name class-or-object))
+ ((object? class-or-object)
+ (class-map-over-class class-name (-object-class class-or-object)))
+ (else (-object-error "class-tree" class-or-object
+ "not a class or object")))
+)
+
+; Return names of each alist.
+
+(define (-class-alist-names class)
+ (list (-class-name class)
+ (map car (-class-elements class))
+ (map car (-class-methods class)))
+)
+
+; Return complete layout of class-or-object.
+
+(define (class-layout class-or-object)
+ (cond ((class? class-or-object)
+ (class-map-over-class -class-alist-names class-or-object))
+ ((object? class-or-object)
+ (class-map-over-class -class-alist-names (-object-class class-or-object)))
+ (else (-object-error "class-layout" class-or-object
+ "not a class or object")))
+)
+
+; Like assq but based on the `name' element.
+; WARNING: Slow.
+
+(define (object-assq name obj-list)
+ (find-first (lambda (o) (eq? (elm-xget o 'name) name))
+ obj-list)
+)
+
+; Like memq but based on the `name' element.
+; WARNING: Slow.
+
+(define (object-memq name obj-list)
+ (let loop ((r obj-list))
+ (cond ((null? r) #f)
+ ((eq? name (elm-xget (car r) 'name)) r)
+ (else (loop (cdr r)))))
+)
+
+; Misc. internal utilities.
+
+; We need a fast vector copy operation.
+; If `vector-copy' doesn't exist (which is assumed to be the fast one),
+; provide a simple version.
+; FIXME: Need deep copier instead.
+
+(if (defined? 'vector-copy)
+ (define -object-vector-copy vector-copy)
+ (define (-object-vector-copy v) (list->vector (vector->list v)))
+)
+
+; Profiling support
+
+(if (and #f (defined? 'proc-profile))
+ (begin
+ (proc-profile elm-get)
+ (proc-profile elm-set!)
+ (proc-profile elm-present?)
+ (proc-profile -method-lookup)
+ (proc-profile send)
+ (proc-profile new)
+ (proc-profile make)
+ ))