918 lines
30 KiB
Plaintext
918 lines
30 KiB
Plaintext
;;;;
|
|
;;;; s t k l o s . s t k -- A variation of the Gregor Kickzales Tiny CLOS
|
|
;;;; for STk
|
|
;;;;
|
|
;;;; Copyright © 1993-1999 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
|
|
;;;;
|
|
;;;; Permission to use, copy, modify, distribute,and license this
|
|
;;;; software and its documentation for any purpose is hereby granted,
|
|
;;;; provided that existing copyright notices are retained in all
|
|
;;;; copies and that this notice is included verbatim in any
|
|
;;;; distributions. No written agreement, license, or royalty fee is
|
|
;;;; required for any of the authorized uses.
|
|
;;;; This software is provided ``AS IS'' without express or implied
|
|
;;;; warranty.
|
|
;;;;
|
|
;;;; Author: Erick Gallesio [eg@unice.fr]
|
|
;;;; Creation date: 20-Feb-1994 21:09
|
|
;;;; Last file update: 27-Sep-1999 12:05 (eg)
|
|
|
|
(when (provided? "stklos")
|
|
(error "STklos already initialized."))
|
|
|
|
(%init-stklos) ; Initialize STklos
|
|
(with-module STk (import STklos)) ; Import the STklos module
|
|
(select-module STklos) ; Rest of file belongs to the STklos module
|
|
(export ; Define the exported symbols of this file
|
|
find-class is-a?
|
|
ensure-metaclass ensure-metaclass-with-supers
|
|
define-class make-class ensure-class
|
|
define-generic make-generic-function ensure-generic-function
|
|
define-method ensure-method method add-method
|
|
object-eqv? object-equal?
|
|
write-object display-object Tk-write-object
|
|
slot-unbound slot-missing
|
|
slot-definition-name slot-definition-options slot-definition-allocation
|
|
slot-definition-getter slot-definition-setter slot-definition-accessor
|
|
slot-definition-init-form slot-definition-init-keyword
|
|
slot-init-function class-slot-definition
|
|
method-body
|
|
compute-get-n-set
|
|
allocate-instance initialize make-instance make
|
|
no-next-method no-applicable-method no-method next-method-exists?
|
|
change-class
|
|
shallow-clone deep-clone
|
|
apply-generic apply-method apply-methods compute-applicable-methods
|
|
method-more-specific? sort-applicable-methods
|
|
class-subclasses class-methods
|
|
slot-value (setter slot-value)
|
|
)
|
|
|
|
;=============================================================================
|
|
;
|
|
; U t i l i t i e s
|
|
;
|
|
;=============================================================================
|
|
|
|
(define (mapappend func . args)
|
|
(if (memv '() args)
|
|
'()
|
|
(append (apply func (map car args))
|
|
(apply mapappend func (map cdr args)))))
|
|
|
|
(define (find-duplicate l) ; find a duplicate in a list; #f otherwise
|
|
(cond
|
|
((null? l) #f)
|
|
((memv (car l) (cdr l)) (car l))
|
|
(else (find-duplicate (cdr l)))))
|
|
|
|
;--------------------------------------------------
|
|
(define (set-symbol! symbol value env)
|
|
(let ((module (%get-module env)))
|
|
(eval `(define ,symbol ,value) (module-environment module))))
|
|
|
|
(define (lookup-symbol symbol env)
|
|
(let ((module (%get-module env)))
|
|
(eval symbol (module-environment module))))
|
|
|
|
(define make-closure eval)
|
|
|
|
;--------------------------------------------------
|
|
(define (specializers l)
|
|
(cond
|
|
((null? l) '())
|
|
((pair? l) (cons (if (pair? (car l)) (cadar l) '<top>)
|
|
(specializers (cdr l))))
|
|
(else '<top>)))
|
|
|
|
(define (formals l)
|
|
(if (pair? l)
|
|
(cons (if (pair? (car l)) (caar l) (car l)) (formals (cdr l)))
|
|
l))
|
|
|
|
;--------------------------------------------------
|
|
; [FIXME] This is too complicate
|
|
(define (%find-class name env . default)
|
|
(if (symbol? name)
|
|
;; Search symbol in the globals of given env. (it must denote a class)
|
|
(or (and (symbol-bound? name env)
|
|
(let ((cls (lookup-symbol name env)))
|
|
(and (is-a? cls <class>) cls)))
|
|
(if (null? default)
|
|
(error "find-class: class ~S not found" name)
|
|
(car default)))
|
|
(if (is-a? name <class>)
|
|
name
|
|
(error "find-class: bad-class ~S" name))))
|
|
|
|
;=============================================================================
|
|
|
|
;;
|
|
;; Find-class
|
|
;;
|
|
(define-macro (find-class name . default)
|
|
(let ((%find-class (with-module STklos %find-class)))
|
|
`(apply ,%find-class ,name ,(the-environment) ',default)))
|
|
|
|
;;
|
|
;; is-a?
|
|
;;
|
|
(define (is-a? obj class)
|
|
(and (memv class (class-precedence-list (class-of obj))) #t))
|
|
|
|
|
|
;=============================================================================
|
|
;
|
|
; M e t a c l a s s e s s t u f f
|
|
;
|
|
;=============================================================================
|
|
|
|
(define ensure-metaclass-with-supers
|
|
(let ((table-of-metas '()))
|
|
(lambda (meta-supers)
|
|
(let ((entry (assoc meta-supers table-of-metas)))
|
|
(if entry
|
|
;; Found a previously created metaclass
|
|
(cdr entry)
|
|
;; Create a new meta-class which inherit from "meta-supers"
|
|
(let ((new (make <class> :dsupers meta-supers
|
|
:slots '()
|
|
:name (gensym "metaclass"))))
|
|
(set! table-of-metas (cons (cons meta-supers new) table-of-metas))
|
|
new))))))
|
|
|
|
(define (ensure-metaclass supers env)
|
|
(if (null? supers)
|
|
(%find-class '<class> env)
|
|
(let* ((all-metas (map (lambda (x)
|
|
(if (is-a? x <class>)
|
|
x
|
|
(class-of (%find-class x env))))
|
|
supers))
|
|
(all-cpls (apply append
|
|
(map (lambda (m) (cdr (class-precedence-list m)))
|
|
all-metas)))
|
|
(needed-metas '()))
|
|
;; Find the most specific metaclasses. The new metaclass will be
|
|
;; a subclass of these.
|
|
(for-each
|
|
(lambda (meta)
|
|
(when (and (not (member meta all-cpls)) (not (member meta needed-metas)))
|
|
(set! needed-metas (append needed-metas (list meta)))))
|
|
all-metas)
|
|
;; Now return a subclass of the metaclasses we found.
|
|
(if (null? (cdr needed-metas))
|
|
(car needed-metas) ; If there's only one, just use it.
|
|
(ensure-metaclass-with-supers needed-metas)))))
|
|
|
|
;=============================================================================
|
|
;
|
|
; D e f i n e - c l a s s
|
|
;
|
|
;=============================================================================
|
|
|
|
;==== Define-class
|
|
(define-macro (define-class name supers slots . options)
|
|
`(begin
|
|
(make-class ,name ,supers ,slots ,@options)
|
|
(make-undefined)))
|
|
|
|
;==== Make-class
|
|
(define-macro (make-class name supers slots . options)
|
|
`(ensure-class
|
|
',name ; name
|
|
',supers ; supers
|
|
',slots ; slots
|
|
,(or (get-keyword :metaclass options #f) ; metaclass
|
|
`(ensure-metaclass ',supers (the-environment)))
|
|
(the-environment) ; environment
|
|
,@options))
|
|
|
|
;==== Ensure-class
|
|
(define (ensure-class name supers slots metaclass env . options)
|
|
(let ((supers (if (null? supers)
|
|
(list (%find-class '<object> env))
|
|
(map (lambda (x) (%find-class x env)) supers))))
|
|
;; Verify that all direct slots are different and that we don't inherit
|
|
;; several time from the same class
|
|
(let ((tmp1 (find-duplicate supers))
|
|
(tmp2 (find-duplicate (map slot-definition-name slots))))
|
|
(when tmp1
|
|
(error "define-class: super class ~S is duplicate in class ~S" tmp1 name))
|
|
(when tmp2
|
|
(error "define-class: slot ~S is duplicate in class ~S" tmp2 name)))
|
|
|
|
;; Everything seems correct, build the class
|
|
(let ((old (%find-class name env #f))
|
|
(cls (apply make metaclass :dsupers supers :slots slots
|
|
:name name :environment env options)))
|
|
(when old (class-redefinition old cls))
|
|
(set-symbol! name cls env)
|
|
cls)))
|
|
|
|
;=============================================================================
|
|
;
|
|
; D e f i n e - g e n e r i c
|
|
;
|
|
;=============================================================================
|
|
|
|
; ==== Define-generic
|
|
(define-macro (define-generic gf)
|
|
`(begin
|
|
(make-generic-function ,gf)
|
|
(make-undefined)))
|
|
|
|
|
|
;==== Make-generic-function
|
|
(define-macro (make-generic-function gf)
|
|
`(ensure-generic-function ',gf (the-environment)))
|
|
|
|
;==== Ensure-generic-function
|
|
(define (ensure-generic-function name env)
|
|
(let* ((name (extended-name->scheme-name name))
|
|
(old-value (and (symbol-bound? name env)
|
|
;FIXME: (not (autoload? name module))
|
|
(lookup-symbol name env))))
|
|
(set-symbol! name
|
|
(make <generic>
|
|
:name name
|
|
:default (and (procedure? old-value) old-value))
|
|
env)))
|
|
|
|
|
|
;=============================================================================
|
|
;
|
|
; D e f i n e - m e t h o d
|
|
;
|
|
;=============================================================================
|
|
|
|
(define-macro (define-method name args . body)
|
|
`(begin
|
|
(ensure-method ',name
|
|
',(formals args)
|
|
',(specializers args)
|
|
',body
|
|
(the-environment))
|
|
(make-undefined)))
|
|
|
|
(define (ensure-method name formals specializers body env . kind-of-method)
|
|
(let* ((name (and name (extended-name->scheme-name name)))
|
|
(old-value (and name
|
|
(symbol-bound? name env)
|
|
;FIXME: (not (autoload? name module))
|
|
(lookup-symbol name env))))
|
|
|
|
;; See if a generic function must be defined for this method.
|
|
(when (and name (not (is-a? old-value <generic>)))
|
|
(ensure-generic-function name env))
|
|
|
|
;; Define the method and, eventually, add it to its generic
|
|
(let ((method (make (if (null? kind-of-method) <method> (car kind-of-method))
|
|
:specializers (map* (lambda (x)
|
|
(if (symbol? x)
|
|
(%find-class x env)
|
|
x))
|
|
specializers)
|
|
:procedure (make-closure
|
|
`(lambda (next-method ,@formals) ,@body)
|
|
env))))
|
|
(when name (add-method (lookup-symbol name env) method))
|
|
; Return the newly created method as result
|
|
method)))
|
|
|
|
;==== Method
|
|
|
|
(define-macro (method args . body)
|
|
`(begin
|
|
(ensure-method #f
|
|
',(formals args)
|
|
',(specializers args)
|
|
',body
|
|
(the-environment))))
|
|
|
|
;==== Add-method
|
|
|
|
(define (add-method-in-classes m)
|
|
;; Add method in all the classes which appears in its specializers list
|
|
(for-each* (lambda (x)
|
|
(let ((dm (class-direct-methods x)))
|
|
(unless (memv m dm)
|
|
(slot-set! x 'direct-methods (cons m dm)))))
|
|
(method-specializers m)))
|
|
|
|
(define (remove-method-in-classes m)
|
|
;; Remove method in all the classes which appears in its specializers list
|
|
(for-each* (lambda (x)
|
|
(slot-set! x 'direct-methods (remv m (class-direct-methods x))))
|
|
(method-specializers m)))
|
|
|
|
(define (compute-new-list-of-methods gf new)
|
|
(let ((new-spec (method-specializers new))
|
|
(methods (generic-function-methods gf)))
|
|
(let Loop ((l methods))
|
|
(if (null? l)
|
|
(cons new methods)
|
|
(if (equal? (method-specializers (car l)) new-spec)
|
|
(begin
|
|
;; This spec. list already exists. Remove old method from dependents
|
|
(remove-method-in-classes (car l))
|
|
(set-car! l new)
|
|
methods)
|
|
(Loop (cdr l)))))))
|
|
|
|
;;
|
|
;; Add-method
|
|
;;
|
|
(define (add-method gf m)
|
|
(slot-set! m 'generic-function gf)
|
|
(slot-set! gf 'methods (compute-new-list-of-methods gf m))
|
|
(add-method-in-classes m)
|
|
m)
|
|
|
|
;=============================================================================
|
|
;
|
|
; Access to Meta objects
|
|
;
|
|
; A lot of them are in C
|
|
;=============================================================================
|
|
|
|
;;;
|
|
;;; Methods
|
|
;;;
|
|
(define-method method-body ((m <method>))
|
|
(let* ((spec (map class-name (slot-ref m 'specializers)))
|
|
(proc (procedure-body (slot-ref m 'procedure)))
|
|
(args (cdadr proc))
|
|
(body (cddr proc)))
|
|
(list* 'method (map list args spec) body)))
|
|
|
|
;;;
|
|
;;; Slots
|
|
;;;
|
|
(define (slot-definition-name s)
|
|
(if (pair? s) (car s) s))
|
|
|
|
(define (slot-definition-options s)
|
|
(and (pair? s) (cdr s)))
|
|
|
|
(define (slot-definition-allocation s)
|
|
(if (symbol? s)
|
|
:instance
|
|
(get-keyword :allocation (cdr s) :instance)))
|
|
|
|
(define (slot-definition-getter s)
|
|
(and (pair? s) (get-keyword :getter (cdr s) #f)))
|
|
|
|
(define (slot-definition-setter s)
|
|
(and (pair? s) (get-keyword :setter (cdr s) #f)))
|
|
|
|
(define (slot-definition-accessor s)
|
|
(and (pair? s) (get-keyword :accessor (cdr s) #f)))
|
|
|
|
(define (slot-definition-init-form s)
|
|
(if (pair? s)
|
|
(let* ((none (list '**none**))
|
|
(v1 (get-keyword :init-form (cdr s) none))
|
|
(v2 (get-keyword :initform (cdr s) none))) ; Backward compatibility
|
|
(if (eq? v1 none)
|
|
(if (eq? v2 none)
|
|
(make-unbound)
|
|
v2)
|
|
v1))
|
|
(make-unbound)))
|
|
|
|
(define (slot-definition-init-keyword s)
|
|
(and (pair? s) (get-keyword :init-keyword (cdr s) #f)))
|
|
|
|
(define (slot-init-function c s)
|
|
(let ((s (slot-definition-name s)))
|
|
(cadr (assoc s (slot-ref c 'getters-n-setters)))))
|
|
|
|
(define (class-slot-definition class slot-name)
|
|
(assoc slot-name (class-slots class)))
|
|
|
|
|
|
;=============================================================================
|
|
;
|
|
; Standard methods
|
|
; used by the C runtime
|
|
;
|
|
;=============================================================================
|
|
|
|
;==== Methods to compare objects
|
|
|
|
(define-method object-eqv? (x y) #f)
|
|
(define-method object-equal? (x y) (eqv? x y))
|
|
|
|
;==== Methods to display/write an object
|
|
|
|
; Code for writing objects must test that the slots they use are
|
|
; bound. Otherwise a slot-unbound method will be called and will
|
|
; conduct to an infinite loop.
|
|
|
|
;; Write
|
|
(define-method write-object (o file)
|
|
(format file "#[instance ~A]" (address-of o)))
|
|
|
|
(define-method write-object ((o <object>) file)
|
|
(let ((class (class-of o)))
|
|
(if (slot-bound? class 'name)
|
|
(format file "#[~A ~A]" (class-name class) (address-of o))
|
|
(next-method))))
|
|
|
|
(define-method write-object((class <class>) file)
|
|
(let ((meta (class-of class)))
|
|
(if (and (slot-bound? class 'name) (slot-bound? meta 'name))
|
|
(format file "#[~A ~A ~A]" (class-name meta) (class-name class)
|
|
(address-of class))
|
|
(next-method))))
|
|
|
|
(define-method write-object((gf <generic>) file)
|
|
(let ((meta (class-of gf)))
|
|
(if (and (slot-bound? gf 'name) (slot-bound? meta 'name)
|
|
(slot-bound? gf 'methods))
|
|
(format file "#[~A ~A (~A)]" (class-name meta)
|
|
(generic-function-name gf)
|
|
(length (generic-function-methods gf)))
|
|
(next-method))))
|
|
|
|
;; Display (do the same thing as write by default)
|
|
(define-method display-object (o file)
|
|
(write-object o file))
|
|
|
|
;; Tk-write-object is called when a STklos object is passed to a Tk-command.
|
|
;; By default, we do the same job as write; but if an object is a <Tk-widget>
|
|
;; we will pass it its Eid. The method for <Tk-widget> is defined elsewhere.
|
|
(define-method Tk-write-object (o file)
|
|
(write-object o file))
|
|
|
|
;==== Slot access
|
|
|
|
(define-method slot-unbound ((c <class>) (o <object>) s)
|
|
(error "Slot `~S' is unbound in object ~S" s o))
|
|
|
|
(define-method slot-missing ((c <class>) (o <object>) s)
|
|
(error "No slot with name `~S' in object ~S" s o))
|
|
|
|
|
|
(define-method slot-missing ((c <class>) (o <object>) s value)
|
|
(slot-missing c o s))
|
|
|
|
; ==== Methods for the possible error we can encounter when calling a gf
|
|
|
|
(define-method no-next-method ((gf <generic>) args)
|
|
(error "No next method when calling ~S\nwith ~S as argument" gf args))
|
|
|
|
(define-method no-applicable-method ((gf <generic>) args)
|
|
(error "No applicable method for ~S\nin call ~S"
|
|
gf (cons (generic-function-name gf) args)))
|
|
|
|
(define-method no-method ((gf <generic>) args)
|
|
(error "No method defined for ~S" gf))
|
|
|
|
|
|
(define-macro (next-method-exists?)
|
|
`((with-module STklos %next-method-exists?) next-method))
|
|
|
|
;=============================================================================
|
|
;
|
|
; Cloning functions (from rdeline@CS.CMU.EDU)
|
|
;
|
|
;=============================================================================
|
|
|
|
(define-method shallow-clone ((self <object>))
|
|
(let ((clone (%allocate-instance (class-of self)))
|
|
(slots (map slot-definition-name
|
|
(class-slots (class-of self)))))
|
|
(for-each (lambda (slot)
|
|
(if (slot-bound? self slot)
|
|
(slot-set! clone slot (slot-ref self slot))))
|
|
slots)
|
|
clone))
|
|
|
|
(define-method deep-clone ((self <object>))
|
|
(let ((clone (%allocate-instance (class-of self)))
|
|
(slots (map slot-definition-name
|
|
(class-slots (class-of self)))))
|
|
(for-each (lambda (slot)
|
|
(if (slot-bound? self slot)
|
|
(slot-set! clone slot
|
|
(let ((value (slot-ref self slot)))
|
|
(if (instance? value)
|
|
(deep-clone value)
|
|
value)))))
|
|
slots)
|
|
clone))
|
|
|
|
;=============================================================================
|
|
;
|
|
; Class redefinition utilities
|
|
;;
|
|
;=============================================================================
|
|
|
|
;==== Class-redefinition
|
|
|
|
(define-method class-redefinition ((old <Class>) (new <Class>))
|
|
;; Work on direct methods:
|
|
;; 1. Remove accessor methods from the old class
|
|
;; 2. Patch the occurences of old in the specializers by new
|
|
;; 3. Displace the methods from old to new
|
|
(remove-class-accessors old) ;; -1-
|
|
(let ((methods (class-direct-methods old)))
|
|
(for-each (lambda (m) (update-direct-method m old new)) ;; -2-
|
|
methods)
|
|
(slot-set! new 'direct-methods methods)) ;; -3-
|
|
|
|
;; Remove the old class from the direct-subclasses list of its super classes
|
|
(for-each (lambda (c) (slot-set! c 'direct-subclasses
|
|
(remv old (class-direct-subclasses c))))
|
|
(class-direct-supers old))
|
|
|
|
;; Redefine all the subclasses of old to take into account modification
|
|
(for-each
|
|
(lambda (c) (update-direct-subclass c old new))
|
|
(class-direct-subclasses old))
|
|
|
|
;; Invalidate class so that subsequent instances slot accesses invoke
|
|
;; change-object-class
|
|
(slot-set! old 'redefined new))
|
|
|
|
|
|
;==== Remove-class-accessors
|
|
|
|
(define-method remove-class-accessors ((c <class>))
|
|
(for-each
|
|
(lambda (m) (if (is-a? m <accessor-method>) (remove-method-in-classes m)))
|
|
(class-direct-methods c)))
|
|
|
|
|
|
;==== Update-direct-method
|
|
|
|
(define-method update-direct-method ((m <method>) (old <class>) (new <class>))
|
|
(let Loop ((l (method-specializers m)))
|
|
(when (pair? l) ; Note: the <top> in dotted list is never used.
|
|
(if (eqv? (car l) old) ; So we can work if we had only proper lists.
|
|
(set-car! l new))
|
|
(Loop (cdr l)))))
|
|
|
|
;==== Update-direct-subclass
|
|
|
|
(define-method update-direct-subclass ((c <class>) (old <Class>) (new <Class>))
|
|
(let ((new-supers (map (lambda (cls) (if (eqv? cls old) new cls))
|
|
(class-direct-supers c))))
|
|
;; Create a new class with same name as c. This will automagically call
|
|
;; class-redefinition on this subclass and redefine all its descent
|
|
(ensure-class (class-name c)
|
|
new-supers
|
|
(class-direct-slots c)
|
|
(class-of c)
|
|
(slot-ref c 'environment))))
|
|
|
|
;=============================================================================
|
|
;
|
|
; Utilities for INITIALIZE methods
|
|
;
|
|
;=============================================================================
|
|
|
|
;;;
|
|
;;; compute-slot-accessors
|
|
;;;
|
|
(define (compute-slot-accessors class slots env)
|
|
(for-each
|
|
(lambda (s)
|
|
(let ((name (slot-definition-name s))
|
|
(getter (slot-definition-getter s))
|
|
(setter (slot-definition-setter s))
|
|
(accessor (slot-definition-accessor s)))
|
|
(when getter
|
|
(ensure-method getter (list 'o) (list class)
|
|
`((slot-ref o ',name))
|
|
env <accessor-method>))
|
|
(when setter
|
|
(ensure-method setter (list 'o 'v) (list class <top>)
|
|
`((slot-set! o ',name v))
|
|
env <accessor-method>))
|
|
(when accessor
|
|
(ensure-method accessor (list 'o) (list class)
|
|
`((slot-ref o ',name))
|
|
env <accessor-method>)
|
|
(ensure-method `(setter ,accessor) (list 'o 'v) (list class <top>)
|
|
`((slot-set! o ',name v))
|
|
env <accessor-method>))))
|
|
slots))
|
|
|
|
;;;
|
|
;;; compute-getters-n-setters
|
|
;;;
|
|
(define (compute-getters-n-setters class slots env)
|
|
|
|
(define (compute-slot-init-function s)
|
|
(let ((init (slot-definition-init-form s)))
|
|
(and (not (unbound? init)) (make-closure `(lambda () ,init) env))))
|
|
|
|
(define (verify-accessors slot l)
|
|
(if (pair? l)
|
|
(let ((get (car l))
|
|
(set (cadr l)))
|
|
(unless (and (closure? get) (= (%procedure-arity get) 1))
|
|
(error "Bad getter closure for slot `~S' in ~S: ~S" slot class get))
|
|
(unless (and (closure? set) (= (%procedure-arity set) 2))
|
|
(error "Bad setter closure for slot `~S' in ~S: ~S" slot class set)))))
|
|
|
|
(map (lambda (s)
|
|
(let* ((s (if (pair? s) s (list s)))
|
|
(g-n-s (compute-get-n-set class s))
|
|
(name (slot-definition-name s)))
|
|
; For each slot we have '(name init-function getter setter)
|
|
; If slot, we have the simplest form '(name init-function . index)
|
|
(verify-accessors name g-n-s)
|
|
(list* name (compute-slot-init-function s) g-n-s)))
|
|
slots))
|
|
|
|
;;;
|
|
;;; compute-cpl
|
|
;;;
|
|
(define (compute-cpl class)
|
|
|
|
(define (filter-cpl class)
|
|
(let ((res '()))
|
|
(for-each (lambda (item)
|
|
(unless (or (eq? item <object>)
|
|
(eq? item <top>)
|
|
(member item res))
|
|
(set! res (cons item res))))
|
|
class)
|
|
res))
|
|
|
|
(let* ((supers (slot-ref class 'direct-supers))
|
|
(big-list (apply append (cons class supers) (map compute-cpl supers))))
|
|
(reverse (list* <top> <object> (filter-cpl big-list)))))
|
|
|
|
;;;
|
|
;;; Compute-get-n-set
|
|
;;;
|
|
(define-method compute-get-n-set ((class <class>) s)
|
|
(case (slot-definition-allocation s)
|
|
(:instance ;; Instance slot
|
|
;; get-n-set is just its offset
|
|
(let ((already-allocated (slot-ref class 'nfields)))
|
|
(slot-set! class 'nfields (+ already-allocated 1))
|
|
already-allocated))
|
|
|
|
(:class ;; Class slot
|
|
;; Class-slots accessors are implemented as 2 closures around
|
|
;; a Scheme variable. As instance slots, class slots must be
|
|
;; unbound at init time. Since assignement to an unbound variable
|
|
;; is not possible with our set! (in this case set! thinks that
|
|
;; the variable has not been defined), our variable is in fact
|
|
;; a vector of length 1. This permits to circumvent this problem,
|
|
;; without introducing a "set-environment" primitive.
|
|
(let ((name (slot-definition-name s)))
|
|
(if (memq name (map slot-definition-name (class-direct-slots class)))
|
|
;; This slot is direct; create a new shared cell
|
|
(let ((shared-cell (make-vector 1)))
|
|
(list (lambda (o) (vector-ref shared-cell 0))
|
|
(lambda (o v) (vector-set! shared-cell 0 v))))
|
|
;; Slot is inherited. Find its definition in superclass
|
|
(let Loop ((l (cdr (class-precedence-list class))))
|
|
(let ((r (assoc name (slot-ref (car l) 'getters-n-setters))))
|
|
(if r
|
|
(cddr r)
|
|
(Loop (cdr l))))))))
|
|
|
|
(:each-subclass ;; slot shared by instances of direct subclass.
|
|
;; (Thomas Buerger, April 1998)
|
|
(let ((shared-cell (make-vector 1)))
|
|
(list (lambda (o) (vector-ref shared-cell 0))
|
|
(lambda (o v) (vector-set! shared-cell 0 v)))))
|
|
|
|
(:virtual;; No allocation
|
|
;; slot-ref and slot-set! function must be given by the user
|
|
(let ((get (get-keyword :slot-ref (slot-definition-options s) #f))
|
|
(set (get-keyword :slot-set! (slot-definition-options s) #f))
|
|
(env (class-environment class)))
|
|
(unless (and get set)
|
|
(error "You must supply a :slot-ref and a :slot-set! in ~A" s))
|
|
(list (make-closure get env)
|
|
(make-closure set env))))
|
|
(else (next-method))))
|
|
|
|
(define-method compute-get-n-set ((o <object>) s)
|
|
(error "Allocation \"~S\" is unknown" (slot-definition-allocation s)))
|
|
|
|
;=============================================================================
|
|
;
|
|
; I n i t i a l i z e
|
|
;
|
|
;=============================================================================
|
|
|
|
(define-method initialize ((object <object>) initargs)
|
|
(%initialize-object object initargs))
|
|
|
|
(define-method initialize ((class <class>) initargs)
|
|
(next-method)
|
|
(let ((dslots (get-keyword :slots initargs '()))
|
|
(supers (get-keyword :dsupers initargs '()))
|
|
(env (get-keyword :environment initargs (global-environment))))
|
|
|
|
(slot-set! class 'name (get-keyword :name initargs '???))
|
|
(slot-set! class 'direct-supers supers)
|
|
(slot-set! class 'direct-slots dslots)
|
|
(slot-set! class 'direct-subclasses '())
|
|
(slot-set! class 'direct-methods '())
|
|
(slot-set! class 'cpl (compute-cpl class))
|
|
(slot-set! class 'redefined #f)
|
|
(slot-set! class 'environment env)
|
|
(let ((slots (%compute-slots class)))
|
|
(slot-set! class 'slots slots)
|
|
(slot-set! class 'nfields 0)
|
|
(slot-set! class 'getters-n-setters (compute-getters-n-setters class
|
|
slots
|
|
env)))
|
|
;; Update the "direct-subclasses" of each inherited classes
|
|
(for-each (lambda (x)
|
|
(slot-set! x 'direct-subclasses
|
|
(cons class (slot-ref x 'direct-subclasses))))
|
|
supers)
|
|
|
|
;; Build getters - setters - accessors
|
|
(compute-slot-accessors class dslots env)))
|
|
|
|
|
|
(define-method initialize ((generic <generic>) initargs)
|
|
(let ((previous-definition (get-keyword :default initargs #f)))
|
|
(next-method)
|
|
(slot-set! generic 'name (get-keyword :name initargs '???))
|
|
(slot-set! generic 'methods (if (is-a? previous-definition <procedure>)
|
|
(list (make <method>
|
|
:specializers <top>
|
|
:procedure
|
|
(lambda (nm . l)
|
|
(apply previous-definition
|
|
l))))
|
|
'()))))
|
|
|
|
(define-method initialize ((method <method>) initargs)
|
|
(next-method)
|
|
(slot-set! method 'generic-function (get-keyword :generic-function initargs #f))
|
|
(slot-set! method 'specializers (get-keyword :specializers initargs '()))
|
|
(slot-set! method 'procedure (get-keyword :procedure initargs (lambda l '()))))
|
|
|
|
|
|
;=============================================================================
|
|
;
|
|
; C h a n g e - c l a s s
|
|
;
|
|
;=============================================================================
|
|
|
|
(define (change-object-class old-instance old-class new-class)
|
|
(let ((new-instance (allocate-instance new-class ())))
|
|
;; Initalize the slot of the new instance
|
|
(for-each (lambda (slot)
|
|
(if (slot-exists-using-class? old-class old-instance slot)
|
|
;; Slot was present in old instance; set it
|
|
(if (slot-bound-using-class? old-class old-instance slot)
|
|
(slot-set-using-class!
|
|
new-class
|
|
new-instance
|
|
slot
|
|
(slot-ref-using-class old-class old-instance slot)))
|
|
;; slot was absent; initialize it with its default value
|
|
(let ((init (slot-init-function new-class slot)))
|
|
(if init
|
|
(slot-set-using-class!
|
|
new-class
|
|
new-instance
|
|
slot
|
|
(apply init '()))))))
|
|
(map slot-definition-name (class-slots new-class)))
|
|
;; Exchange old an new instance in place to keep pointers valids
|
|
(%modify-instance old-instance new-instance)
|
|
old-instance))
|
|
|
|
|
|
(define-method change-class ((old-instance <object>) (new-class <class>))
|
|
(change-object-class old-instance (class-of old-instance) new-class))
|
|
|
|
;=============================================================================
|
|
;
|
|
; M a k e
|
|
;
|
|
; A new definition which overwrite the previous one which was built-in
|
|
;
|
|
;=============================================================================
|
|
|
|
(define-method allocate-instance ((class <class>) initargs)
|
|
(%allocate-instance class))
|
|
|
|
(define-method make-instance ((class <class>) . initargs)
|
|
(let ((instance (allocate-instance class initargs)))
|
|
(initialize instance initargs)
|
|
instance))
|
|
|
|
(define make make-instance)
|
|
|
|
;=============================================================================
|
|
;
|
|
; a p p l y - g e n e r i c
|
|
;
|
|
; Protocol for calling standard generic functions.
|
|
; This protocol is not used for real <generic> functions (in this case we use
|
|
; a completely C hard-coded protocol).
|
|
; Apply-generic is used by STklos for calls to subclasses of <generic>.
|
|
;
|
|
; The code below is similar to the first MOP described in AMOP. In particular,
|
|
; it doesn't used the currified approach to gf call. There are 2 reasons for
|
|
; that:
|
|
; - the protocol below is exposed to mimic completely the one written in C
|
|
; - the currified protocol would be imho inefficient in C.
|
|
;=============================================================================
|
|
|
|
(define-method apply-generic ((gf <generic>) args)
|
|
(if (null? (slot-ref gf 'methods))
|
|
(no-method gf args))
|
|
(let ((methods (compute-applicable-methods gf args)))
|
|
(if methods
|
|
(apply-methods gf (sort-applicable-methods gf methods args) args)
|
|
(no-applicable-method gf args))))
|
|
|
|
(define-method compute-applicable-methods ((gf <generic>) args)
|
|
(apply find-method gf args))
|
|
|
|
(define-method sort-applicable-methods ((gf <generic>) methods args)
|
|
(let ((targs (map class-of args)))
|
|
(sort methods (lambda (m1 m2) (method-more-specific? m1 m2 targs)))))
|
|
|
|
(define-method method-more-specific? ((m1 <method>) (m2 <method>) targs)
|
|
(%method-more-specific? m1 m2 targs))
|
|
|
|
(define-method apply-method ((gf <generic>) methods build-next args)
|
|
(apply (method-procedure (car methods))
|
|
(build-next (cdr methods) args)
|
|
args))
|
|
|
|
(define-method apply-methods ((gf <generic>) (l <list>) args)
|
|
(letrec ((next (lambda (procs args)
|
|
(lambda new-args
|
|
(let ((a (if (null? new-args) args new-args)))
|
|
(if (null? procs)
|
|
(no-next-method gf a)
|
|
(apply-method gf procs next a)))))))
|
|
(apply-method gf l next args)))
|
|
|
|
;=============================================================================
|
|
;
|
|
; <Composite-metaclass> and <Active-metaclass>
|
|
;
|
|
;=============================================================================
|
|
|
|
(autoload "active-slot" <Active-metaclass>)
|
|
(autoload "composite-slot" <Composite-metaclass>)
|
|
(export <Composite-metaclass> <Active-metaclass>)
|
|
|
|
;=============================================================================
|
|
;
|
|
; T o o l s
|
|
;
|
|
;=============================================================================
|
|
|
|
(define (list2set l) ;; duplicate the standard list->set
|
|
(let Loop ((l l) (res '())) ;; function but using eq instead of eqv
|
|
(cond ;; which really sucks a lot, uselessly here
|
|
((null? l) res)
|
|
((memq (car l) res) (Loop (cdr l) res))
|
|
(else (Loop (cdr l) (cons (car l) res))))))
|
|
|
|
|
|
(define (class-subclasses c)
|
|
(letrec ((allsubs (lambda (c)
|
|
(cons c (mapappend allsubs (class-direct-subclasses c))))))
|
|
(list2set (cdr (allsubs c)))))
|
|
|
|
(define (class-methods c)
|
|
(list2set (mapappend class-direct-methods (cons c (class-subclasses c)))))
|
|
|
|
;;
|
|
;; Clos like SLOT-VALUE
|
|
;; Note: SLOT-VALUE is a gf whereas SLOT-REF and SLOT-SET! are functions.
|
|
;;
|
|
(define-method slot-value ((o <object>) s)
|
|
(slot-ref o s))
|
|
|
|
(define-method (setter slot-value) ((o <object>) s v)
|
|
(slot-set! o s v))
|
|
|
|
;=============================================================================
|
|
;
|
|
; Backward compatibility
|
|
;
|
|
;=============================================================================
|
|
(define class-cpl class-precedence-list) ; Don' use these
|
|
(define get-slot-allocation slot-definition-allocation) ; obolete defs
|
|
(define slot-definition-initform slot-definition-init-form) ; anymore
|
|
|
|
(export class-cpl get-slot-allocation slot-definition-initform)
|
|
|
|
(provide "stklos")
|
|
|