1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
|
;;; -*-Scheme-*-
;;;
;;; A simple `OOPS' package
(require 'hack.la)
(provide 'oops)
(define class-size 5)
(define instance-size 3)
;;; Classes and instances are represented as vectors. The first
;;; two slots (tag and class-name) are common to classes and instances.
(define (tag v) (vector-ref v 0))
(define (set-tag! v t) (vector-set! v 0 t))
(define (class-name v) (vector-ref v 1))
(define (set-class-name! v n) (vector-set! v 1 n))
(define (class-instance-vars c) (vector-ref c 2))
(define (set-class-instance-vars! c v) (vector-set! c 2 v))
(define (class-env c) (vector-ref c 3))
(define (set-class-env! c e) (vector-set! c 3 e))
(define (class-super c) (vector-ref c 4))
(define (set-class-super! c s) (vector-set! c 4 s))
(define (instance-env i) (vector-ref i 2))
(define (set-instance-env! i e) (vector-set! i 2 e))
;;; Methods are bound in the class environment.
(define (method-known? method class)
(eval `(bound? ',method) (class-env class)))
(define (lookup-method method class)
(eval method (class-env class)))
(define (class? c)
(and (vector? c) (= (vector-length c) class-size) (eq? (tag c) 'class)))
(define (check-class sym c)
(if (not (class? c))
(error sym "argument is not a class")))
(define (instance? i)
(and (vector? i) (= (vector-length i) instance-size)
(eq? (tag i) 'instance)))
(define (check-instance sym i)
(if (not (instance? i))
(error sym "argument is not an instance")))
;;; Evaluate `body' within the scope of instance `i'.
(define-macro (with-instance i . body)
`(eval '(begin ,@body) (instance-env ,i)))
;;; Set a variable in an instance.
(define (instance-set! instance var val)
(eval `(set! ,var ',val) (instance-env instance)))
;;; Set a class variable when no instance is available.
(define (class-set! class var val)
(eval `(set! ,var ',val) (class-env class)))
;;; Convert a class variable spec into a binding suitable for a `let'.
(define (make-binding var)
(if (symbol? var)
(list var '()) ; No initializer given; use ()
var)) ; Initializer has been specified; leave alone
;;; Check whether the elements of `vars' are either a symbol or
;;; of the form (symbol initializer).
(define (check-vars vars)
(if (not (null? vars))
(if (not (or (symbol? (car vars))
(and (pair? (car vars)) (= (length (car vars)) 2)
(symbol? (caar vars)))))
(error 'define-class "bad variable spec: ~s" (car vars))
(check-vars (cdr vars)))))
;;; Check whether the class var spec `v' is already a member of
;;; the list `l'. If this is the case, check whether the initializers
;;; are identical.
(define (find-matching-var l v)
(cond
((null? l) #f)
((eq? (caar l) (car v))
(if (not (equal? (cdar l) (cdr v)))
(error 'define-class "initializer mismatch: ~s and ~s"
(car l) v)
#t))
(else (find-matching-var (cdr l) v))))
;;; Same as above, but don't check initializer.
(define (find-var l v)
(cond
((null? l) #f)
((eq? (caar l) (car v)) #t)
(else (find-var (cdr l) v))))
;;; Create a new list of class var specs by discarding all variables
;;; from `b' that are already a member of `a' (with identical initializers).
(define (join-vars a b)
(cond
((null? b) a)
((find-matching-var a (car b)) (join-vars a (cdr b)))
(else (join-vars (cons (car b) a) (cdr b)))))
;;; The syntax is as follows:
;;; (define-class class-name . options)
;;; options are: (super-class class-name)
;;; (class-vars . var-specs)
;;; (instance-vars . var-specs)
;;; each var-spec is either a symbol or (symbol initializer).
(define-macro (define-class name . args)
(let ((class-vars) (instance-vars (list (make-binding 'self)))
(super) (super-class-env))
(do ((a args (cdr a))) ((null? a))
(cond
((not (pair? (car a)))
(error 'define-class "bad argument: ~s" (car a)))
((eq? (caar a) 'class-vars)
(check-vars (cdar a))
(set! class-vars (cdar a)))
((eq? (caar a) 'instance-vars)
(check-vars (cdar a))
(set! instance-vars (append instance-vars
(map make-binding (cdar a)))))
((eq? (caar a) 'super-class)
(if (> (length (cdar a)) 1)
(error 'define-class "only one super-class allowed"))
(set! super (cadar a)))
(else
(error 'define-class "bad keyword: ~s" (caar a)))))
(if (not (null? super))
(let ((class (eval super)))
(set! super-class-env (class-env class))
(set! instance-vars (join-vars (class-instance-vars class)
instance-vars)))
(set! super-class-env (the-environment)))
`(define ,name
(let ((c (make-vector class-size '())))
(set-tag! c 'class)
(set-class-name! c ',name)
(set-class-instance-vars! c ',instance-vars)
(set-class-env! c (eval `(let* ,(map make-binding ',class-vars)
(the-environment))
,super-class-env))
(set-class-super! c ',super)
c))))
(define-macro (define-method class lambda-list . body)
(if (not (pair? lambda-list))
(error 'define-method "bad lambda list"))
`(begin
(check-class 'define-method ,class)
(let ((env (class-env ,class))
(method (car ',lambda-list))
(args (cdr ',lambda-list))
(forms ',body))
(eval `(define ,method (lambda ,args ,@forms)) env)
#v)))
;;; All arguments of the form (instance-var init-value) are used
;;; to initialize the specified instance variable; then an
;;; initialize-instance message is sent with all remaining
;;; arguments.
(define-macro (make-instance class . args)
`(begin
(check-class 'make-instance ,class)
(let* ((e (the-environment))
(i (make-vector instance-size #f))
(class-env (class-env ,class))
(instance-vars (class-instance-vars ,class)))
(set-tag! i 'instance)
(set-class-name! i ',class)
(set-instance-env! i (eval `(let* ,instance-vars (the-environment))
class-env))
(eval `(set! self ',i) (instance-env i))
(init-instance ',args ,class i e)
i)))
(define (init-instance args class instance env)
(let ((other-args))
(do ((a args (cdr a))) ((null? a))
(if (and (pair? (car a)) (= (length (car a)) 2)
(find-var (class-instance-vars class) (car a)))
(instance-set! instance (caar a) (eval (cadar a) env))
(set! other-args (cons (eval (car a) env) other-args))))
(call-init-methods class instance (reverse! other-args))))
;;; Call all initialize-instance methods in super-class to sub-class
;;; order in the environment of `instance' with arguments `args'.
(define (call-init-methods class instance args)
(let ((called '()))
(let loop ((class class))
(if (not (null? (class-super class)))
(loop (eval (class-super class))))
(if (method-known? 'initialize-instance class)
(let ((method (lookup-method 'initialize-instance class)))
(if (not (memq method called))
(begin
(apply (hack-procedure-environment!
method (instance-env instance))
args)
(set! called (cons method called)))))))))
(define (send instance msg . args)
(check-instance 'send instance)
(let ((class (eval (class-name instance))))
(if (not (method-known? msg class))
(error 'send "message not understood: ~s" `(,msg ,@args))
(apply (hack-procedure-environment! (lookup-method msg class)
(instance-env instance))
args))))
;;; If the message is not understood, return #f. Otherwise return
;;; a list of one element, the result of the method.
(define (send-if-handles instance msg . args)
(check-instance 'send-if-handles instance)
(let ((class (eval (class-name instance))))
(if (not (method-known? msg class))
#f
(list (apply (hack-procedure-environment! (lookup-method msg class)
(instance-env instance))
args)))))
(define (describe-class c)
(check-class 'describe-class c)
(format #t "Class name: ~s~%" (class-name c))
(format #t "Superclass: ~s~%"
(if (not (null? (class-super c)))
(class-super c)
'None))
(format #t "Instancevars: ")
(do ((v (class-instance-vars c) (cdr v)) (space #f #t)) ((null? v))
(if space
(format #t " "))
(print (cons (caar v) (cadar v))))
(format #t "Classvars/Methods: ")
(define v (car (environment->list (class-env c))))
(if (not (null? v))
(do ((f v (cdr f)) (space #f #t)) ((null? f))
(if space
(format #t " "))
(print (car f)))
(print 'None))
#v)
(define (describe-instance i)
(check-instance 'describe-instance i)
(format #t "Instance of: ~s~%" (class-name i))
(format #t "Instancevars: ")
(do ((f (car (environment->list (instance-env i))) (cdr f))
(space #f #t)) ((null? f))
(if space
(format #t " "))
(print (car f)))
#v)
|