eoops.el

Last edit

Summary: Rückgängig gemacht auf den Stand vom 2013-08-24 11:21 UTC

Changed:

< (re-WikiModes-backward "^(class ")

to

> (re-search-backward "^(class ")


Download

;;; eoops.el --- An Object Oriented Programming System in Elisp

;; Copyright (C) 1992  Twin Sun, Inc.
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

(provide 'eoops)
(load-library "bytecomp") ;; bytecomp has no provide

(defvar eoops:class-path
  "/tmp/elisp/classes" "Where the eoops class source is stored.")

(defun map:new ()
  "Returns a new map.  We implement a map as a pair,
with head the symbol `map and tail a Lisp association list.  Here,
an association list is a possibly empty list of lists, each with first
element <argument> and second element <image>.  Each argument and
image is an arbitrary Lisp object."
  (list 'map))

(defun map:pairs (map)
  "Returns the association list of map."
  (cdr map))

(defun map:2nd (list)
  "is just like Common-Lisp's cadr."
  (car (cdr list)))

(defun map:get (map argument)
  "Returns the image of MAP at ARGUMENT or nil if argument is not in
the map's domain."
  (map:2nd (assoc argument (map:pairs map))))

(defun map:set (map argument image)
  "Updates the value of MAP at ARGUMENT to be IMAGE and returns the image."
  (let ((pair (assoc argument (map:pairs map))))
    (if pair
        (setcar (cdr pair) image)
      (setcdr map (cons (list argument image) 
                        (map:pairs map)))))
  image)

(defun map:range (map)
  "Returns the range of *map,* i.e., a list of the map's images."
  (mapcar 'map:2nd (map:pairs map)))

(defvar eoops:classes (map:new) "The eoops class database.")

(defun eoops:symbol-concat (&rest s)
  "Returns a symbol whose print-string is the concatenation of the
list of SYMBOLS/STRINGS."
  (let ((string '(lambda (x) (format "%s" x))))
    (intern (apply 'concat (mapcar string s)))))

(defmacro ce:for (symbol list &rest body)
  "Binds VARIABLE to successive car's of LIST and evaluates BODY.
returning the list of results.  This is just a convenient abbreviation
of mapcar.  The 'ce:' prefix denotes our library of Common
Emacs-lisp functions and macros."
  (` (mapcar '(lambda ((, symbol)) (,@ body)) 
            (, list))))

