1 ; Cgen's Object System.
2 ; Copyright (C) 2000 Red Hat, Inc.
3 ; This file is part of CGEN.
4 ; See file COPYING.CGEN for details.
6 ; When Guile has an official object implementation that is stable, things will
7 ; be switched over then. Until such time, there's no point in getting hyper
8 ; (although doing so is certainly fun, but only to a point).
9 ; If the Guile team decides there won't be any official object system
10 ; (which isn't unreasonable) then we'll pick the final object system then.
11 ; Until such time, there are better things to do than trying to build a
12 ; better object system. If this is important enough to you, help the Guile
13 ; team finish the module(/object?) system.
22 ; full-elm-initial-list
23 ; full-method-alist ; ??? not currently used
26 ; PARENT-NAME-LIST is a list of the names of parent classes (the inheritance
29 ; ELM-ALIST is an alist of (symbol private? vector-index . initial-value)
30 ; for this class only.
31 ; Values can be looked up by name, via elm-make-[gs]etter routines, or
32 ; methods can use elm-get/set! for speed.
33 ; Various Lisp (or Lisp-like) OOP systems (e.g. CLOS, Dylan) call these
34 ; "slots". Maybe for consistency "slot" would be a better name. Some might
35 ; confuse that with intentions at directions. Given that something better
36 ; will eventually happen, being deliberately different is useful.
38 ; METHOD-ALIST is an alist of (symbol . (virtual? . procedure)) for this
41 ; FULL-ELM-INITIAL-LIST is the elements of the flattened inheritance tree.
42 ; Initially it is #f meaning it hasn't been computed yet.
43 ; It is computed when the class is first instantiated. During development,
44 ; it can be reset to #f after some module has been reloaded (requires all
45 ; object instantiation happens later of course).
47 ; FULL-METHOD-ALIST is an alist of the methods of the flattened inheritance
48 ; tree. Each element is (symbol . (parent-list-entry . method)).
49 ; Initially it is #f meaning it hasn't been computed yet.
50 ; It is computed when the class is first instantiated. During development,
51 ; it can be reset to #f after some module has been reloaded (requires all
52 ; object instantiation happens later of course).
54 ; CLASS-DESCRIPTOR is the processed form of parent-name-list.
55 ; There is an entry for the class and one for each parent (recursively):
56 ; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...).
57 ; mi? is #t if the class or any parent class has multiple inheritance.
58 ; This is used by the element access routines.
59 ; base-offset is the offset in the element vector of the baseclass (or first
60 ; baseclass in the mi case).
61 ; delta is the offset from base-offset of the class's own elements
62 ; (as opposed to elements in any parent class).
63 ; child-backpointer is #f in the top level object.
64 ; ??? child->subclass, parent->superclass?
65 ; Initially the class-descriptor is #f meaning it hasn't been computed yet.
66 ; It is computed when the class is first instantiated. During development,
67 ; it can be reset to #f after some module has been reloaded (requires all
68 ; object instantiation to happen later of course).
70 ; An object is a vector of 2 elements: #(object-elements class-descriptor).
71 ; ??? Things would be simpler if objects were a pair but that makes eval'ing
72 ; them trickier. Vectors are nice in that they're self-evaluating, though
73 ; due to the self-referencing, which Guile 1.2 can't handle, apps have to
75 ; ??? We could use smobs/records/whatever but the difference isn't big enough
76 ; for me to care at this point in time.
78 ; `object-elements' looks like:
86 ; CLASS is the class the object is an instance of.
90 ; (class-make name parents elements methods) -> class
92 ; Create a class. The result is then passed back by procedures requiring
93 ; a class argument. Note however that PARENTS is a list of class names,
94 ; not the class data type. This allows reloading the definition of a
95 ; parent class without having to reload any subclasses. To implement this
96 ; classes are recorded internally, and `object-init!' must be called if any
97 ; class has been redefined.
99 ; (class-list) -> list of all defined classes
101 ; (class-name class) -> name of CLASS
103 ; (class-lookup class-name) -> class
105 ; (class-instance? class object) -> #t if OBJECT is an instance of CLASS
107 ; (object-class object) -> class of OBJECT
109 ; (object-class-name object) -> class name of OBJECT
111 ; (send object method-name . args) -> result of invoking METHOD-NAME
113 ; (send-next object method-name . args) -> result of invoking next METHOD-NAME
115 ; (new class) -> instantiate CLASS
117 ; The object is initialized with values specified when CLASS
118 ; (and its parent classes) was defined.
120 ; (vmake class . args) -> instantiate class and initialize it with 'vmake!
122 ; This is shorthand for (send (new class) 'vmake! args).
123 ; ARGS is a list of option names and arguments (a la CLOS).
124 ; ??? Not implemented yet.
126 ; (method-vmake! object . args) -> modify OBJECT from ARGS
128 ; This is the standard 'vmake! method, available for use by user-written
130 ; ??? Not implemented yet.
132 ; (make class . args) -> instantiate CLASS and initialize it with 'make!
134 ; This is shorthand for (send (new class) 'make! arg1 ...).
135 ; This is a positional form of `new'.
137 ; (method-make-make! class elm1-name elm2-name ...) -> unspecified
139 ; Create a 'make! method that sets the specified elements.
141 ; (object-copy object) -> copy of OBJ
143 ; ??? Whether to discard the parent or keep it and retain specialization
146 ; (object-copy-top object) -> copy of OBJECT with spec'n discarded
148 ; (object-parent object parent-path) -> parent object in OBJECT via PARENT-PATH
150 ; (class? foo) -> return #t if FOO is a class
152 ; (object? foo) -> return #t if FOO is an object
154 ; (method-make! class name lambda) -> unspecified
156 ; Add method NAME to CLASS.
158 ; (method-make-virtual! class name lambda) -> unspecified
160 ; Add virtual method NAME to CLASS.
162 ; (method-make-forward! class elm-name methods) -> unspecified
164 ; Add METHODS to CLASS that pass the "message" onto the object in element
167 ; (method-make-virtual-forward! class elm-name methods) -> unspecified
169 ; Add virtual METHODS to CLASS that pass the "message" onto the object in
172 ; (elm-get object elm-name) -> value of element ELM-NAME in OBJ
174 ; Can only be used in methods.
176 ; (elm-set! object elm-name new-value) -> unspecified
178 ; Set element ELM-NAME in OBJECT to NEW-VALUE.
179 ; Can only be used in methods.
181 ; (elm-make-getter class elm-name) -> lambda
183 ; Return lambda to get the value of ELM-NAME in CLASS.
185 ; (elm-make-setter class elm-name) -> lambda
187 ; Return lambda to set the value of ELM-NAME in CLASS.
189 ; Conventions used in this file:
190 ; - procs/vars internal to this file are prefixed with "-"
191 ; [Of course this could all be put in a module; later if ever since
192 ; once Guile has its own official object system we'll convert. Note that
193 ; it currently does not.]
194 ; - except for a few exceptions, public procs begin with one of
195 ; class-, object-, elm-, method-.
196 ; The exceptions are make, new, parent, send.
198 (define -class-tag "class")
199 (define -object-tag "object")
201 ; List of all classes.
203 (define -class-list '())
205 ; ??? Were written as a procedures for Hobbit's sake (I think).
206 (define -object-unspecified #:unspecified)
207 (define -object-unbound #:unbound)
209 ; Associative list of classes to be traced.
211 (define -object-debug-classes #f)
213 ; Associative list of elements to be traced.
215 (define -object-debug-elements #f)
217 ; Associative list of messages to be traced.
219 (define -object-debug-methods #f)
221 ; True if error messages are verbose and debugging messages are printed.
223 (define -object-verbose? #f)
225 ; Cover fn to set verbosity.
227 (define (object-set-verbose! verbose?)
228 (set! -object-verbose? verbose?)
231 ; Signal error if not class/object.
233 (define (-class-check maybe-class proc-name . extra-text)
234 (if (not (class? maybe-class))
236 (append! (list proc-name maybe-class "not a class")
240 (define (-object-check-name maybe-name proc-name . extra-text)
241 (if (not (symbol? maybe-name))
243 (append! (list proc-name maybe-name) extra-text)))
246 (define (-object-check maybe-object proc-name . extra-text)
247 (if (not (object? maybe-object))
249 (append! (list proc-name maybe-object "not an object")
254 ; X is any arbitrary Scheme data.
255 (define (-object-error proc-name x . text)
256 (error (string-append proc-name ": " (apply string-append text)
259 " (class: " (-object-class-name x)
260 (if (method-present? x 'get-name)
261 (string-append ", name: "
270 ; Low level class operations.
272 ; Return boolean indicating if X is a class.
274 (define (class? class)
275 (and (vector? class) (eq? -class-tag (vector-ref class 0)))
280 (define (-class-name class) (vector-ref class 1))
281 (define (-class-parents class) (vector-ref class 2))
282 (define (-class-elements class) (vector-ref class 3))
283 (define (-class-methods class) (vector-ref class 4))
284 (define (-class-all-initial-values class) (vector-ref class 5))
285 (define (-class-all-methods class) (vector-ref class 6))
286 (define (-class-class-desc class) (vector-ref class 7))
288 (define (-class-set-parents! class parents)
289 (vector-set! class 2 parents)
292 (define (-class-set-elements! class elm-alist)
293 (vector-set! class 3 elm-alist)
296 (define (-class-set-methods! class method-alist)
297 (vector-set! class 4 method-alist)
300 (define (-class-set-all-initial-values! class init-list)
301 (vector-set! class 5 init-list)
304 (define (-class-set-all-methods! class all-meth-list)
305 (vector-set! class 6 all-meth-list)
308 (define (-class-set-class-desc! class parent-list)
309 (vector-set! class 7 parent-list)
313 ; The new definition overrides any existing definition.
315 (define (-class-make! name parents elements methods)
316 (let ((class (vector -class-tag name parents elements methods #f #f #f))
317 (list-entry (assq name -class-list)))
319 (set-cdr! list-entry class)
320 (set! -class-list (acons name class -class-list)))
324 ; Lookup a class given its name.
325 ; The result is the class or #f if not found.
327 (define (class-lookup name) (assq-ref -class-list name))
329 ; Return a list of all direct parent classes of CLASS.
331 (define (-class-parent-classes class)
332 ; -class-parents returns the names, we want the actual classes.
333 (let loop ((parents (-class-parents class))
337 (let ((parent (class-lookup (car parents))))
339 ; The proc name we pass here is made up as we don't
340 ; want it to be the name of an internal proc.
341 (-object-error "class" (car parents) "not a class"))
342 (loop (cdr parents) (cons parent result)))))
345 ; Cover proc of -class-name for the outside world to use.
346 ; The result is the name of the class or #f if CLASS is not a class.
347 ; We could issue an error here, but to be consistent with object-class-name
350 (define (class-name class)
356 ; Return a boolean indicating if CLASS or any parent class has
357 ; multiple inheritance.
359 (define (-class-mi? class)
360 (-class-desc-mi? (-class-class-desc class))
363 ; Class descriptor utilities.
364 ; A class-descriptor is:
365 ; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...)
367 ;(define (-class-desc-make class offset bkptr parents)
368 ; (append (list class offset bkptr) parents)
370 (define (-class-desc? maybe-class-desc)
371 (and (pair? maybe-class-desc)
372 (class? (car maybe-class-desc)))
374 (define -class-desc-class car)
375 (define -class-desc-mi? cadr)
376 (define -class-desc-offset caddr)
377 (define -class-desc-offset-base caaddr)
378 (define -class-desc-offset-delta cdaddr)
379 (define -class-desc-child cadddr)
380 (define -class-desc-parents cddddr)
381 ; Note that this is an assq on the classes themselves, not their names.
382 ; The result is the parent's class-descriptor.
383 (define -class-desc-lookup-parent assq)
385 ; Compute the class descriptor of CLASS.
386 ; OFFSET is the beginning offset in the element vector.
387 ; We can assume the parents of CLASS have already been initialized.
389 ; A class-descriptor is:
390 ; (class mi? (base-offset . delta) child-backpointer (parent1-entry) ...)
391 ; MI? is a boolean indicating if multiple inheritance is present.
392 ; BASE-OFFSET is the offset into the object vector of the baseclass's elements
393 ; (or first baseclass in the mi case).
394 ; DELTA is the offset from BASE-OFFSET of the class's own elements.
395 ; CHILD is the backlink to the direct child class or #f for the top class.
396 ; ??? Is the use of `top' backwards from traditional usage?
398 (define (-class-compute-class-desc class offset child)
400 ; OFFSET must be global to the calculation because it is continually
401 ; incremented as we recurse down through the hierarchy (actually, as we
402 ; traverse back up). At any point in time it is the offset from the start
403 ; of the element vector of the next class's elements.
404 ; Object elements are laid out using a depth first traversal of the
407 (define (compute1 class child base-offset)
409 ; Build the result first, then build our parents so that our parents have
410 ; the right value for the CHILD-BACKPOINTER field.
411 ; Use a bogus value for mi? and offset for the moment.
412 ; The correct values are set later.
414 (let ((result (list class #f (cons 999 999) child))
415 (mi? (> (length (-class-parents class)) 1)))
417 ; Recurse on the parents.
418 ; We use `append!' here as the location of `result' is now fixed so
419 ; that our parent's child-backpointer remains stable.
422 (let loop ((parents (-class-parents class))
424 (base-offset base-offset))
426 (reverse! parent-descs)
427 (let ((parent (class-lookup (car parents))))
429 ; The proc name we pass here is made up as we don't
430 ; want it to be the name of an internal proc.
431 (-object-error "class" (car parents) "not a class"))
435 (let ((parent-desc (compute1 parent result base-offset)))
437 (cons parent-desc parent-descs)
440 (list-set! result 1 mi?)
441 (list-set! result 2 (cons base-offset (- offset base-offset)))
442 (set! offset (+ offset (length (-class-elements class))))
445 (compute1 class child offset)
448 ; Return the top level class-descriptor of CLASS-DESC.
450 (define (-class-desc-top class-desc)
451 (if (-class-desc-child class-desc)
452 (-class-desc-top (-class-desc-child class-desc))
456 ; Pretty print a class descriptor.
458 (define (class-desc-dump class-desc)
459 (let* ((cep (current-error-port))
460 (top-desc (-class-desc-top class-desc))
461 (spaces (lambda (n port)
462 (display (make-string n #\space) port)))
463 (writeln (lambda (indent port . args)
465 (for-each (lambda (arg) (display arg port))
469 (letrec ((dump (lambda (cd indent)
470 (writeln indent cep "Class: "
471 (-class-name (-class-desc-class cd)))
472 (writeln indent cep " mi?: "
473 (-class-desc-mi? cd))
474 (writeln indent cep " base offset: "
475 (-class-desc-offset-base cd))
476 (writeln indent cep " delta: "
477 (-class-desc-offset-delta cd))
478 (writeln indent cep " child: "
479 (if (-class-desc-child cd)
480 (-class-name (-class-desc-class
481 (-class-desc-child cd)))
483 (for-each (lambda (parent-cd) (dump parent-cd (+ indent 4)))
484 (-class-desc-parents cd))
486 (display "Top level class: " cep)
487 (display (-class-name (-class-desc-class top-desc)) cep)
493 ; Low level object utilities.
496 ; All elements get initial (or unbound) values.
498 (define (-object-make! class)
499 (-class-check-init! class)
500 (vector (apply vector (append! (list -object-tag class)
501 (-class-all-initial-values class)))
502 (-class-class-desc class))
505 ; Make an object using VALUES.
506 ; VALUES must specify all elements in the class (and parent classes).
508 (define (-object-make-with-values! class class-desc values)
509 (-class-check-init! class)
510 (vector (apply vector (append! (list -object-tag class) values))
515 ; If TOP?, the copy is of the top level object with any specialization
517 ; WARNING: A shallow copy is currently done on the elements!
519 (define (-object-copy obj top?)
521 (vector (-object-vector-copy (-object-elements obj))
522 (-class-class-desc (-object-top-class obj)))
523 (vector (-object-vector-copy (-object-elements obj))
524 (-object-class-desc obj)))
527 ; Specialize an object to be one from a parent class.
528 ; The result is the same object, but with a different view (confined to
529 ; a particular parent class).
531 (define (-object-specialize obj class-desc)
532 (vector (-object-elements obj) class-desc)
537 (define (-object-elements obj) (vector-ref obj 0))
538 (define (-object-class-desc obj) (vector-ref obj 1))
539 (define (-object-class obj) (-class-desc-class (-object-class-desc obj)))
540 (define (-object-class-name obj) (-class-name (-object-class obj)))
541 (define (-object-top-class obj) (vector-ref (-object-elements obj) 1))
543 (define (-object-elm-get obj class-desc elm-base-offset)
544 (vector-ref (-object-elements obj)
545 (+ (-class-desc-offset-base class-desc) elm-base-offset))
548 (define (-object-elm-set! obj class-desc elm-base-offset new-val)
549 (vector-set! (-object-elements obj)
550 (+ (-class-desc-offset-base class-desc) elm-base-offset)
555 ; Return a boolean indicating of OBJ has multiple-inheritance.
557 (define (-object-mi? obj)
558 (-class-mi? (-object-top-class obj))
561 ; Return boolean indicating if X is an object.
563 (define (object? obj)
565 (= (vector-length obj) 2)
566 (vector? (vector-ref obj 0))
567 (eq? -object-tag (vector-ref (vector-ref obj 0) 0))
568 (-class-desc? (vector-ref obj 1)))
571 ; Return the class of an object.
573 (define (object-class obj)
574 (-object-check obj "object-class")
578 ; Cover proc of -object-class-name for the outside world to use.
579 ; The result is the name of the class or #f if OBJ is not an object.
581 (define (object-class-name obj)
583 (-object-class-name obj)
589 ; Return the list of initial values for CLASS.
590 ; The result does not include parent classes.
592 (define (-class-my-initial-values class)
593 (map cadr (-class-elements class))
596 ; Initialize class if not already done.
597 ; FIXME: Need circularity check. Later.
599 (define (-class-check-init! class)
600 ; This should be fast the second time through, so don't do any
601 ; computation until we know it's necessary.
603 (if (not (-class-all-initial-values class))
607 ; First pass ensures all parents are initialized.
608 (for-each -class-check-init!
609 (-class-parent-classes class))
611 ; Next pass initializes the initial value list.
614 (let ((parents (-class-parent-classes class)))
615 (append (apply append (map get-inits parents))
616 (-class-my-initial-values class))))))
618 (let* ((parents (-class-parent-classes class))
619 (inits (append (apply append (map get-inits parents))
620 (-class-my-initial-values class))))
621 (-class-set-all-initial-values! class inits)))
623 ; Next pass initializes the class's class-descriptor.
624 ; Object elements begin at offset 2 in the element vector.
625 (-class-set-class-desc! class
626 (-class-compute-class-desc class 2 #f))
634 ; PARENTS is a list of names of parent classes. The parents need not
635 ; exist yet, though they must exist when the class is first instantiated.
636 ; ELMS is a either a list of either element names or name/value pairs.
637 ; Elements without initial values are marked as "unbound".
638 ; METHODS is an initial alist of methods. More methods can be added with
641 (define (class-make name parents elms methods)
644 ; Mark elements without initial values as unbound, and
645 ; compute indices into the element vector (relative to the class's
647 ; Elements are recorded as (symbol initial-value private? . vector-index)
648 ; FIXME: For now all elements are marked as "public".
649 (let loop ((elm-list-tmp '()) (index 0) (elms elms))
651 (set! elm-list (reverse! elm-list-tmp)) ; done
652 (if (pair? (car elms))
653 (loop (acons (caar elms)
654 (cons (cdar elms) (cons #f index))
658 (loop (acons (car elms)
659 (cons -object-unbound (cons #f index))
664 (let ((result (-class-make! name parents elm-list methods)))
666 ; Create the standard `make!' method.
667 ; The caller can override afterwards if desired.
668 ; Note that if there are any parent classes then we don't know the names
669 ; of all of the elements yet, that is only known after the class has been
670 ; initialized which only happens when the class is first instantiated.
671 ; This method won't be called until that happens though so we're safe.
672 ; This is written without knowledge of the names, it just initializes
674 (method-make! result 'make!
676 (let ((self (car args)))
677 ; Ensure exactly all of the elements are provided.
678 (if (not (= (length args)
679 (- (vector-length (-object-elements self)) 1)))
680 (-object-error "make!" "" "wrong number of arguments to method `make!'"))
681 (-object-make-with-values! (-object-top-class self)
682 (-object-class-desc self)
688 ; Create an object of a class CLASS.
691 (-class-check class "new")
694 (display (string-append "Instantiating class " (-class-name class) ".\n")
695 (current-error-port)))
697 (-object-make! class)
700 ; Make a copy of OBJ.
701 ; WARNING: A shallow copy is done on the elements!
703 (define (object-copy obj)
704 (-object-check obj "object-copy")
705 (-object-copy obj #f)
708 ; Make a copy of OBJ.
709 ; This makes a copy of top level object, with any specialization discarded.
710 ; WARNING: A shallow copy is done on the elements!
712 (define (object-copy-top obj)
713 (-object-check obj "object-copy-top")
714 (-object-copy obj #t)
717 ; Utility to define a standard `make!' method.
718 ; A standard make! method is one in which all it does is initialize
721 (define (method-make-make! class args)
723 (append (list 'lambda (cons 'self args))
724 (map (lambda (elm) (list 'elm-set! 'self
725 (list 'quote elm) elm))
728 (method-make! class 'make! (eval1 lambda-expr))
732 ; The "standard" way to invoke `make!' is (send (new class) 'make! ...).
733 ; This puts all that in a cover function.
735 (define (make class . operands)
736 (apply send (append (cons (new class) '()) '(make!) operands))
739 ; Return #t if class X is a subclass of BASE-NAME.
741 (define (-class-subclass? base-name x)
742 (if (eq? base-name (-class-name x))
744 (let loop ((parents (-class-parents x)))
747 (if (-class-subclass? base-name (class-lookup (car parents)))
749 (loop (cdr parents))))))
752 ; Return #t if OBJECT is an instance of CLASS.
753 ; This does not signal an error if OBJECT is not an object as this is
754 ; intended to be used in class predicates.
756 (define (class-instance? class object)
757 (-class-check class "class-instance?")
759 (-class-subclass? (-class-name class) (-object-class object))
763 ; Element operations.
765 ; Lookup an element in a class-desc.
766 ; The result is (class-desc . (private? . elm-offset)) or #f if not found.
767 ; ??? We could define accessors of the result but knowledge of its format
768 ; is restricted to this section of the source.
770 (define (-class-lookup-element class-desc elm-name)
771 (let* ((class (-class-desc-class class-desc))
772 (elm (assq elm-name (-class-elements class))))
774 (cons class-desc (cddr elm))
775 (let loop ((parents (-class-desc-parents class-desc)))
778 (let ((elm (-class-lookup-element (car parents) elm-name)))
781 (loop (cdr parents)))))
786 ; Given the result of -class-lookup-element, return the element's delta
789 (define (-elm-delta index)
790 (+ (-class-desc-offset-delta (car index))
794 ; Return a boolean indicating if ELM is bound in OBJ.
796 (define (elm-bound? obj elm)
797 (-object-check obj "elm-bound?")
798 (let* ((index (-class-lookup-element (-object-class-desc obj) elm))
799 (val (-object-elm-get obj (car index) (-elm-delta index))))
800 (not (eq? val -object-unbound)))
803 ; Subroutine of elm-get.
805 (define (-elm-make-method-getter self name)
806 (-object-check self "elm-get")
807 (let ((index (-class-lookup-element (-object-class-desc self) name)))
809 (procedure->memoizing-macro
812 (-object-elm-get obj (-object-class-desc obj)
813 ,(-elm-delta index)))))
814 (-object-error "elm-get" self "element not present: " name)))
817 ; Get an element from an object.
818 ; If OBJ is `self' then the caller is required to be a method and we emit
819 ; memoized code. Otherwise we do things the slow way.
820 ; ??? There must be a better way.
821 ; What this does is turn
822 ; (elm-get self 'foo)
824 ; ((-elm-make-method-get self 'foo) self)
825 ; Note the extra set of parens. -elm-make-method-get then does the lookup of
826 ; foo and returns a memoizing macro that returns the code to perform the
827 ; operation with O(1). Cute, but I'm hoping there's an easier/better way.
829 (defmacro elm-get (self name)
831 `(((-elm-make-method-getter ,self ,name)) ,self)
832 `(elm-xget ,self ,name))
835 ; Subroutine of elm-set!.
837 (define (-elm-make-method-setter self name)
838 (-object-check self "elm-set!")
839 (let ((index (-class-lookup-element (-object-class-desc self) name)))
841 (procedure->memoizing-macro
843 `(lambda (obj new-val)
844 (-object-elm-set! obj (-object-class-desc obj)
845 ,(-elm-delta index) new-val))))
846 (-object-error "elm-set!" self "element not present: " name)))
849 ; Set an element in an object.
850 ; This can only be used by methods.
851 ; See the comments for `elm-get'!
853 (defmacro elm-set! (self name new-val)
855 `(((-elm-make-method-setter ,self ,name)) ,self ,new-val)
856 `(elm-xset! ,self ,name ,new-val))
859 ; Get an element from an object.
860 ; This is for invoking from outside a method, and without having to
861 ; use elm-make-getter. It should be used sparingly.
863 (define (elm-xget obj name)
864 (-object-check obj "elm-xget")
865 (let ((index (-class-lookup-element (-object-class-desc obj) name)))
866 ; FIXME: check private?
868 (-object-elm-get obj (car index) (-elm-delta index))
869 (-object-error "elm-xget" obj "element not present: " name)))
872 ; Set an element in an object.
873 ; This is for invoking from outside a method, and without having to
874 ; use elm-make-setter. It should be used sparingly.
876 (define (elm-xset! obj name new-val)
877 (-object-check obj "elm-xset!")
878 (let ((index (-class-lookup-element (-object-class-desc obj) name)))
879 ; FIXME: check private?
881 (-object-elm-set! obj (car index) (-elm-delta index) new-val)
882 (-object-error "elm-xset!" obj "element not present: " name)))
885 ; Return a boolean indicating if object OBJ has element NAME.
887 (define (elm-present? obj name)
888 (-object-check obj "elm-present?")
889 (->bool (-class-lookup-element (-object-class-desc obj) name))
892 ; Return lambda to get element NAME in CLASS.
893 ; FIXME: validate name.
895 (define (elm-make-getter class name)
896 (-class-check class "elm-make-getter")
897 ; We use delay here as we can't assume parent classes have been
899 (let ((fast-index (delay (-class-lookup-element
900 (-class-class-desc class) name))))
902 ; ??? Should be able to use fast-index in mi case.
903 ; ??? Need to involve CLASS in lookup.
904 (let ((index (if (-object-mi? obj)
905 (-class-lookup-element (-object-class-desc obj) name)
906 (force fast-index))))
907 (-object-elm-get obj (car index) (-elm-delta index)))))
910 ; Return lambda to set element NAME in CLASS.
911 ; FIXME: validate name.
913 (define (elm-make-setter class name)
914 (-class-check class "elm-make-setter")
915 ; We use delay here as we can't assume parent classes have been
917 (let ((fast-index (delay (-class-lookup-element
918 (-class-class-desc class) name))))
920 ; ??? Should be able to use fast-index in mi case.
921 ; ??? Need to involve CLASS in lookup.
922 (let ((index (if (-object-mi? obj)
923 (-class-lookup-element (-object-class-desc obj) name)
924 (force fast-index))))
925 (-object-elm-set! obj (car index) (-elm-delta index) newval))))
928 ; Return a list of all elements in OBJ.
930 (define (elm-list obj)
931 (cddr (vector->list (-object-elements obj)))
936 ; Lookup the next method in a class.
937 ; This means begin the search in the parents.
938 ; ??? What should this do for virtual methods. At present we treat them as
941 (define (-method-lookup-next class-desc method-name)
942 (let loop ((parents (-class-desc-parents class-desc)))
945 (let ((meth (-method-lookup (car parents) method-name #f)))
948 (loop (cdr parents))))))
951 ; Lookup a method in a class.
952 ; The result is (class-desc . method). If the method is found in a parent
953 ; class, the associated parent class descriptor is returned. If the method is
954 ; a virtual method, the appropriate subclass's class descriptor is returned.
955 ; VIRTUAL? is #t if virtual methods are to be treated as such.
956 ; Otherwise they're treated as normal methods.
958 ; FIXME: We don't yet implement the method cache.
960 (define (-method-lookup class-desc method-name virtual?)
962 (display (string-append "Looking up method " method-name " in "
963 (-class-name (-class-desc-class class-desc)) ".\n")
964 (current-error-port)))
966 (let ((meth (assq method-name (-class-methods (-class-desc-class class-desc)))))
968 (if (and virtual? (cadr meth)) ; virtual?
969 ; Traverse back up the inheritance chain looking for overriding
970 ; methods. The closest one to the top is the one to use.
971 (let loop ((child (-class-desc-child class-desc))
972 (goal-class-desc class-desc)
977 (display (string-append "Looking up virtual method "
979 (-class-name (-class-desc-class child))
981 (current-error-port)))
982 (let ((meth (assq method-name (-class-methods (-class-desc-class child)))))
984 ; Method found, update goal object and method.
985 (loop (-class-desc-child child) child meth)
986 ; Method not found at this level.
987 (loop (-class-desc-child child) goal-class-desc goal-meth))))
988 ; Went all the way up to the top.
989 (cons goal-class-desc (cddr goal-meth))))
991 (cons class-desc (cddr meth)))
992 ; Method not found, search parents.
993 (-method-lookup-next class-desc method-name)))
996 ; Return a boolean indicating if object OBJ has method NAME.
998 (define (method-present? obj name)
999 (-object-check obj "method-present?")
1000 (->bool (-method-lookup (-object-class-desc obj) name #f))
1003 ; Return method NAME of CLASS or #f if not present.
1004 ; ??? Assumes CLASS has been initialized.
1006 (define (method-proc class name)
1007 (-class-check class "method-proc")
1008 (let ((meth (-method-lookup (-class-class-desc class) name #t)))
1014 ; Add a method to a class.
1015 ; FIXME: ensure method-name is a symbol
1017 (define (method-make! class method-name method)
1018 (-class-check class "method-make!")
1019 (if (not (procedure? method))
1020 (-object-error "method-make!" method "method must be a procedure"))
1021 (-class-set-methods! class (acons method-name
1023 (-class-methods class)))
1027 ; Add a virtual method to a class.
1028 ; FIXME: ensure method-name is a symbol
1030 (define (method-make-virtual! class method-name method)
1031 (-class-check class "method-make-virtual!")
1032 (if (not (procedure? method))
1033 (-object-error "method-make-virtual!" method "method must be a procedure"))
1034 (-class-set-methods! class (acons method-name
1036 (-class-methods class)))
1040 ; Utility to create "forwarding" methods.
1041 ; METHODS are forwarded to class member ELM-NAME, assumed to be an object.
1042 ; The created methods take a variable number of arguments.
1043 ; Argument length checking will be done by the receiving method.
1044 ; FIXME: ensure elm-name is a symbol
1046 (define (method-make-forward! class elm-name methods)
1047 (for-each (lambda (method-name)
1050 (eval1 `(lambda args
1052 (cons (elm-get (car args)
1054 (cons (quote ,method-name)
1060 ; Same as method-make-forward! but creates virtual methods.
1061 ; FIXME: ensure elm-name is a symbol
1063 (define (method-make-virtual-forward! class elm-name methods)
1064 (for-each (lambda (method-name)
1065 (method-make-virtual!
1067 (eval1 `(lambda args
1069 (cons (elm-get (car args)
1071 (cons (quote ,method-name)
1077 ; Utility of send, send-next.
1079 (define (-object-method-notify obj method-name maybe-next)
1080 (set! -object-verbose? #f)
1081 (display (string-append "Sending " maybe-next method-name " to"
1082 (if (method-present? obj 'get-name)
1083 (let ((name (send obj 'get-name)))
1084 (if (or (symbol? name) (string? name))
1085 (string-append " object " name)
1088 " class " (object-class-name obj) ".\n")
1089 (current-error-port))
1090 (set! -object-verbose? #t)
1093 ; Invoke a method in an object.
1094 ; When the method is invoked, the (possible parent class) object in which the
1095 ; method is found is passed to the method.
1096 ; ??? The word `send' comes from "sending messages". Perhaps should pick
1097 ; a better name for this operation.
1099 (define (send obj method-name . args)
1100 (-object-check obj "send")
1101 (-object-check-name method-name "send" "not a method name")
1102 (if -object-verbose? (-object-method-notify obj method-name ""))
1104 (let ((class-desc.meth (-method-lookup (-object-class-desc obj)
1107 (apply (cdr class-desc.meth)
1108 (cons (-object-specialize obj (car class-desc.meth))
1110 (-object-error "send" obj "method not supported: " method-name)))
1113 ; Invoke the next method named METHOD-NAME in the heirarchy of OBJ.
1114 ; i.e. the method that would have been invoked if the calling method
1116 ; This may only be called by a method.
1117 ; ??? Ideally we shouldn't need the METHOD-NAME argument. It could be
1118 ; removed with a bit of effort, but is it worth it?
1120 (define (send-next obj method-name . args)
1121 (-object-check obj "send-next")
1122 (-object-check-name method-name "send-next" "not a method name")
1123 (if -object-verbose? (-object-method-notify obj method-name "next "))
1125 (let ((class-desc.meth (-method-lookup-next (-object-class-desc obj)
1128 (apply (cdr class-desc.meth)
1129 (cons (-object-specialize obj (car class-desc.meth))
1131 (-object-error "send-next" obj "method not supported: " method-name)))
1134 ; Parent operations.
1136 ; Subroutine of `parent' to lookup a (potentially nested) parent class.
1137 ; The result is the parent's class-descriptor or #f if not found.
1139 (define (-class-parent class-desc parent)
1140 (let* ((parent-descs (-class-desc-parents class-desc))
1141 (desc (-class-desc-lookup-parent parent parent-descs)))
1144 (let loop ((parents parent-descs))
1147 (let ((desc (-class-parent (car parents) parent)))
1150 (loop (cdr parents))))))))
1153 ; Subroutine of `parent' to lookup a parent via a path.
1154 ; PARENT-PATH, a list, is the exact path to the parent class.
1155 ; The result is the parent's class-descriptor or #f if not found.
1156 ; For completeness' sake, if PARENT-PATH is empty, CLASS-DESC is returned.
1158 (define (-class-parent-via-path class-desc parent-path)
1159 (if (null? parent-path)
1161 (let ((desc (-class-desc-lookup-parent (car parent-path)
1162 (-class-desc-parents class-desc))))
1164 (if (null? (cdr parent-path))
1166 (-class-parent-via-path (car desc) (cdr parent-path)))
1170 ; Lookup a parent class of object OBJ.
1171 ; CLASS is either a class or a list of classes.
1172 ; If CLASS is a list, it is a (possibly empty) "path" to the parent.
1173 ; Otherwise it is any parent and is searched for breadth-first.
1174 ; ??? Methinks this should be depth-first.
1175 ; The result is OBJ, specialized to the found parent.
1177 (define (object-parent obj class)
1178 (-object-check obj "object-parent")
1179 (cond ((class? class) #t)
1180 ((list? class) (for-each (lambda (class) (-class-check class
1183 (else (-object-error "object-parent" class "invalid parent path")))
1185 ; Hobbit generates C code that passes the function
1186 ; -class-parent-via-path or -class-parent, not the appropriate
1188 ; (let ((result ((if (or (null? class) (pair? class))
1189 ; -class-parent-via-path
1192 ; So it's rewritten like this.
1193 (let ((result (if (class? class)
1194 (-class-parent (-object-class-desc obj) class)
1195 (-class-parent-via-path (-object-class-desc obj) class))))
1197 (-object-specialize obj result)
1198 (-object-error "object-parent" obj "parent not present")))
1199 ; FIXME: should print path in error message.
1202 ; Make PARENT-NAME a parent of CLASS, cons'd unto the front of the search
1203 ; order. This is used to add a parent class to a class after it has already
1204 ; been created. Obviously this isn't something one does willy-nilly.
1205 ; The parent is added to the front of the current parent list (affects
1208 (define (class-cons-parent! class parent-name)
1209 (-class-check class "class-cons-parent!")
1210 (-object-check-name parent-name "class-cons-parent!" "not a class name")
1211 (-class-set-parents! class (cons parent-name (-class-parents class)))
1215 ; Make PARENT-NAME a parent of CLASS, cons'd unto the end of the search order.
1216 ; This is used to add a parent class to a class after it has already been
1217 ; created. Obviously this isn't something one does willy-nilly.
1218 ; The parent is added to the end of the current parent list (affects
1221 (define (class-append-parent! class parent-name)
1222 (-class-check class "class-append-parent!")
1223 (-object-check-name parent-name "class-append-parent!" "not a class name")
1224 (-class-set-parents! obj (append (-class-parents obj) (list parent-name)))
1228 ; Miscellaneous publically accessible utilities.
1230 ; Reset the object system (delete all classes).
1232 (define (object-reset!)
1233 (set! -class-list '())
1237 ; Call once to initialize the object system.
1238 ; Only necessary if classes have been modified after objects have been
1239 ; instantiated. This usually happens during development only.
1241 (define (object-init!)
1242 (for-each (lambda (class)
1243 (-class-set-all-initial-values! class #f)
1244 (-class-set-all-methods! class #f)
1245 (-class-set-class-desc! class #f))
1247 (for-each (lambda (class)
1248 (-class-check-init! class))
1253 ; Return list of all classes.
1255 (define (class-list) (map cdr -class-list))
1257 ; Utility to map over a class and all its parent classes, recursively.
1259 (define (class-map-over-class proc class)
1261 (map (lambda (class) (class-map-over-class proc class))
1262 (-class-parent-classes class)))
1265 ; Return class tree of a class or object.
1267 (define (class-tree class-or-object)
1268 (cond ((class? class-or-object)
1269 (class-map-over-class class-name class-or-object))
1270 ((object? class-or-object)
1271 (class-map-over-class class-name (-object-class class-or-object)))
1272 (else (-object-error "class-tree" class-or-object
1273 "not a class or object")))
1276 ; Return names of each alist.
1278 (define (-class-alist-names class)
1279 (list (-class-name class)
1280 (map car (-class-elements class))
1281 (map car (-class-methods class)))
1284 ; Return complete layout of class-or-object.
1286 (define (class-layout class-or-object)
1287 (cond ((class? class-or-object)
1288 (class-map-over-class -class-alist-names class-or-object))
1289 ((object? class-or-object)
1290 (class-map-over-class -class-alist-names (-object-class class-or-object)))
1291 (else (-object-error "class-layout" class-or-object
1292 "not a class or object")))
1295 ; Like assq but based on the `name' element.
1298 (define (object-assq name obj-list)
1299 (find-first (lambda (o) (eq? (elm-xget o 'name) name))
1303 ; Like memq but based on the `name' element.
1306 (define (object-memq name obj-list)
1307 (let loop ((r obj-list))
1308 (cond ((null? r) #f)
1309 ((eq? name (elm-xget (car r) 'name)) r)
1310 (else (loop (cdr r)))))
1313 ; Misc. internal utilities.
1315 ; We need a fast vector copy operation.
1316 ; If `vector-copy' doesn't exist (which is assumed to be the fast one),
1317 ; provide a simple version.
1318 ; FIXME: Need deep copier instead.
1320 (if (defined? 'vector-copy)
1321 (define -object-vector-copy vector-copy)
1322 (define (-object-vector-copy v) (list->vector (vector->list v)))
1327 (if (and #f (defined? 'proc-profile))
1329 (proc-profile elm-get)
1330 (proc-profile elm-xset!)
1331 (proc-profile elm-present?)
1332 (proc-profile -method-lookup)