;;; -*-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)