(defmacro ce:let (bindings &rest body)
  "This is a destructuring let.  (let BINDINGS . BODY) binds
BINDINGS, then evaluates forms in BODY, returning the value of the
last form.  BINDINGS is a list; each element is either a symbol
bound to nil or a list (pattern value) binding the symbols in
pattern to corresponding value's.  Each value can refer to
symbols already bound in bindings. For example:

   (ce:let (((first second . tail) '(1 2 3 4)))
      (list first second tail))
   =>  (1 2 (3 4))."

  (` (let* (, (ce:bindings bindings)) (,@ body))))

(defun ce:bindings (bindings)
  "Given PATTERN, returns a list of bindings.  PATTERN
is a tree of symbols.  [ce:bindings] returns a list ((symbol
access)*) where SYMBOL occurs in the PATTERN and ACCESS is
an accessing expression of nested calls to car and cdr."
  (let ((s 'ce:let)
        (b '(lambda (pattern path)
              (cond
               ((null pattern)
                nil)
               ((symbolp pattern)
                (list (list pattern path)))
               ((consp pattern)
                (append 
                  (funcall b (car pattern) 
                             (list 'car path))
                  (funcall b (cdr pattern) 
                             (list 'cdr path))))))))
    (apply 
      'append 
      (ce:for bind bindings
        (if (and (consp bind) (consp (car bind)))
            (cons (list s (nth 1 bind)) 
                  (funcall b (car bind) s))
          (list bind))))))

(defmacro class (nc np slots &rest methods)
  "This macro creates a new class record, fills in its fields, and
then compiles the class.  The class record remembers the class's name,
slots, methods, and other information."
  (let ((c (or (map:get eoops:classes nc) 
               (map:new))))
    (let* ((np (map:get c 'parent))
           (p  (map:get eoops:classes np))
           (s  (map:get p 'children)))
      (if p (map:set p 'children (delq nc s))))
    (eoops:require-class np)
    (let* ((p (map:get eoops:classes np))
           (s (map:get p 'children)))
      (if p (map:set p 'children (cons nc s))))
    (map:set c 'parent np)
    (map:set c 'slots slots)
    (map:set c 'name nc)
    (map:set c 'compiled nil)
    (map:set c 'methods
	     (if (stringp (car methods)) 
		 (cdr methods)
	       methods))
    (map:set eoops:classes nc c)
    (eoops:compile-class c)
    (map:set c 'modtime 
             (eoops:class-mod-time nc 'obj)))
  (list 'quote nc))

(defmacro new (nc)
  "Calls the function [eoops:new] after quoting the CLASS argument."
  (` (eoops:new (quote (, nc)))))

(defmacro @ (&rest arguments)
  "Send eoops message to self."
  (` ($ self (,@ arguments))))

(defmacro $ (receiver selector &rest args)
  "Implements fast message passing.  The $ macro implements a
partial method-lookup at compile-time, by precomputing the hash code
for the SELECTOR.  Then $ constructs code to dispatch on the class
of the *receiver* at run-time.  There are three cases which are
described in the Eoops paper."
  (let ((h (eoops:hash selector))
        (s (list 'quote selector)))
    (cond 
     ((eq receiver 'super)
      (` (let ((super 
                (map:get (map:get 
                           eoops:classes 
                           '(, eoops:super-class))
                         'compiled)))
           (funcall (cdr (assq '(, selector) 
                               (aref super (, h))))
                    self
                    (,@ args)))))
     ((symbolp receiver)
      (` (funcall 
          (cdr (assq (, s) (aref 
                             (aref (, receiver) 0) 
                             (, h))))       
          (, receiver)
          (,@ args))))
     (t (` (let ((eoops:receiver (, receiver)))
             ($ eoops:receiver 
                (, selector) 
                (,@ args))))))))

(defun eoops:compile-class (c)
  "Compile class C.  Writes compiled version to disk."
  (let* ((np (map:get c 'parent))
         (p  (map:get eoops:classes np))
         (pcv (map:get p 'compiled))
         (nc (map:get c 'name))
         (cv (make-vector eoops:vtable-size nil)))
    (message "eoops compiling %s" nc)
    (aset cv 0 nc)
    (let ((methods (map:get c 'methods))
          (eoops:super-class np))
      (ce:for method methods
        (ce:let ((((selector . parms) . body) method)
                 (doc 
                  (format "(%s %s) " nc selector))
                 (body 
                  (if (stringp (car body))
                      (cons (concat doc (car body)) 
                            (cdr body))
                    (cons doc body)))
                 (code
                  (` (lambda (, parms) (,@ body)))))
;          (message "Compiling %s %s..." nc selector)
          (eoops:store-method cv selector code))))
    (map:set c 'compiled cv)
    (eoops:write-class c)
    (eoops:compile-slot-accessors nc c cv pcv)
    (map:set c 'compiled cv)
    (let ((pv (map:get p 'compiled)))
      (map:set c 'compiled
               (eoops:inherit-behavior pv cv)))
    (mapcar '(lambda (ns) (eoops:compile-class
                           (map:get eoops:classes ns)))
            (map:get c 'children))))

(defun eoops:compile-slot-accessors (nc c cv pcv)
  "Compiles the slot accessors for class named NC, defined as C,
stores result in class vector CV, inherits from PCV."
  (let ((i (if pcv (aref pcv 1) 1))
	(slots (map:get c 'slots)))
    (aset cv 1 (+ i (length slots)))
    (ce:for slot slots
      (let* ((get (if (consp slot) (car slot) slot))
	     (set (eoops:symbol-concat get ":"))
	     (doc (if (consp slot) (nth 1 slot) ""))
	     (gdoc (format "(%s %s) %s" nc get doc))
	     (sdoc (format "(%s %s) %s" nc set doc)))
	(eoops:store-method
	 cv get (` (lambda () (, gdoc) (aref self (, i)))))
	(eoops:store-method
	 cv set
	 (` (lambda (v) (, sdoc) (aset self (, i) v))))
	(setq i (1+ i))))))

(defun eoops:new (nc)
  "Checks, at run-time, that the specified class, NC, is loaded.  If
it is not currently loaded, [eoops:require-class] is invoked to load
it.  Normally, a class is repeatedly instantiated but only the first
invocation of eoops:new may require the expense of loading the class.
After the class record, *c*, is retrieved, the *cv* is retrieved, the
instance vector is created and initialized, an init message is sent to
the new instance, and finally the new initialized instance is
returned."
  (let* ((c (or (map:get eoops:classes nc)
                (progn (eoops:require-class nc)
                       (map:get eoops:classes nc))))
         (cv (map:get c 'compiled))
         (self (make-vector (aref cv 1) nil)))
    (aset self 0 cv)
    ($ self init)
    self))

;; The first two elements of a class vector are the class's name and
;; instance vector size.  The rest of the class vector elements
;; implement a hash table for the methods for the selectors to which
;; the class responds.  The size of the hash table is stored in the
;; constant htable-size.  Therefore, the length of a class vector,
;; vtable-size, is 2 + htable-size. 

(defconst eoops:htable-size 23 "Size of hash table.")

(defconst eoops:vtable-size (+ 2 eoops:htable-size) "Size of class vector.")

(defun eoops:hash (symbol)
  "Returns a relatively unique integer for SYMBOL, between 2 and htable-size."
  (let* ((s (symbol-name symbol))
         (i (length s))
         (r 0))
    (while (< 0 i)
      (setq i (1- i))
      (setq r (+ r r (aref s i))))
    (+ 2 (% (max r (- r)) eoops:htable-size))))

(defun eoops:store-method (cv selector method)
  "Stores in CV's hash bucket at SELECTOR a byte-compiled METHOD."
  (let* ((f (byte-compile-lambda
              (eoops:add-self method)))
         (h (eoops:hash selector))
         (bucket (aref cv h)))
    (aset cv h 
      (eoops:update-cv-bucket bucket selector f))))

(defun eoops:add-self (f)
  "Given a lambda expression F, prepends the symbol 'self to the
arguments in f.  This is required since $ calls a method with the
receiver as an additional argument prepended to those specified in a
method definition."
  (ce:let (((lambda arguments . body) f))
    (` (lambda (self (,@ arguments)) (,@ body)))))

(defun eoops:inherit-behavior (pv cv)
  "Copies the methods from the parent-vector to the class-vector.
Called when the compiled code is loaded, this implements load-time
inheritance."
  (let ((ncv (make-vector eoops:vtable-size nil))
        (i eoops:vtable-size))
    (aset ncv 0 (aref cv 0))
    (aset ncv 1 (aref cv 1))
    (while (< 2 i)
      (setq i (1- i))
      (if pv (aset ncv i (copy-alist (aref pv i))))
      (ce:for entry (aref cv i)
        (aset ncv i (eoops:update-cv-bucket 
                      (aref ncv i)
                      (car entry)
                      (cdr entry)))))
    ncv))

(defun eoops:update-cv-bucket (bucket selector f)
  "Updates the hash table BUCKET's alist at index SELECTOR, to contain
the lambda expression F."
  (let ((pair (assq selector bucket)))
    (cond (pair (rplacd pair f) bucket)
          (t (cons (cons selector f) bucket)))))

(defun eoops:require-class (nc)
  "Loads the specified class, NC, when either the class has never been
loaded or when its sources, if available, are newer than the object
file."
  (cond
   ((not nc) t)
   ((and (map:get eoops:classes nc)
	 (eq (eoops:file-type-to-load nc) 'obj))
    t)
   (t (eoops:load-file nc))))

(defun eoops:load-file (nc)
  "Loads the class, NC, from disk."
  (let ((file-type (eoops:file-type-to-load nc)))
    (if (eq file-type 'src) (eoops:delete-locks nc))
    (message "eoops loading %s" nc)
    (load (eoops:class-file-name nc file-type) nil 'nomessage 'nosuffix)))

(defun eoops:load-class (nc c)
  "Stores the class record C in eoops:classes under the name NC.  This
function is explicitly invoked in a class's object file.  Loading a
class enforces that the parent is loaded.  The parent's class vector
is then copied and merged with the current class's class vector by
eoops:inherit-behavior.  This inheritance step is done at load time so
that a new object file need not be created and written for a class
when an ancestor is modified."
  (if (eq 'src (eoops:file-type-to-load nc))
      (eoops:load-file nc)
    (let ((np (map:get c 'parent)))
      (eoops:require-class np)
      (let* ((p  (map:get eoops:classes np))
	     (pv (map:get p 'compiled))
	     (cv (map:get c 'compiled))
	     (s (map:get p 'children)))
	(map:set eoops:classes nc c)
	(map:set c 'modtime 
		 (eoops:class-mod-time nc 'obj))
	(eoops:compile-slot-accessors nc c cv pv)
	(map:set c 'compiled 
		 (eoops:inherit-behavior pv cv))
	(if p (map:set p 'children (cons nc s)))))))

(defun eoops:write-class (c)
  "Writes the compiled class C, whose class name is nc, to the file
nc.elc in the directory that is the value of eoops:class-path.  The
resulting file, when loaded into Emacs, will install class c.  Each
file contains only one class and each class has to be in exactly one
file.  However, a file may include other expressions.  Therefore,
eoops:write-class first byte-compiles the source file and then
replaces the item corresponding to the class definition by the printed
representation of the class.  Setting print-depth to nil makes sure
that the prin1 prints the complete class.  Since a class's children
field is a list of its currently loaded subclasses, this field is set
to nil before printing."
  (let* ((standard-output 
           (get-buffer-create "*Compiled*"))
         (print-depth nil)
         (nc (map:get c 'name))
         (src-name (eoops:class-file-name nc 'src))
         (obj-name (eoops:class-file-name nc 'obj))
         (prefix 
           (format "(eoops:load-class '%s '" nc))
         (suffix ")"))
    (cond
     ((not (file-exists-p obj-name))
;      (message "Writing class %s..." nc)
      (byte-compile-file src-name)
      (set-buffer standard-output)
      (erase-buffer)
      (insert-file obj-name)
      (goto-char (point-max))
      (re-search-backward "^(class ")
      (delete-region (point)
		     (progn (forward-sexp) (point)))
      (insert prefix)
      (let ((s (map:get c 'children)))
	(map:set c 'children nil)
	(prin1 c)
	(map:set c 'children s)
	(insert suffix))
      (let ((make-backup-files nil))
	(write-file obj-name))
;      (message "Writing class %s...done" nc)
      ))
    (kill-buffer standard-output)))

(defun eoops:class-file-name (nc type)
  "Returns the full path name of a file storing the class NC.  TYPE
can either be 'src or 'obj."
  (format (cond ((eq type 'src) "%s/%s.el")
                ((eq type 'obj) "%s/%s.elc"))
          eoops:class-path nc))

(defun eoops:class-mod-time (nc type)
  "Returns the last modification time of the source or object file
corresponding to the class NC."
  (nth 5 (file-attributes 
           (eoops:class-file-name nc type))))

(defun eoops:time-newer (ta tb)
  "Compares two time values returned by eoops:class-mod-time and
returns true if its first argument is greater than its second."
  (ce:let (((ta1 ta2) ta)
           ((tb1 tb2) tb))
    (or (> ta1 tb1)
        (and (= ta1 tb1)
             (> ta2 tb2)))))

(defun eoops:file-type-to-load (nc)
  "Returns which file type should be loaded."
  (let ((s-time (eoops:class-mod-time nc 'src))
	(o-time (eoops:class-mod-time nc 'obj)))
    (cond
     ((not nc) nil)
     ((and o-time s-time 
	   (eoops:time-newer o-time s-time))
      'obj)
     ((and o-time (not s-time)) 'obj)
     (t 'src))))

(defun eoops:delete-locks (nc)
  "'Locks' are used to attempt to avoid disk contention when more than one
person tries to compile the same class simultaneously."
  (let ((fname (eoops:class-file-name nc 'obj)))
    (condition-case err
	(delete-file fname)
      (error nil))))