;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: KR; Base: 10 -*- ;; The Garnet User Interface Development Environment ;; ;; This code was written as part of the Garnet project at Carnegie ;; Mellon University, and has been placed in the public domain. ;;; Slot bits assignment: ;; 0-9 types encoding ;; - inherited ;; - is-parent ;; - constant-slot ;; - is-update-slot ;; - local-only-slot ; not implemented ;; - parameter-slot ; not implemented (in-package :kr) (declaim (inline clear-one-slot)) (defun clear-one-slot (schema slot entry) "Completely clear a slot, including dependencies, inherited, etc... BUT... Leave around the declarations (constant, type, update,...)" (locally (declare #.*special-kr-optimization*) (let ((the-entry (or entry (slot-accessor schema slot)))) (when the-entry (setf (sl-value the-entry) *no-value* (sl-bits the-entry) (logand (sl-bits the-entry) *clear-slot-mask*)))))) (declaim (inline clear-schema-slots)) (defun clear-schema-slots (schema) "Completely clear ALL the slots in the ." (locally (declare #.*special-kr-optimization*) (clrhash (schema-bins schema)))) (defun value-fn (schema slot) "Does the actual work of G-VALUE." (g-value-body schema slot T T)) (defun g-local-value-fn (schema slot) "Similar to g-value-fn, but no inheritance." (g-value-body schema slot NIL T)) (let ((list-of-one (list nil))) (defun get-dependents (schema slot) "RETURNS: the formulas which depend on the of the ." (let ((value (slot-dependents (slot-accessor schema slot)))) (if (listp value) value (progn (setf (car list-of-one) value) list-of-one))))) (declaim (inline get-lambda)) (defun get-lambda (formula) "Returns the lambda expression in a formula, or NIL." (when (formula-p formula) (a-formula-lambda formula))) (defun enable-a-demon (demon) "Turns ON a demon if it was turned off. If all demons are currently disabled, the variable *demons-disabled* is made of the form (T demon), where the names following the T are, in fact, enabled." (cond ((eq *demons-disabled* T) (list T demon)) ((eq *demons-disabled* NIL)) ; nothing is disabled ((listp *demons-disabled*) ;; A list (if (eq (car *demons-disabled*) T) ;; Special format (if (memberq demon (cdr *demons-disabled*)) *demons-disabled* ; nothing is needed (cons T (cons demon (cdr *demons-disabled*)))) ;; Normal format (if (memberq demon *demons-disabled*) (remove demon *demons-disabled*) *demons-disabled*))) ((eq demon *demons-disabled*) NIL) (t *demons-disabled*))) (defun disable-a-demon (demon) (if (eq *demons-disabled* T) T ; everything is already turned off (if (eq *demons-disabled* NIL) demon (if (listp *demons-disabled*) ;; A list (if (eq (car *demons-disabled*) T) ;; Special format used by with-demon-enable (if (memberq demon *demons-disabled*) (let ((new-value (delete demon *demons-disabled*))) (if (null (cdr new-value)) T new-value)) ;; Already disabled *demons-disabled*) ;; Normal format (cons demon *demons-disabled*)) ;; A single value - make a list. (list demon *demons-disabled*))))) (defun demon-is-disabled (demon) "Is the currently enabled?" (if (listp *demons-disabled*) (if (eq (car *demons-disabled*) T) ;; Special format (not (memberq demon (cdr *demons-disabled*))) ;; Normal format (memberq demon *demons-disabled*)) (eq demon *demons-disabled*))) (defun g-value-inherit-values (schema slot is-leaf slot-structure) "Search up the tree for inherited slot. RETURNS: the inherited value, or NIL." (declare (ftype (function (t &optional t) t) formula-fn)) (let (has-parents) (when (a-local-only-slot slot) ; These CANNOT be inherited. (return-from g-value-inherit-values NIL)) (dolist (relation *inheritance-relations*) (dolist (parent (if (eq relation :IS-A) (get-local-value schema :IS-A) (get-local-value schema relation))) (setf has-parents T) (let ((entry (slot-accessor parent slot)) (value *no-value*) bits ; parent bits (intermediate-constant NIL)) (when entry (setf value (sl-value entry)) (when (is-constant (sl-bits entry)) (setf intermediate-constant T))) (if (eq value *no-value*) ;; Attempt to inherit from its ancestors. (multiple-value-setq (value bits) (g-value-inherit-values parent slot NIL nil)) ;; If value, just set bits. (setf bits (sl-bits entry))) (unless (eq value *no-value*) (if (and bits (is-parent bits)) ;; Clear the parent bit, since we will set the child. (setf bits (logand bits *not-parent-mask*)) ;; Set the bit in the parent which says that the value was ;; inherited by someone. (if entry ;; Destructively set the bits. (setf (sl-bits entry) (logior bits *is-parent-mask*)) (set-slot-accessor parent slot value (logior bits *is-parent-mask*) nil))) ;; Copy the value down to the inheriting slot, unless the value ;; contains a formula. (let ((was-formula (formula-p value))) (when was-formula ;; Inherit the formula, making a copy of it. (setf value (formula-fn value (a-formula-cached-value value))) (setf (a-formula-schema value) schema) (setf (a-formula-slot value) slot) (set-cache-is-valid value NIL)) ;; Copy down, mark as inherited if inherited (when (and is-leaf slot-structure) ; slot had constant bit (setf bits (logior bits (sl-bits slot-structure)))) (setf bits (logior *inherited-mask* bits #+TEST (logand bits *not-parent-constant-mask*))) (when intermediate-constant (setf bits (logior *constant-mask* bits))) (set-slot-accessor schema slot value bits (slot-dependents slot-structure))) (return-from g-value-inherit-values (values value bits)))))) ;; We didn't find anything, so return an appropriate null value and set ;; the local cache (even though we have no value) to avoid further ;; inheritance search. (set-slot-accessor schema slot (if has-parents NIL *no-value*) (cond (is-leaf (if slot-structure (logior *inherited-mask* (sl-bits slot-structure)) *inherited-mask*)) (has-parents *inherited-parent-mask*) (t ; top-level, no parents *is-parent-mask*)) (slot-dependents slot-structure)) *no-value*)) ;; G-CACHED-VALUE ;; (declaim (inline g-cached-value)) (defun g-cached-value (schema slot) "Returns the value of the in the . If this is a formula, it returns the cached value of the formula, without ever recomputing the formula." ;; Note use of GET-VALUE (let ((g-cached-value-val (get-value schema slot))) (if (formula-p g-cached-value-val) (cached-value g-cached-value-val) g-cached-value-val))) (defun g-value-no-copy (schema slot &optional skip-local) "This is a specialized function which does inheritance but does NOT copy values down. It is used by the :INITIALIZE method, which is called exactly once per object and should NOT copy down anything (since the method will never be used again)." (unless skip-local ;; Is there a local value? (let ((value (slot-accessor schema slot))) (when value (return-from g-value-no-copy (sl-value value))))) ;; Now try inherited values. (dolist (relation *inheritance-relations*) (dolist (*schema-self* (if (eq relation :IS-A) (get-local-value schema :IS-A) (get-local-value schema relation))) (unless (eq *schema-self* schema) ; avoid infinite loops! (let ((value (g-value-no-copy *schema-self* slot))) (when value (return-from g-value-no-copy value))))))) ;;; PRINTING AND DEBUGGING (declaim (fixnum *debug-names-length* *debug-index*)) (defparameter *debug-names-length* 500) (defvar *debug-names* (make-array *debug-names-length* :initial-element nil)) (defvar *debug-index* -1) (defvar *intern-unnamed-schemata* T "This variable may be set to NIL to prevent PS from automatically creating any unnamed schemata it prints out.") (defun cache-schema-name (schema name) "This does not cause any creation of symbols. It simply records the schema in an array, thus creating a semi-permanent way to refer to a schema." (unless (find-if #'(lambda (x) (and x (eql (schema-name x) name))) *debug-names*) ;; A new schema. Store it in the next position (cycle if ;; we reach the end of the array). (setf (aref *debug-names* (setf *debug-index* (mod (incf *debug-index*) *debug-names-length*))) schema))) ;; (defun make-new-schema-name (schema name) "Creates symbols for all automatic schema names that happen to be printed out." (let* ((debug-package (find-package "KR-DEBUG")) parent (symbol (intern (cond ((stringp name) ;; a name-prefix schema (format nil "~A-~D" name (incf *schema-counter*))) ((setf parent (if (formula-p schema) (a-formula-is-a schema) (when (not-deleted-p schema) (car (get-local-value schema :IS-A))))) (let ((parent-name (when parent (schema-name parent)))) (when (or (integerp parent-name) (stringp parent-name)) ;; Parent is unnamed yet - force a name. (with-output-to-string (bit-bucket) (print-the-schema parent bit-bucket 0)) (setf parent-name (schema-name parent))) (format nil "~A-~D" parent-name name))) (t (format nil "~C~D" (if (formula-p schema) #\F #\S) name))) debug-package))) (set symbol schema) (setf (schema-name schema) symbol) (export symbol debug-package))) (defun print-the-slot (slot stream level) (declare (ignore level)) (format stream "" (full-sl-dependents slot)) (format stream ">"))) (defun print-the-schema (schema stream level) (declare (ignore level)) (let ((name (schema-name schema)) (destroyed (not (not-deleted-p schema)))) ;; This version is for debugging. Record the latest schemata in the ;; array. (cond ((or (integerp name) (stringp name)) ;; This is a nameless schema. Print it out, and record it in the ;; debugging array. (when *intern-unnamed-schemata* (make-new-schema-name schema name)) (cache-schema-name schema name) ;; This gives control over whether unnamed schemata are interned. (setf name (schema-name schema))) ((null name) ;; This was a deleted schema (setf name '*DESTROYED*))) (when destroyed (format stream "*DESTROYED*(was ")) (if *print-as-structure* (progn (format stream "#k<~S" name) (dolist (slot *print-structure-slots*) (let ((value (g-value schema slot))) (when value (format stream " (~S ~S)" slot value)))) (format stream ">") (when destroyed (format stream ")"))) (progn (format stream "~S" name) (when destroyed (format stream ")")))))) (defun name-for-schema (schema) "Given a schema, returns its printable name as a string. The string CANNOT be destructively modified. Note that this returns the pure name, without the #k<> notation." (let ((name (schema-name schema))) (when (or (integerp name) (stringp name)) ;; This is a nameless schema. Print it out, and record it in the ;; debugging array. (when *intern-unnamed-schemata* (make-new-schema-name schema name)) (cache-schema-name schema name) ;; This gives control over whether unnamed schemata are interned. (setf name (schema-name schema))) (symbol-name name))) (defun s (number) "This is a debugging function which returns a schema, given its internal number. It only works if the schema was printed out rather recently, i.e., if it is contained in the temporary array of names." (setf number (format nil "~D" number)) (find-if #'(lambda (x) (and x (symbolp (schema-name x)) (do* ((name (symbol-name (schema-name x))) (i (1- (length name)) (1- i)) (j (1- (length number)) (1- j))) ((minusp j) (unless (digit-char-p (schar name i)) x)) (unless (char= (schar name i) (schar number j)) (return nil))))) *debug-names*)) ;;; RELATIONS (defun unlink-one-value (schema slot value) ; e.g., child :is-a parent "Remove the inverse link from to , following the inverse of ." (let ((inverse (cadr (assocq slot *relations*)))) ; e.g., is-a-inv (when inverse ;; If the relation has an INVERSE slot, remove from the ;; inverse slot. (let ((entry (slot-accessor value inverse)) ; e.g., A child B values) (when entry (setf values (sl-value entry)) (if (eq (car values) schema) ;; is first in the inverse list (set-slot-accessor value inverse (delete schema values) (sl-bits entry) (slot-dependents entry)) ;; just do a destructive operation (setf (cdr values) (delete schema (cdr values))))))))) (defun unlink-all-values (schema slot) "Same as above, but unlinks all schemata that are in ." (let ((inverse (cadr (assocq slot *relations*)))) (when inverse (let ((entry (if (eq slot :IS-A) (slot-accessor schema :IS-A) (if (eq slot :IS-A-INV) (slot-accessor schema :IS-A-INV) (slot-accessor schema slot))))) (when entry (dolist (parent (sl-value entry)) (when (not-deleted-p parent) ; parent is not destroyed ;; If the terminal has an INVERSE slot, remove from the ;; inverse slot. (let ((entry (if (eq inverse :is-a-inv) (slot-accessor parent :is-a-inv) ; e.g., A child B (slot-accessor parent inverse))) values) (when entry (setf values (sl-value entry)) (if (eq (car values) schema) (pop (sl-value entry)) (setf (cdr values) (delete schema (cdr values))))))))))))) (defun link-in-relation (schema slot values) "Since the are being added to , see if we need to put in an inverse link to from each of the . This happens when is a relation with an inverse." (let ((inverse (if (eq slot :is-a) :is-a-inv (cadr (assocq slot *relations*))))) (when inverse ;; is a list: cycle through them all (dolist (value values) (let* ((entry (if (eq slot :is-a) (slot-accessor value :is-a-inv) (slot-accessor value inverse))) (previous-values (when entry (sl-value entry)))) (if entry ;; Create the back-link. We use primitives here to avoid looping. (if (or *schema-is-new* (not (memberq schema previous-values))) ;; Handle an important special case efficiently. (if (eq (sl-value entry) *no-value*) ;; There was no value after all! (setf (sl-value entry) (list schema)) ;; There were real values. (push schema (sl-value entry)))) ;; There was no inverse in the parent yet. (set-slot-accessor value inverse (list schema) *local-mask* nil))))))) (defun check-relation-slot (schema slot values) "We are setting the (a relation) to . Check that the latter contains valid relation entries. RETURNS: (or a list of a single value, if is not a list) if success; *no-value* if failure." (unless (listp values) (format t "S-VALUE: relation ~s in schema ~S should be given a list of values!~%" slot schema) (if (schema-p values) (setf values (list values)) ; go ahead, use anyway. (return-from check-relation-slot *no-value*))) (dolist (value values) (unless (is-schema value) (when-debug (format t "S-VALUE: value ~s for relation ~s in ~s is not a schema! Ignored.~%" value slot schema)) (return-from check-relation-slot *no-value*))) (do ((value values (cdr value))) ((null value)) (when (memberq (car value) (cdr value)) (format t "Trying to set relation slot ~S in schema ~S with duplicate value ~S!~%" slot schema (car value)) (format t " The slot was not set.~%") (return-from check-relation-slot *no-value*))) values) (declaim (inline inherited-p)) (defun inherited-p (schema slot) "Similar to HAS-SLOT-P, but when there is a formula checks whether this is an inherited formula." (let ((entry (slot-accessor schema slot))) (when entry (or (is-inherited (sl-bits entry)) (and (formula-p (sl-value entry)) (formula-p (a-formula-is-a (sl-value entry)))))))) (defun formula-push (f) (bordeaux-threads:with-lock-held (*formula-lock*) (push f *formula-pool*))) ;;; encode types (defparameter *types-table* (make-hash-table :test #'equal) "Hash table used to look up a Lisp type and returns its code") (defparameter *types-table-lock* (bordeaux-threads:make-recursive-lock) "Lock to synchonize access to *types-table*") (defmacro with-types-table-lock-held ((table) &body body) `(let ((,table *types-table*)) (bordeaux-threads:with-recursive-lock-held (*types-table-lock*) ,@body))) (declaim (fixnum *types-array-inc*)) (defparameter *types-array-inc* 255) ;; allocate in blocks of this size (declaim (fixnum *next-type-code*)) (defparameter *next-type-code* 0) ;; next code to allocate (defparameter types-array NIL "Array used to decode a number into its corresponding Lisp type.") (defparameter type-fns-array NIL "Array used to decode a number into its corresponding type-fn.") (defparameter type-docs-array NIL "Array used to decode a number into its corresponding documentation string.") (declaim (inline code-to-type code-to-type-fn code-to-type-doc check-kr-type)) (defun code-to-type (type-code) (svref types-array type-code)) (defun code-to-type-fn (type-code) (svref type-fns-array type-code)) (defun code-to-type-doc (type-code) (svref type-docs-array type-code)) (defun check-kr-type (value code) (funcall (code-to-type-fn code) value)) (declaim (inline find-lisp-predicate)) (defun find-lisp-predicate (simple-type) "Given simple type ('NULL, 'KEYWORD, etc...), returns the name of the lisp predicate to test this ('NULL, 'KEYWORDP, etc....)" (let ((p-name (concatenate 'string (symbol-name simple-type) "P")) (-p-name (concatenate 'string (symbol-name simple-type) "-P"))) (cond ((memberq simple-type '(NULL ATOM)) simple-type) (T (or (find-symbol p-name 'common-lisp) (find-symbol -p-name 'common-lisp) (find-symbol p-name) (find-symbol -p-name) (error "Could not find predicate for simple-type ~S~%" simple-type)))))) (defun make-lambda-body (complex-type) (with-types-table-lock-held (types-table) (let (code) (cond ((consp complex-type) ;; complex type (a list) (let ((fn (first complex-type)) (args (rest complex-type))) (case fn ((OR AND NOT) (cons fn (mapcar #'make-lambda-body args))) (MEMBER `(memberq value ',args)) ((IS-A-P IS-A) `(is-a-p value ,(second complex-type))) (SATISFIES `(,(second complex-type) value)) ((INTEGER REAL) (let* ((pred (find-lisp-predicate fn)) (lo (first args)) (lo-expr (when (and lo (not (eq lo '*))) (if (listp lo) `((< ,(car lo) value)) `((<= ,lo value))))) (hi (second args)) (hi-expr (when (and hi (not (eq hi '*))) (if (listp hi) `((> ,(car hi) value)) `((>= ,hi value)))))) (if (or lo-expr hi-expr) `(and (,pred value) ,@lo-expr ,@hi-expr) `(,pred value)))) (T (error "Unknown complex-type specifier: ~S~%" fn))))) ((setq code (gethash (symbol-name complex-type) types-table)) ;; is this a def-kr-type? (make-lambda-body (code-to-type code))) (T ;; simple-type (list (find-lisp-predicate complex-type) 'value)))))) (defun type-to-fn (type) "Given the Lisp type, construct the lambda expr, or return the built-in function" (with-types-table-lock-held (types-table) (let (code) (cond ((consp type) ; complex type (if (eq (car type) 'SATISFIES) (let ((fn-name (second type))) `',fn-name) ;; koz `(function (lambda (value) (declare #.*special-kr-optimization*) ,(make-lambda-body type))))) ((setq code (gethash (symbol-name type) types-table)) ;; is this a def-kr-type? (code-to-type-fn code)) (T `',(find-lisp-predicate type)))))) (declaim (inline copy-extend-array)) (defun copy-extend-array (oldarray oldlen newlen) (let ((result (make-array newlen))) (dotimes (i oldlen) (setf (svref result i) (svref oldarray i))) result)) (defun get-next-type-code () "Return the next available type-code, and extend the type arrays if necessary." (let ((curlen (length types-array))) (when (>= *next-type-code* curlen) ;; out of room, allocate more space (let ((newlen (+ curlen *types-array-inc*))) (setf types-array (copy-extend-array types-array curlen newlen) type-fns-array (copy-extend-array type-fns-array curlen newlen) type-docs-array (copy-extend-array type-docs-array curlen newlen)))) ;; in any case, return current code, then add one to it (prog1 *next-type-code* (incf *next-type-code*)))) (defun add-new-type (typename type-body type-fn &optional type-doc) "This adds a new type, if necessary Always returns the CODE of the resulting type (whether new or not)" (with-types-table-lock-held (types-table) (let ((code (gethash (or typename type-body) types-table))) (if code ;; redefining same name (if (equal (code-to-type code) type-body) ;; redefining same name, same type (progn (format t "Ignoring redundant def-kr-type of ~S to ~S~%" typename type-body) (return-from add-new-type code)) ;; redefining same name, new type --> replace it! (format t "def-kr-type redefining ~S from ~S to ~S~%" typename (code-to-type code) type-body)) ;; defining a new name, establish new code (progn (setq code (or (gethash type-body types-table) (get-next-type-code))) (setf (gethash typename types-table) code))) (unless (gethash type-body types-table) (setf (gethash type-body types-table) code)) (setf (svref types-array code) (if typename (if (stringp typename) (intern typename (find-package "KR")) typename) type-body)) (setf (svref type-docs-array code) (or type-doc NIL)) (setf (svref type-fns-array code) (if (and (symbolp type-fn) ;; koz (fboundp type-fn)) (symbol-function type-fn) type-fn)) code))) (defun kr-type-error (type) (error "Type ~S not defined; use~% (def-kr-type ... () '~S)~%" type type)) (eval-when (:execute :compile-toplevel :load-toplevel) (defun encode-type (type) "Given a LISP type, returns its encoding." (with-types-table-lock-held (types-table) ;; if there, just return it! (cond ((gethash type types-table)) ((and (listp type) (eq (car type) 'SATISFIES)) ;; add new satisfies type (add-new-type NIL type (type-to-fn type))) ((symbolp type) (or (gethash (symbol-name type) types-table) (let ((predicate (find-lisp-predicate type))) (when predicate (add-new-type NIL type predicate))) (kr-type-error type))) (T (kr-type-error type)))))) (defun set-type-documentation (type string) "Add a human-readable description to a Lisp type." (setf (aref type-docs-array (encode-type type)) string)) (defun get-type-documentation (type) "RETURNS: the documentation string for the internal number ." (aref type-docs-array (encode-type type))) ;;; Formula and slot code. ;; Helper function ;; (defun eliminate-constant-formula () (declare (ftype (function (t t) t) destroy-constraint)) ;; This was a constant formula! Commit suicide. (with-demons-disabled (destroy-constraint *schema-self* *schema-slot*)) (when *warning-on-evaluation* (format t "formula (~S ~S) is constant - eliminated~%" *schema-self* *schema-slot*)) (let ((entry (slot-accessor *schema-self* *schema-slot*))) (if entry (setf (sl-bits entry) (logior *constant-mask* (sl-bits entry)))))) (declaim (inline slot-is-constant)) (defun slot-is-constant (schema slot) (let ((entry (slot-accessor schema slot))) (is-constant (sl-bits entry)))) (declaim (fixnum *warning-level*)) (defparameter *warning-level* 0) ;; Helper function ;; (defun re-evaluate-formula (schema-self schema-slot current-formula entry #+EAGER eval-type) (let ((*schema-self* schema-self) (*schema-slot* schema-slot) (*current-formula* current-formula) #+EAGER (*eval-type* eval-type) ) (when *warning-on-evaluation* (dotimes (i *warning-level*) (write-string " ")) (format t "evaluating ~S (on ~S, slot ~S)~%" *current-formula* *schema-self* *schema-slot*) (incf *warning-level* 2)) (let* ((*within-g-value* T) (*check-constants* ; only for the first evaluation! (unless *constants-disabled* (zerop (the fixnum (a-formula-number *current-formula*))))) (*accessed-slots* NIL) (*is-constant* T) (declared-constant (when *check-constants* (when (or entry (setf entry (slot-accessor *schema-self* *schema-slot*))) (is-constant (sl-bits entry)))))) (when declared-constant ; save work, since we know the answer (setf *check-constants* nil)) (set-cache-mark *current-formula* *sweep-mark*) (let ((the-result (catch 'no-link ;; If no-link, return cached-value anyway. ;; Evaluate the formula. (let ((new-v (funcall (coerce (a-formula-function *current-formula*) 'function)))) (if (and *types-enabled* (multiple-value-bind (value result) (check-slot-type *schema-self* *schema-slot* new-v T entry) (cond ((eq result :REPLACE) (setf new-v value) NIL) ((eq result T) T) (T NIL)))) ;; A type error (setf new-v NIL) ;; OK (unless (eq new-v (cached-value *current-formula*)) ;; Do nothing if value has not changed. (let ((*check-constants* *check-constants*)) ;; Call the pre-set-demon function on this schema if ;; this slot is an interesting slot. (run-pre-set-demons *schema-self* *schema-slot* new-v :CURRENT-FORMULA :FORMULA-EVALUATION) #+EAGER (do-eager-reeval new-v) ;; Set the cache to the new value (setf (cached-value *current-formula*) new-v)))) new-v)))) (if (or declared-constant (and *check-constants* *is-constant* *accessed-slots*)) ;; Eliminate constant formulas, if needed. (eliminate-constant-formula) ;; Mark formula as valid here. (unless *setting-formula-p* (set-cache-is-valid *current-formula* t))) (when *warning-on-evaluation* (decf *warning-level* 2)) the-result)))) ;; We are working with a formula. Note that broken links leave ;; the formula valid. ;; (defun g-value-formula-value (schema-self slot formula entry) (let ((*schema-self* schema-self)) (if (cache-is-valid formula) (a-formula-cached-value formula) (progn (unless *within-g-value* ;; Bump the sweep mark only at the beginning of a chain of formula ;; accesses. Increment by 2 since lower bit is "valid" flag. (incf *sweep-mark* 2)) (if (= (cache-mark formula) *sweep-mark*) ;; If the sweep mark is the same as the current one, WE ARE IN THE ;; MIDDLE OF A CIRCULARITY. Just use the old value, and mark it ;; valid. (progn (when *warning-on-circularity* (format t "Warning - circularity detected on ~S, slot ~S~%" *schema-self* slot)) (unless *setting-formula-p* (set-cache-is-valid formula T)) (a-formula-cached-value formula)) ;; Compute, cache and return the new value. (re-evaluate-formula *schema-self* slot formula entry)))))) ;;; Inheritance (defun copy-to-all-instances (schema a-slot value &optional (is-first T)) "Forces the to be physically copied to the of all instances of the , even though local values were defined. However, if there was a local formula, do nothing." (s-value schema a-slot value) ;; Do not create copies of formulas, but set things up for inheritance (when (and is-first (formula-p value)) (setf value *no-value*)) (dolist (inverse *inheritance-inverse-relations*) (let ((children (if (eq inverse :IS-A-INV) ; for efficiency (let ((entry (slot-accessor schema :IS-A-INV))) (when entry (sl-value entry))) (get-local-value schema inverse)))) (unless (eq children *no-value*) (dolist (child children) ;; force new inheritance (unless (formula-p (get-value child a-slot)) ;; Do not override if the user has specified a local formula! (copy-to-all-instances child a-slot value NIL))))))) (defun update-inherited-internal (child a-slot entry) (let ((old-value (sl-value entry))) (unless (eq old-value *no-value*) (let ((child-bits (sl-bits entry))) (when (is-inherited child-bits) ;; NOTE: we erase the inherited value in all cases, even if it might ;; have been inherited from somewhere else (in the case of multiple ;; inheritance). In any case, this is correct; at worst, it may ;; cause the value to be needlessly inherited again. ;; Force the children to re-inherit. (when (formula-p old-value) (delete-formula old-value T)) (clear-one-slot child a-slot entry) ;; Recursively change children. (update-inherited-values child a-slot *no-value* NIL)))))) (defun update-inherited-values (schema a-slot value is-first) "This function is used when a value is changed in a prototype. It makes sure that any child schema which inherited the previous value is updated with the new value. INPUTS: - : the new (i.e., current) value for the - : the setting of the slot bits for the , before the current value-setting operation. - : if non-nil, this is the top-level call. " (let ((*schema-self* schema)) (unless is-first ;; Invoke demons and propagate change around. (run-pre-set-demons schema a-slot value NIL :INHERITANCE-PROPAGATION) (run-invalidate-demons schema a-slot NIL) (propagate-change schema a-slot)) (dolist (inverse *inheritance-inverse-relations*) (let ((children (if (eq inverse :IS-A-INV) ; for efficiency (let ((entry (slot-accessor schema :IS-A-INV))) (when entry (sl-value entry))) (get-local-value schema inverse)))) (unless (eq children *no-value*) (dolist (child children) (let ((entry (slot-accessor child a-slot))) (when entry ;; If child had no value, no need to propagate down (setf is-first NIL) ;; force new inheritance (update-inherited-internal child a-slot entry))))))))) ;;; Slot and formula change code. (declaim (inline mark-as-changed)) (defun mark-as-changed (schema slot) "Forces formulas which depend on the in the to be invalidated. Mostly used for internal implementation. This function can be used when manually changing a slot (without using s-value). It will run the demons and propagate the invalidate wave to all the ordinary places." (let ((entry (slot-accessor schema slot))) (run-invalidate-demons schema slot entry) (when (and entry (is-parent (sl-bits entry))) (update-inherited-values schema slot (sl-value entry) T))) (propagate-change schema slot)) (declaim (inline mark-as-invalid)) (defun mark-as-invalid (schema slot) "Invalidates the value of the formula at in the of the . If the value is not a formula, nothing happens." (let ((value (get-value schema slot))) (when (formula-p value) (set-cache-is-valid value NIL)))) (defun recompute-formula (schema slot) "Forces the formula installed on the of the to be recomputed, propagating the change as needed. This may be used for implementation of formulas which depend on some non-KR value." (let* ((entry (slot-accessor schema slot)) (formula (when entry (sl-value entry)))) (when (formula-p formula) (let ((bits (sl-bits entry))) (unless *within-g-value* (incf *sweep-mark* 2)) (re-evaluate-formula schema slot formula entry #+EAGER *eval-type*) (run-invalidate-demons schema slot entry) (when (is-parent bits) (update-inherited-values schema slot formula T)) #-EAGER (propagate-change schema slot) #+EAGER (propagate))))) (defun propagate-change (schema slot) "Since the of the was modified, we need to propagate the change to all the formulas which depended on the old value." (let ((entry (slot-accessor schema slot))) ;; access the dependent formulas. (do-one-or-list (formula (slot-dependents entry) T) ;; Stop propagating if this dependent formula was already marked dirty. (if (and (not-deleted-p formula) (cache-is-valid formula)) (let* ((new-schema (on-schema formula)) (new-slot (on-slot formula)) (schema-ok (schema-p new-schema)) (new-entry NIL)) (unless (and new-schema new-slot) (when *warning-on-disconnected-formula* (format t "Warning: disconnected formula ~S in propagate-change ~S ~S~%" formula schema slot)) (continue-out)) (if schema-ok (progn (setf new-entry (slot-accessor new-schema new-slot)) (run-invalidate-demons new-schema new-slot new-entry)) #+GARNET-DEBUG (progn (format t "propagate-change: formula ~S on destroyed object ~S ~S~% ~ from change in schema ~S, slot ~S.~%" formula new-schema new-slot schema slot))) ;; The formula gets invalidated here. (set-cache-is-valid formula nil) ;; Notify all children who used to inherit the old value of the ;; formula. (if (and schema-ok new-entry) (if (slot-dependents new-entry) (propagate-change new-schema new-slot)))))))) (defun visit-inherited-values (schema a-slot function) "Similar to update-inherited-values, but used when the hierarchy is modified or when an inheritable slot is destroyed. SIDE EFFECTS: - the is called on all children which actually inherit the values in the of the . This is determined by a fast check (the list of values should be EQ to that of the parent). Note that the is called after all children have been visited.. This allows it to be a destructive function." (let* ((entry (slot-accessor schema a-slot)) (parent-entry (when entry (sl-value entry)))) (dolist (inverse *inheritance-inverse-relations*) (dolist (child (if (eq inverse :IS-A-INV) (get-local-value schema :IS-A-INV) (get-local-value schema inverse))) (let* ((entry (slot-accessor child a-slot)) (value (when entry (sl-value entry)))) (when (and value (is-inherited (sl-bits entry)) (eq value parent-entry)) (visit-inherited-values child a-slot function) (funcall function child a-slot))))))) (defun run-demons-and-set-value (schema slot new-value old-value is-relation is-formula was-formula the-bits entry) "Internal function which runs demons as appropriate (before changing the value) and then physically sets the in the to be ." (run-invalidate-demons schema slot entry) ;; Now set the value in the slot to be new-value. (cond ((and was-formula (not is-formula)) ;; This is the case when we allow temporary overwriting (setf (cached-value old-value) new-value) ;; Set this to NIL, temporarily, in order to cause propagation ;; to leave the value alone. It will be validated by s-value. (set-cache-is-valid old-value NIL)) (t ;; All other cases (when (and is-formula (null (cached-value new-value))) ;; place old value in the cache only if an initial value ;; was not provided for the new formula ;; Set value, but keep formula invalid. (setf (cached-value new-value) (if was-formula (cached-value old-value) old-value))) ;; Take care of relations. (when is-relation (when old-value (unlink-all-values schema slot)) (link-in-relation schema slot new-value)) (let ((new-bits (or the-bits *local-mask*))) (if entry ;; This is a special slot - just set it (setf (sl-value entry) new-value (sl-bits entry) new-bits) ;; This is not a special slot. (set-slot-accessor schema slot new-value new-bits nil))))) ;; Now propagate the change to all the children which used to ;; inherit the previous value of this slot from the schema. (when (and the-bits (is-parent the-bits)) (let ((*setting-formula-p* T)) (update-inherited-values schema slot new-value T)))) (defun constant-slot-error (schema slot) (cerror "Set the slot anyway" "Schema ~S - trying to set slot ~S, which is constant." schema slot)) (declaim (inline check-not-constant)) (defun check-not-constant (schema slot entry) "Signals an error if the of the is not constant." (and (not *constants-disabled*) entry (is-constant (sl-bits entry)) (constant-slot-error schema slot))) (declaim (inline slot-constant-p)) (defun slot-constant-p (schema slot) "RETURN: T if the in the is constant, nil otherwise" (let ((entry (slot-accessor schema slot))) (when entry (is-constant (sl-bits entry))))) (defun set-formula-error (schema slot formula) "Called to give error message on multiply-installed formulas." ;; Formulas can only be installed on one slot! (format t "(s-value ~S ~S): formula ~S is already installed on~%~ schema ~S, slot ~S. Ignored.~%" schema slot formula (on-schema formula) (on-slot formula)) formula) (defun s-value-fn (schema slot value) "Does all the work of the macro S-VALUE. RGA --- no, returns two values: the value function set to and t if there was an error. Note that in the case of a type error, it returns the current value of the slot." (locally (declare #.*special-kr-optimization*) (unless (schema-p schema) #+GARNET-DEBUG (if schema (error "S-VALUE called with the non-object ~S (slot ~S, value ~S)." schema slot value) (error "S-VALUE called with a null schema: (slot ~S, value ~S)." slot value)) ;; RGA added t for error return. (return-from s-value-fn (values value t))) (let* ((entry (slot-accessor schema slot)) (old-value (when entry ;; Slot position is known at compile time (sl-value entry))) the-bits is-depended) ;; give error if setting constant slot (check-not-constant schema slot entry) (if entry (setf the-bits (sl-bits entry) is-depended (slot-dependents entry)) (setf the-bits 0)) (when (and the-bits (not (is-inherited the-bits)) (eq old-value value) value) ;; We are setting to the same value as the old one! Do nothing. ;; RGA --- Error return function (return-from s-value-fn (values value nil))) (when (and *types-enabled* (not (formula-p value))) (multiple-value-bind (result error-p) (check-slot-type schema slot value T entry) (cond ((not error-p)) ; Everything is OK ((eq error-p T) ;; A type error - user wants to do nothing ;; RGA --should return old-value, added a second ;; value indicating error. (return-from s-value-fn (values old-value t))) (T ;; A type error - user supplied new value (setf value result))))) (let ((is-formula nil) (is-relation nil) (was-formula (formula-p old-value))) ;; Check for special cases in relation slots. (when (and (setf is-relation (relation-p slot)) (eq (setf value (check-relation-slot schema slot value)) *no-value*)) ;; RGA --- added no-error return code (return-from s-value-fn (values old-value nil))) ;; If we are installing a formula, make sure that the formula ;; points to the schema and slot. (when (formula-p value) (when (on-schema value) ;; RGA --- added error return code. (return-from s-value-fn (values (set-formula-error schema slot value) T))) (setf is-formula T) (setf (on-schema value) schema) (setf (on-slot value) slot) (unless (schema-name value) ;; This is an obscure case. It may happen if somebody stores a ;; formula away, deletes the formula from its original slot, and ;; then restores the formula. This is generally bad practice, but ;; there are cases when it may be necessary. (incf *schema-counter*) (setf (schema-name value) *schema-counter*))) ;; Now we call a demon to perform redisplay activities if the new ;; value is not a formula. If the new value is a formula, it has ;; not been evaluated yet so we do not know what its result is. ;; Since the display demon needs to know the new result to determine ;; if the object's bounding box should be merged with a clip region, ;; it does not make sense to call the display demon until the new ;; result is known (unless is-formula (run-pre-set-demons schema slot value NIL :S-VALUE)) ;; Now we can set the new value. (setf the-bits (logand the-bits *not-inherited-mask*)) (run-invalidate-demons schema slot entry) ;; Now set the value in the slot to be . (cond ((and was-formula (not is-formula)) (when (zerop (a-formula-number old-value)) (format t "*** Warning: you are setting the value of slot ~S of object ~S. This slot contains a formula which was never evaluated. The formula is now valid, but its dependencies are not set up properly. As a result, the formula will never be evaluated. In order for this formula to work properly, you should have used (g-value ~S ~S) before using S-VALUE. If you want to fix things now, re-install the formula using s-value.~%" slot schema schema slot)) ;; This is the case when we allow temporary overwriting (setf (cached-value old-value) value) ;; Set this to NIL, temporarily, in order to cause propagation ;; to leave the value alone. It will be validated by s-value. (set-cache-is-valid old-value NIL)) (t ;; All other cases (when (and is-formula (null (cached-value value))) ;; place old value in the cache only if an initial value ;; was not provided for the new formula ;; Set value, but keep formula invalid. (setf (cached-value value) (if was-formula (cached-value old-value) old-value))) ;; Take care of relations. (when is-relation (when old-value (unlink-all-values schema slot)) (link-in-relation schema slot value)) (let ((new-bits (or the-bits *local-mask*))) (if entry ;; This is a special slot - just set it (setf (sl-value entry) value (sl-bits entry) new-bits) ;; This is not a special slot. (setf entry (set-slot-accessor schema slot value new-bits nil)))))) ;; Now propagate the change to all the children which used to ;; inherit the previous value of this slot from the schema. (when (and the-bits (is-parent the-bits)) (let ((*setting-formula-p* T)) (update-inherited-values schema slot value T))) ;; Notify all dependents that the value changed. (when is-depended (let ((*warning-on-disconnected-formula* nil)) (propagate-change schema slot))) (when (and was-formula (not is-formula)) ;; We validate now, rather than earlier, because of a technicality ;; in demons-and-old-values. (set-cache-is-valid old-value T)) ;; Was the old value a formula? (when (and was-formula is-formula) ;; This is replacing a formula with another. Eliminate the dependency ;; to the old one. (delete-formula old-value T)) (when is-relation ;; A relation slot is being changed. We may need to invalidate all ;; inherited values. (reset-inherited-values schema)) ;; RGA added nil error return flag. (values value nil))))) (defun internal-s-value (schema slot value) "This is a stripped-down version of s-value-fn which is used by create-schema and friends. It skips a lot of the stuff that is unnecessary at schema creation time." (let ((is-formula (formula-p value)) (is-relation (relation-p slot))) (when is-relation (unless (listp value) (setf value (list value))) ;; Check for special cases in relation slots. (when (eq (setf value (check-relation-slot schema slot value)) *no-value*) (return-from internal-s-value NIL))) ;; If we are installing a formula, make sure that the formula ;; points to the schema and slot. (when is-formula (when (on-schema value) (return-from internal-s-value (set-formula-error schema slot value))) (setf (on-schema value) schema) (setf (on-slot value) slot)) (set-slot-accessor schema slot value *local-mask* nil) ;; Take care of relations. (when is-relation (link-in-relation schema slot value)) value)) (defun set-is-a (schema value) "A specialized version of internal-s-value" ;; Check for special cases in relation slots. (when (eq (setf value (check-relation-slot schema :is-a value)) *no-value*) (return-from set-is-a NIL)) ;; Set slot (set-slot-accessor schema :IS-A value *local-mask* NIL) (link-in-relation schema :IS-A value) value) (defun eliminate-formula-dependencies (formula except-schema) "If is non-nil, it indicates that a schema is in the process of being destroyed, and hence dependencies to THAT schema should not be tracked down." (do-one-or-list (schema (a-formula-depends-on formula)) (unless (or (eq schema except-schema) (deleted-p schema)) ; schema is destroyed (iterate-slot-value (schema T T T) slot value ; suppress warning (let ((formulas (slot-dependents iterate-slot-value-entry))) (if (listp formulas) ;; Several dependents (when (memberq formula formulas) (setf (full-sl-dependents iterate-slot-value-entry) (delete formula formulas))) ;; One dependent (when (eq formula formulas) (setf (full-sl-dependents iterate-slot-value-entry) NIL)))))))) (defun delete-formula (formula remove-from-parent) "Eliminate all dependency pointers from the , since it is no longer installed on a slot. INPUTS: - : the formula to get rid of - : if T, do a more thorough job of deleting everything, and destroy the schema itself." (when (a-formula-number formula) (eliminate-formula-dependencies formula NIL) (when remove-from-parent ;; Eliminate the from its parent's list of children. (let ((parent (a-formula-is-a formula))) (when parent (delete-one-or-list formula (a-formula-is-a-inv parent))))) ;; Formula was not destroyed yet (setf (a-formula-bins formula) nil ; mark as destroyed. (a-formula-schema formula) nil (a-formula-slot formula) nil (a-formula-lambda formula) nil (a-formula-depends-on formula) nil) (let ((meta (a-formula-meta formula))) (when meta (setf (a-formula-meta formula) NIL) (destroy-schema meta))) (formula-push formula))) (defun destroy-slot-helper (x slot) ;; Make sure formulas are updated properly (mark-as-changed x slot) ;; Physically remove the slot in the child. (clear-one-slot x slot NIL)) (defparameter *in-destroy-slot* 0) (defparameter *invalid-destroy-slot* 0) (defun destroy-slot (schema slot) "Eliminates the , and all the values it contains, from the , taking care of possible constraints." ;; Take care of all formulas which used to depend on this slot. (let ((entry (slot-accessor schema slot)) old-value) (when entry (setf old-value (sl-value entry)) (check-not-constant schema slot entry) (let ((bits (sl-bits entry)) (dependents (slot-dependents entry)) new-value) (run-invalidate-demons schema slot entry) (when dependents ;; Access all dependent formulas. (do-one-or-list (formula dependents) #+EAGER (setf formula (car formula)) #+EAGER (setf in-pq (eval-bit-set formula)) (incf *in-destroy-slot*) (unless (cache-is-valid formula) (incf *invalid-destroy-slot*)) ;; If this value is depended on by others, replace their value ;; by the current value. (let ((the-schema (on-schema formula)) (the-slot (on-slot formula))) (when (and the-schema (schema-p the-schema) ; not destroyed (not (formula-p (g-value the-schema the-slot)))) ;; Avoid complications with shared formulas. (s-value the-schema the-slot (g-value the-schema the-slot)))) ;; The formula is then marked invalid. #-EAGER (set-cache-is-valid formula NIL) #+EAGER (progn ;; set the formula's fixed bit back to nil to indicate it should ;; be evaluated during this iteration of the constraint solver (set-fixed-bit formula nil) (when (not in-pq) (setf *eval-queue* (insert-pq formula *eval-queue*)))))) ;; Destroy the formula, if this was a constrained slot. (when (formula-p old-value) (delete-formula old-value T)) (when (relation-p slot) (unlink-all-values schema slot)) (setf new-value (g-value-inherit-values schema slot T entry)) ;; Call the pre-set-demon function on this schema if ;; this slot is an interesting slot and the value it is ;; now inheriting is different from its previous value (run-pre-set-demons schema slot new-value old-value :DESTROY-SLOT) #+EAGER ;; Add this slot's dependents to the evaluation queue if its ;; new inherited value is different from its old value. (unless (equal old-value new-value) (add-to-reeval schema slot)) (let ((was-parent (and bits (is-parent bits)))) (when was-parent ;; Was this slot inherited by other schemata? If so, make sure ;; they will inherit the right value afterwards. (update-inherited-values schema slot new-value T) (visit-inherited-values schema slot #'destroy-slot-helper)))) ;; Now go ahead and physically destroy the slot. (clear-one-slot schema slot NIL) NIL))) (defun delete-schema (schema recursive-p) "Internal function. If , this is being called from within recursive-destroy-schema, so there is no need to maintain upwards relations properly." (when (not-deleted-p schema) ; do nothing if schema is already destroyed ;; Remove all inverse links. (if (formula-p schema) ;; Formulas do not use regular relations. (let ((parent (a-formula-is-a schema)) children) (when parent (setf children (a-formula-is-a-inv parent)) (setf (a-formula-is-a-inv parent) (if (listp children) (delete schema children) (if (eq schema children) NIL children)))) (do-one-or-list (child (a-formula-is-a-inv schema)) ;; ? What exactly should happen here ? (setf (a-formula-is-a child) NIL))) ;; A normal schema (progn (unless recursive-p (iterate-slot-value (schema NIL NIL NIL) value ; eliminate warning (when (relation-p slot) (unlink-all-values schema slot)))) (iterate-slot-value (schema NIL NIL NIL) slot value ; eliminate warning ;; Delete any formula value. (when (formula-p value) ;; This is a formula. Get rid of it. (delete-formula value (not recursive-p)) (delete-schema value recursive-p))) ;; Physically delete all the slots (clear-schema-slots schema))) ;; Now wipe out the symbol value as well. (when (symbolp (schema-name schema)) (makunbound (schema-name schema))) ;; This is used as a marker for deleted schemas. (setf (schema-bins schema) nil))) (defun find-direct-dependency (expression target) "RETURNS: T if the given , or one of its subexpressions, directly depends on the . This must be a direct dependency, i.e., one which does not use a link." (when (listp expression) (or (and (eq (car expression) 'GV) (eq (cadr expression) target)) (dolist (thing expression) (when (find-direct-dependency thing target) (return T)))))) (defun destroy-schema (schema &optional (send-destroy-message NIL) recursive-p) "Destroys the , eliminates all dependencies to and from it." (unless (schema-p schema) ;; If schema is already destroyed, do nothing. (return-from destroy-schema)) (let ((done nil) bizarre) (iterate-slot-value (schema T T NIL) slot ; eliminate warning (unless (eq value *no-value*) ;; Look at all formulas which depend on this slot. (do-one-or-list (formula (slot-dependents iterate-slot-value-entry)) (when (and formula ; defensive programming (not (memberq formula done))) ;; If this is a value depended on by others, replace their ;; value by the current value. Do this, however, only if the ;; dependency is a DIRECT one, i.e., if the name of the ;; schema we are destroying is wired into the formula. If ;; this is a link, leave things as they are. (let ((the-form (or (a-formula-lambda formula) ; for o-formulas (and (setf bizarre ;; This should always be a ;; list, but be prudent just ;; in case. (a-formula-function formula)) (listp bizarre) (cddr bizarre))))) (when (find-direct-dependency the-form schema) ;; This is indeed a direct-dependency formula. Install the ;; appropriate value. (s-value (on-schema formula) (on-slot formula) (g-value (on-schema formula) (on-slot formula))) (push formula done) ;; The formula now commits suicide. (delete-formula formula (not recursive-p)))))) ;; If this is a formula, eliminate dependencies to it, so we ;; do not get warnings in propagate-change. (when (formula-p value) (delete-formula value T)))) (when send-destroy-message ;; Call the :DESTROY method. (kr-call-initialize-method schema :DESTROY)) ;; Physically delete the schema. (delete-schema schema recursive-p))) (defun recursive-destroy-schema (schema level) "This is an internal function used by CREATE-INSTANCE. The purpose is to destroy not only the itself, but also its instances (and so on, recursively)." (unless (or (formula-p schema) ; safety check (deleted-p schema)) (let* ((entry (slot-accessor schema :IS-A-INV)) (children (if entry (sl-value entry)))) (unless (eq children *no-value*) (dolist (child children) (unless (eq child schema) (recursive-destroy-schema child (1+ level))))) (when *warning-on-create-schema* (if (zerop level) (format t "Warning - create-schema is destroying the old ~S.~%" schema) (format t "Warning - create-schema is recursively destroying ~S.~%" schema))))) (destroy-schema schema NIL (if (zerop level) ;; if this is a top-level schema which has no prototype, use an ;; indiscriminate destroy. (null (slot-accessor schema :is-a)) T))) (defun reset-inherited-values (schema) "Since the slot was changed, all children of the may have to inherit different values." (iterate-slot-value (schema T NIL T) ; use inheritance! (unless (relation-p slot) (unless (eq value *no-value*) (when (is-inherited (sl-bits iterate-slot-value-entry)) (destroy-slot schema slot)))))) ;;; SCHEMA PRINTING (defun print-one-value (value type) (let ((string (cond ((formula-p value) (let ((cached (cached-value value)) (valid (cache-is-valid value))) (if (or valid cached) (format nil "~S(~S . ~D)" value cached valid) (format nil "~S(nil . NIL)" value)))) ((eq value *no-value*) "") (t (format nil "~S" value))))) (when type (setf string (concatenate 'simple-string string (format nil " ~([~S]~)" type)))) (write-string string) (length string))) (defun print-one-slot-helper (value column indent space-p type) (when (> column 78) (format t "~% ") (setf column (indent-by indent))) (when space-p (write-string " ")) (incf column (print-one-value value type)) column) (defun print-meta (formula) "Print the meta-information associated with a formula." (let ((meta (a-formula-meta formula))) (when (and meta (schema-p meta)) (format t " ---- meta information (~A):~%" meta) (call-on-ps-slots meta 'SLOT-PRINTER)))) (defun indent-by (indent) (dotimes (i indent) (write-string " ")) (* indent 3)) (defun force-down-helper (schema original-slots slots) (iterate-slot-value (schema T T NIL) value ; eliminate warning (unless (memberq slot original-slots) (pushnew slot slots))) (dolist (parent (get-local-value schema :IS-A)) (setf slots (force-down-helper parent original-slots slots))) slots) (defun force-down-all-inheritance (schema) "A potentially VERY expensive operation. It is done by PS when it wants to print out all inherited and inheritable slots of an object." (let ((original-slots nil)) (iterate-slot-value (schema T NIL NIL) value ; eliminate warning (push slot original-slots)) (dolist (slot (force-down-helper schema original-slots nil)) (get-value schema slot)))) (defun call-func-on-one-slot (schema slot inherited-ok function types-p indent limits) "Helper function for the following. The is called with: (schema slot formula inherited valid real-value types-p indent limits)" (let* ((entry (slot-accessor schema slot)) (values (when entry (sl-value entry))) (bits (when entry (sl-bits entry))) form valid real-value) (when bits (let ((are-inherited (and (is-inherited bits) ;; inherited formulas are printed anyway. (not (formula-p values))))) (unless (and (not inherited-ok) are-inherited) (unless (eq values *no-value*) (if (formula-p values) (let ((cached (cached-value values)) (is-valid (cache-is-valid values))) (setq form values) (setq valid is-valid) (setq real-value cached)) ;; else not a formula (setq real-value values)) (funcall function schema slot form are-inherited valid real-value types-p bits indent limits))) ;; Indicate that the function was called. T)))) (defun call-on-ps-slots (schema function &key (control t) inherit (indent NIL) types-p all-p) "Apply the to slots, the way PS would." (declare (special print-schema-control)) (let ((is-ps (eq function 'SLOT-PRINTER))) ; true if inside PS (when (null indent) (setf indent 0)) (when (numberp schema) (setf schema (s schema))) (unless (or (schema-p schema) (formula-p schema)) (when is-ps (format t "~S~%" schema)) (return-from call-on-ps-slots nil)) (when is-ps (indent-by indent)) (cond ((formula-p schema) (setf control NIL)) ((eq control :default) ;; use default control schema (setf control PRINT-SCHEMA-CONTROL)) ((eq control T) ;; use schema itself as the control schema (i.e., use hierarchy) (setf control schema))) (let ((slots-ignored (when control (g-value-no-copy control :IGNORED-SLOTS))) (sorted (when control (g-value-no-copy control :SORTED-SLOTS))) (limit-values (when control (g-value-no-copy control :LIMIT-VALUES))) (global-limit (if control (g-value-no-copy control :GLOBAL-LIMIT-VALUES) most-positive-fixnum)) (*print-as-structure* (if (and control (g-value-no-copy control :print-as-structure)) ;; value is defined (g-value-no-copy control :print-as-structure) ;; value is undefined *print-as-structure*)) (*print-structure-slots* (when control (g-value-no-copy control :print-slots)))) (when is-ps (format t "{~S~%" schema)) ;; Print out all the sorted slots, first. (dolist (o sorted) (call-func-on-one-slot schema o inherit function types-p indent (or (second (assocq o limit-values)) global-limit))) ;; Now print the remaining slots. (unless (listp slots-ignored) (setf slots-ignored (list slots-ignored))) ;; Pre-inherit all slots that are inheritable. (unless (a-formula-p schema) (when inherit (force-down-all-inheritance schema)) (if all-p (iterate-slot-value (schema T T NIL) (unless (or (memberq slot slots-ignored) (memberq slot sorted) (eq value *no-value*)) (call-func-on-one-slot schema slot inherit function types-p indent (or (second (assocq slot limit-values)) global-limit)))) (iterate-slot-value (schema T NIL NIL) (unless (or (memberq slot slots-ignored) (memberq slot sorted) (eq value *no-value*)) (call-func-on-one-slot schema slot inherit function types-p indent (or (second (assocq slot limit-values)) global-limit)))))) (when (and slots-ignored is-ps) (indent-by indent) (format t " List of ignored slots: ~{ ~A~}~%" slots-ignored)) ;; special formula slots? (when (a-formula-p schema) (if is-ps (progn (indent-by indent) (format t " lambda: ~(~S~)~%" (a-formula-lambda schema)) (format t " cached value: (~S . ~S)~%" (cached-value schema) (cache-is-valid schema)) (format t " on schema ~S, slot ~S~%" (on-schema schema) (on-slot schema)) (indent-by indent)) (dolist (name '(:lambda :cached-value-valid :cached-value :schema :slot)) (funcall function schema name nil nil T ; valid (g-formula-value schema name) nil 0 indent nil))))) (when is-ps (format t " }~%")))) (defun call-on-one-slot (schema slot function) "Similar to CALL-ON-PS-SLOTS, but works on one slot only." (call-func-on-one-slot schema slot T function NIL 0 NIL)) (defun slot-printer (schema name formula are-inherited valid values type-p bits indent limit-values) "Used by PS to print out one slot." (declare (ignore schema)) (let ((number 0) (printed nil) (column (+ 20 (indent-by indent))) (*print-length* 10) ; do not print out very long arrays! type) (if are-inherited (format t " ~(~S~) (inherited): " name) (format t " ~S = " name)) (when type-p (setf type (code-to-type (extract-type-code bits)))) (when formula (format t "~S(" formula)) (cond ((eq values *no-value*) (if type-p (setf column (print-one-slot-helper ; print types *no-value* column indent T type))) (setf printed T)) ((and values (listp values) (listp (cdr values))) (format t "(") (dolist (value values) (setf printed t) (setf column (print-one-slot-helper value column indent (> number 0) nil)) (incf number) (when (and limit-values (> number limit-values)) ;; Too many values: use ellipsis form. (format t " ...") (return nil))) (format t ")") (when formula (format t " . ~S)" valid)) (when type (print-one-slot-helper ; print out type *no-value* column indent nil type))) ((null values) (if formula (format t "nil . ~S)" valid) (format t " NIL")) (setf printed T) (when type (print-one-slot-helper ; print out type *no-value* column indent nil type))) (t (setf printed t) (setf column (print-one-slot-helper values column indent (not formula) type)) (when formula (format t " . ~S)" valid)))) (if printed (terpri) (format t " NIL~%")))) (defun ps (schema &key (control t) inherit (indent 0) types-p all-p (stream *standard-output*)) "PS prints the . The optional arguments allow fancy control of what is printed. A control schema may be used to determine which options are printed, which ones are ignored, etc. See the manual for details. can be one of the following: - T, which means that the itself is used as the control schema; - :DEFAULT, which means that the schema KR:PRINT-SCHEMA-CONTROL is used; - any schema, which is used as the control schema. - NIL, which means that the is printed in its entirety (i.e. no schema control.) If is non-nil, slots that have been inherited are also printed. is used for debugging and should not be set by the user." (let ((*standard-output* stream)) (call-on-ps-slots schema 'SLOT-PRINTER :control control :inherit inherit :indent indent :types-p types-p :all-p all-p) (when (formula-p schema) (print-meta schema))) schema) (defun the-bits (bits) (if (integerp bits) ;; The normal case (let ((type (extract-type-code bits))) (format t "~:[-~;p~]~:[-~;l~]~:[-~;C~]~:[-~;P~]~:[-~;u~]~:[-~;i~] " (is-parameter bits) (is-local-only bits) (is-constant bits) (is-parent bits) (is-update-slot bits) (is-inherited bits)) (unless (zerop type) (format t "[~(~S~)] " (code-to-type type)))) ;; A special case for formula slots which are stored in a special way (format t "---- "))) (defun full-normal-slot (schema slot) "Helper function for FULL." (format t "~(~24S~) " slot) (let* ((entry (slot-accessor schema slot)) (values (if entry (sl-value entry))) (bits (if entry (sl-bits entry))) (dependents (slot-dependents entry))) (the-bits bits) (if entry ;; Slot is there (let ((first t)) (when (eq values *no-value*) (setf values NIL)) (if (and (listp values) (listp (cdr values))) (when values (format t " (") (dolist (value values) (if first (setf first nil) (write-string " ")) (print-one-value value NIL)) (format t ")")) (progn (write-string " ") (print-one-value values NIL))) ;; Show dependent formulas, if any (when dependents (format t " ****--> ") (do-one-or-list (f dependents) (format t " ~s" f))) (terpri)) ;; No slot??? (terpri))) (values)) (defun full (&rest schemata) "Internal debugging - print out schemata in gory detail." (dolist (schema schemata) (when (numberp schema) (setf schema (s schema))) (let ((is-formula (a-formula-p schema))) ;; use iterators to get inherited slots as well (if is-formula ;; This is a formula (progn (format t "---------------------------------------------- formula ~S~%" schema) ;; print special formula slots. (format t "Schema, slot: ~S ~S~%" (on-schema schema) (on-slot schema)) (format t "Cached value: (~S . ~S)~%" (cached-value schema) (a-formula-number schema)) (format t "Depends on: ~S~%" (a-formula-depends-on schema)) (format t "Lambda: ~(~S~)~%" (a-formula-lambda schema)) (when (a-formula-is-a schema) (format t "parent formula: ~S~%" (a-formula-is-a schema))) (when (a-formula-is-a-inv schema) (format t "children: ~S~%" (a-formula-is-a-inv schema))) (print-meta schema)) ;; This is a normal slot (progn (format t "---------------------------------------------- schema ~S~%" schema) (iterate-slot-value (schema T T nil) value ;; eliminate warning (full-normal-slot schema slot)))))) (values)) ;;; O-O PROGRAMMING (defun find-parent (schema slot) "Find a parent of from which the can be inherited." (dolist (relation *inheritance-relations*) (dolist (a-parent (if (eq relation :is-a) (get-local-value schema :IS-A) (get-local-value schema relation))) (when a-parent (let ((value (g-local-value a-parent slot))) (if value (return-from find-parent (values value a-parent)) (multiple-value-bind (value the-parent) (find-parent a-parent slot) (when value (return-from find-parent (values value the-parent)))))))))) (defun old-kr-send-function (schema slot &rest args) "Same as KR-SEND, but as a function." (let ((the-function (g-value schema slot))) (when the-function ;; Bind these in case call prototype method is used. (let ((*kr-send-self* schema) (*kr-send-slot* slot) (*demons-disabled* T)) (apply the-function args))))) (defun kr-call-initialize-method (schema slot) "This is similar to kr-send-function, except that it is careful NOT to inherit the method, which is only used once. This is to reduce unnecessary storage in every object." (let ((the-function (g-value-no-copy schema slot))) (when the-function ;; Bind these in case call prototype method is used. (let ((*kr-send-self* schema) (*kr-send-slot* slot) (*kr-send-parent* NIL) (*demons-disabled* T)) (funcall the-function schema))))) (defun kr-init-method (schema the-function) "Similar, but even more specialized. It is only called by create-schema and friends, which know whether an :initialize method was specified locally." (let ((*kr-send-parent* nil)) (if the-function (setf *kr-send-parent* schema) (multiple-value-setq (the-function *kr-send-parent*) (find-parent schema :INITIALIZE))) (when the-function ;; Bind these in case call prototype method is used. (let ((*kr-send-self* schema) (*kr-send-slot* :INITIALIZE) (*kr-send-parent* NIL) #-(and) (*demons-disabled* T)) (funcall the-function schema))))) (defun call-prototype-function (&rest args) "Functional version of CALL-PROTOTYPE-METHOD." (let (parent) (if (get-local-value *kr-send-self* *kr-send-slot*) (setf parent *kr-send-self*) (multiple-value-bind (method real-parent) (find-parent *kr-send-self* *kr-send-slot*) (declare (ignore method)) (setf parent real-parent))) (multiple-value-bind (function- the-parent) (find-parent parent *kr-send-slot*) (when function- (let ((*kr-send-self* the-parent) (*kr-send-parent* NIL)) (apply function- args)))))) ;;; Schemas (defun allocate-schema-slots (schema) (locally (declare #.*special-kr-optimization*) (setf (schema-bins schema) (make-hash-table :test #'eq #+sbcl :synchronized #+sbcl t))) schema) (defun make-a-new-schema (name) "Creates a schema with the given , making sure to destroy the old one by that name if it exists. The initial number of slots is ." (locally (declare #.*special-kr-optimization*) (when (keywordp name) (setf name (symbol-name name))) (cond ((null name) ;; An unnamed schema. (let ((schema (make-schema))) (setf *schema-counter* (1+ *schema-counter*)) (setf (schema-name schema) *schema-counter*) (allocate-schema-slots schema) schema)) ((stringp name) ;; This clause must precede the next! (let ((schema (make-schema))) (allocate-schema-slots schema) (setf (schema-name schema) name) schema)) ;; Is this an existing schema? If so, destroy the old one and its ;; children. ((and (boundp name) (symbolp name)) (let ((schema (symbol-value name))) (if (is-schema schema) (progn (recursive-destroy-schema schema 0) (allocate-schema-slots schema)) (progn (setf schema (make-schema)) (allocate-schema-slots schema) (eval `(defvar ,name)))) ;; Assign the new schema as the value of the variable . (setf (schema-name schema) name) (set name schema))) ((symbolp name) (eval `(defvar ,name)) (let ((schema (make-schema))) (allocate-schema-slots schema) (setf (schema-name schema) name) (set name schema))) (t (format t "Error in CREATE-SCHEMA - ~S is not a valid schema name.~%" name))))) ;;; Constant slots (defun process-one-constant (schema slot) "The in was declared constant." ;; set slot information (let ((entry (slot-accessor schema slot))) (if (null entry) ;; Slot is not present - create it, mark constant. (set-slot-accessor schema slot *no-value* *constant-mask* nil) ;; Slot is present (setf (sl-bits entry) (logior *constant-mask* (sl-bits entry)))))) (defun declare-constant (schema slot) "Declare slot constants AFTER instance creation time." (unless *constants-disabled* (if (eq slot T) ;; This means that all constants declared in :MAYBE-CONSTANT should be ;; made constant (let ((maybe (g-value-no-copy schema :MAYBE-CONSTANT))) (dolist (m maybe) (declare-constant schema m))) ;; This is the normal case - only 1 slot. (let ((constant-list (g-value schema :CONSTANT)) (positive T)) (do ((list constant-list (if (listp list) (cdr list) NIL)) (prev nil list) c) ((null list) (setf constant-list (cons slot (if (listp constant-list) constant-list (list constant-list)))) (s-value schema :CONSTANT constant-list) (process-one-constant schema slot)) (setf c (if (listp list) (car list) list)) (cond ((eq c :EXCEPT) (setf positive NIL)) ((eq c slot) (when positive ;; Slot is already marked constant, so there's nothing ;; to do. (process-one-constant schema slot) (return nil)) ;; Slot was explicitly excepted from constant list. (setf (cdr prev) (cddr prev)) ; remove from :EXCEPT (when (and (null (cdr prev)) (eq (car prev) :EXCEPT)) ;; We are removing the last exception to the constant list (let ((end (nthcdr (- (length constant-list) 2) constant-list))) (setf (cdr end) nil))) (setf constant-list (cons c constant-list)) (s-value schema :CONSTANT constant-list) (process-one-constant schema slot) (return)))))))) (defun merge-prototype-values (object slot parents values) "Process declarations such as :DECLARE (:PARAMETERS T :EXCEPT :WIDTH), which modify the prototype's specification of a declaration by adding or removing new slot names." (unless values (setf values (when parents (g-value (car parents) slot)))) (let ((exceptions nil) (add-prototype nil) (results nil)) ;; process declarations (when (and values (not (eq values :NONE))) (if (listp values) (progn (if (eq (car values) 'QUOTE) (format t "The ~S list for schema ~S is specified incorrectly - too many quotes:~% ~S~%" slot object values)) ;; Normal case - a list (do ((c values (cdr c))) ((null c)) (cond ((eq (car c) T) (setf add-prototype T)) ((eq (car c) :EXCEPT) ; following is list of exceptions. (setf exceptions (cdr c)) (return)) (t (if (eq slot :CONSTANT) (process-one-constant object (car c)) (pushnew (car c) results)))))) ;; For the case (:CONSTANT T), for example - single value (if (eq values T) (setf add-prototype T) (if (eq slot :CONSTANT) (process-one-constant object values) (setf results (list values))))) (when add-prototype ; Add slots declared in prototype (let ((maybe-constant (if (eq slot :CONSTANT) (g-value-no-copy object :MAYBE-CONSTANT) (g-value (car parents) slot)))) (do-one-or-list (c maybe-constant) (unless (memberq c exceptions) (if (eq slot :CONSTANT) (process-one-constant object c) (pushnew c results)))))) (unless (eq slot :CONSTANT) (setf results (nreverse results)) (unless (equal results values) (s-value object slot results)))))) (defun process-constant-slots (the-schema parents constants do-types) "Process local-only and constant declarations." (locally (declare #.*special-kr-optimization*) ;; Install all update-slots entries, and set their is-update-slot bits (dolist (slot (g-value-no-copy the-schema :UPDATE-SLOTS)) (let ((entry (slot-accessor the-schema slot))) (if entry (setf (sl-bits entry) (set-is-update-slot (sl-bits entry))) (set-slot-accessor the-schema slot *no-value* (set-is-update-slot *local-mask*) NIL)))) ;; Mark the local-only slots. (dolist (parent parents) (dolist (local (g-value-no-copy parent :LOCAL-ONLY-SLOTS)) (unless (listp local) (cerror "Ignore the declaration" "create-instance (object ~S, parent ~S): :local-only-slots declarations should consist of lists of the form (:slot T-or-NIL). Found the expression ~S instead." the-schema parent local) (return)) ;; Set the slots marked as local-only (let ((slot (car local))) (unless (slot-accessor the-schema slot) (if (second local) ;; Copy down the parent value, once and for all. (let* ((entry (slot-accessor parent slot)) (value (if entry (sl-value entry)))) (unless (formula-p value) ;; Prevent inheritance from ever happening (internal-s-value the-schema slot (g-value parent slot)))) ;; Avoid inheritance and set the slot to NIL. (internal-s-value the-schema slot NIL)))))) ;; Now process constant declarations. (unless *constants-disabled* (merge-prototype-values the-schema :CONSTANT parents constants)) ;; Now process type declarations (when (and do-types *types-enabled*) ;; Copy type declarations down from the parent(s), unless overridden ;; locally. (dolist (parent parents) (iterate-slot-value (parent T T nil) value ;; suppress warning (let ((bits (sl-bits iterate-slot-value-entry))) ; get parent's bits ;; keep only the type information (setf bits (logand bits *type-mask*)) (unless (zerop bits) (let ((the-entry (slot-accessor the-schema slot))) (if the-entry (let ((schema-bits (sl-bits the-entry))) (when (zerop (extract-type-code schema-bits)) ;; Leave type alone, if one was declared locally. (setf (sl-bits the-entry) (logior (logand schema-bits *all-bits-mask*) bits)))) (set-slot-accessor the-schema slot *no-value* bits NIL))))))) ;; Typecheck (iterate-slot-value (the-schema T T nil) (unless (eq value *no-value*) (unless (formula-p value) ; don't bother with formulas. (multiple-value-bind (new-value result) (check-slot-type the-schema slot value) (when (eq result :REPLACE) (s-value the-schema slot new-value))))))))) (defun add-update-slot (schema slot &optional turn-off) "Turn the of the into an :update-slot; add the to the contents of :update-slots, and turn on the internal bit. If is T, make the be no longer an update slot." (let ((entry (slot-accessor schema slot))) (if entry ;; There is an entry - manipulate the bits directly (if turn-off ;; Turn bit off (setf (sl-bits entry) (logand (sl-bits entry) (lognot *is-update-slot-mask*))) ;; Turn bit on (setf (sl-bits entry) (set-is-update-slot (sl-bits entry)))) ;; There is no entry (unless turn-off (set-slot-accessor schema slot *no-value* (set-is-update-slot *local-mask*) NIL)))) (if turn-off (setf (g-value schema :UPDATE-SLOTS) (delete slot (g-value schema :UPDATE-SLOTS))) (pushnew slot (g-value schema :UPDATE-SLOTS)))) (eval-when (:execute :compile-toplevel :load-toplevel) (defun cannot-be-quoted (value) (or (listp value) (and (symbolp value) (not (keywordp value)))))) (eval-when (:execute :compile-toplevel :load-toplevel) (defun process-slot-descriptor (x) (if (listp x) (if (find-if #'cannot-be-quoted (cdr x)) (cons 'list x) `',x) x))) (defun merge-declarations (declaration keyword output) (let ((old (find keyword output :key #'second))) (if old (setf (cadr (third old)) (union (cdr declaration) (cadr (third old)))) (push `(cons ,keyword ',(cdr declaration)) output))) output) (eval-when (:execute :compile-toplevel :load-toplevel) (defun process-slots (slots) "This function processes all the parameters of CREATE-INSTANCE and returns an argument list suitable for do-schema-body. It is called at compile time. RETURNS: a list, with elements as follows: - FIRST: the prototype (or list of prototypes), or NIL; - SECOND: the list of type declarations, in the form (type slot slot ...) or NIL (if there were no type declarations) or :NONE (if the declaration (type) was used, which explicitly turns off all types declared in the prototype(s). - REST OF THE LIST: all slot specifiers, with :IS-A removed (because that information is moved to the prototype list)." (let ((output nil) (is-a nil)) (do ((head slots (cdr head)) (types nil) (had-types nil) ; true if there is a declaration slot) ((null head) (if types (push `(quote ,types) output) (if had-types (push :NONE output) (push NIL output)))) (setf slot (car head)) (cond ((null slot) ;; This is an error. (cerror "Ignore the specification" "Error in CREATE-SCHEMA: NIL is not a valid slot ~ specifier; ignored.~%~ Object ~S, slot specifiers are ~S~%" kr::*create-schema-schema* head)) ((keywordp slot) ;; Process declarations and the like. (case slot (:NAME-PREFIX (pop head)) (:DECLARE (pop head) (dolist (declaration (if (listp (caar head)) (car head) (list (car head)))) (case (car declaration) (:TYPE (setf had-types T) (dolist (spec (cdr declaration)) (push spec types))) ((:CONSTANT :IGNORED-SLOTS :LOCAL-ONLY-SLOTS :MAYBE-CONSTANT :PARAMETERS :OUTPUT :SORTED-SLOTS :UPDATE-SLOTS) (setf output (merge-declarations declaration (car declaration) output))) (t (cerror "Ignore the declaration" "Unknown declaration (~S) in object creation:~%~S~%" (car declaration) declaration))))))) ((listp slot) ;; Process slot descriptors. (if (eq (car slot) :IS-A) (setf is-a (if (cddr slot) `(list ,@(cdr slot)) (cadr slot))) (if (listp (cdr slot)) (if (find-if #'cannot-be-quoted (cdr slot)) (if (cddr slot) (push `(list ,(car slot) . ,(cdr slot)) output) (push `(cons ,(car slot) . ,(cdr slot)) output)) (if (cddr slot) (push `'(,(car slot) . ,(cdr slot)) output) (push `'(,(car slot) . ,(cadr slot)) output))) (push (cdr slot) output)))) (T (cerror "Ignore the specification" "A slot specification should be of the form ~ (:name [values]*) ;~%found ~S instead. Object ~S, slots ~S." slot kr::*create-schema-schema* slots)))) (cons is-a output)))) (defun handle-is-a (schema is-a generate-instance override) (if (or (eq schema is-a) (memberq schema is-a)) (format t "~A: cannot make ~S an instance of itself! ~ Using NIL instead.~%" (if generate-instance "CREATE-INSTANCE" "CREATE-SCHEMA") schema) ;; Make sure :override does not duplicate is-a-inv contents. (let ((*schema-is-new* (not override))) (set-is-a schema is-a)))) (defun do-schema-body (schema is-a generate-instance do-constants override types &rest slot-specifiers) "Create-schema and friends expand into a call to this function." (when (equal is-a '(nil)) (format t "*** (create-instance ~S) called with an illegal (unbound?) class name.~%" schema) (setf is-a NIL)) (unless (listp is-a) (setf is-a (list is-a))) (when is-a (let ((*schema-is-new* T)) ; Bind to prevent search on insertion ; of :is-a-inv in parent schemata. ;; Check for immediate is-a loop, and set the :IS-A slot. (handle-is-a schema is-a generate-instance override))) (do* ((slots slot-specifiers (cdr slots)) (slot (car slots) (car slots)) (initialize-method NIL) (constants NIL) (had-constants NIL) ; true if declared, including NIL (cancel-constants (find '(:constant) slot-specifiers :test #'equal)) (parent (car is-a)) (slot-counter (if is-a 1 0))) ((null slots) ;; Process the type declarations. (unless (eq types :NONE) (dolist (type types) (if (cdr type) (let ((n (encode-type (car type)))) (dolist (slot (cdr type)) (set-slot-type schema slot n))) (format t "*** ERROR - empty list of slots in type declaration ~ for object ~S:~% ~S~%" schema (car type))))) ;; Process the constant declarations, and check the types. (when do-constants (process-constant-slots schema is-a (if had-constants ;; There WAS a constant declaration, perhaps NIL. (if constants (if (formula-p constants) (g-value-formula-value schema :CONSTANT constants NIL) constants) :NONE) ;; There was no constant declaration. NIL) (not (eq types :NONE)))) ;; Merge prototype and local declarations. (dolist (slot slot-specifiers) (when (and (listp slot) (memberq (car slot) '(:IGNORED-SLOTS :LOCAL-ONLY-SLOTS :MAYBE-CONSTANT :PARAMETERS :OUTPUT :SORTED-SLOTS :UPDATE-SLOTS))) (merge-prototype-values schema (car slot) is-a (cdr slot)))) (when generate-instance ;; We are generating code for a CREATE-INSTANCE, really. (kr-init-method schema initialize-method)) schema) (cond ((eq slot :NAME-PREFIX) ;; Skip this and the following argument (pop slots)) ((consp slot) (let ((slot-name (car slot)) (slot-value (cdr slot))) (case slot-name ; handle a few special slots. (:INITIALIZE (when slot-value ;; A local :INITIALIZE method was provided (setf initialize-method slot-value))) (:CONSTANT (setf constants (cdr slot)) (setf had-constants T))) ;; Check that the slot is not declared constant in the parent. (when (and (not cancel-constants) (not *constants-disabled*) (not *redefine-ok*)) (when (and parent (slot-constant-p parent slot-name)) (cerror "If continued, the value of the slot will change anyway" "Slot ~S in ~S was declared constant in prototype ~S!~%" slot-name schema (car is-a)))) (if override ;; This is more costly - check whether the slot already exists, ;; dependencies, etc. (s-value schema slot-name slot-value) ;; No check needed in this case. (setf slot-counter (internal-s-value schema slot-name slot-value))))) (T (format t "Incorrect slot specification: object ~S ~S~%" schema slot))))) (eval-when (:execute :compile-toplevel :load-toplevel) (defun creation-message (name) (when *print-new-instances* (when (and (listp name) (eq (car name) 'QUOTE)) (format *standard-output* "~&Object ~S~%" (eval name)))))) (defun end-create-instance (schema) "Processes the second half of a create-instance. Begin-create-instance must have been called on the ." (process-constant-slots schema (get-local-value schema :IS-A) (get-local-value schema :CONSTANT) nil) (kr-init-method schema (get-local-value schema :INITIALIZE))) ;;; TYPE CHECKING (declaim (inline get-slot-type-code)) (defun get-slot-type-code (object slot) (let ((entry (slot-accessor object slot))) (and entry (get-entry-type-code entry)))) (declaim (inline g-type)) (defun g-type (object slot) (let ((type (get-slot-type-code object slot))) (and type (code-to-type type)))) (defun check-slot-type (object slot value &optional (error-p T) entry) "Check whether has the right type for the in the . RETURNS: if error-p is non-nil: multiple values: - a replacement value, if the user chose to continue and supply a replacement; T if no error; NIL otherwise; - T (if type error and the user did not supply a value), or NIL (if there was no type error), or :REPLACE, if a replacement value was supplied by the user. if error-p is nil: an error string describing what error condition was found. " (let ((type-code (if entry (get-entry-type-code entry) (get-slot-type-code object slot)))) (or (null type-code) (zerop type-code) (check-kr-type value type-code) (let* ((type (code-to-type type-code)) (readable-type (get-type-documentation type)) (message (format nil "bad KR type: value ~S~:[~*~;, a ~S,~] is not valid for slot ~S in~% object ~S. The slot is declared of type ~S~@[,~% i.e., ~A~].~@[~% The value was computed by a formula.~]~%" value value (type-of value) slot object type readable-type (formula-p (get-local-value object slot))))) (if error-p (progn (cerror "Retain old value in the slot" message) (values T T)) message))))) ;; This version allows multiple restart actions. However, it is extremely ;; slow and sets up a ton of garbage (Cons space and Other space). It should ;; not be used for common operations, such as s-value. ;; ;;; (defun check-slot-type (object slot value &optional (error-p T)) ;;; (loop ;;; (restart-case ;;; (return ;;; (let ((type (get-slot-type-code object slot))) ;;; (if type ;;; (if (zerop type) ;;; (values T NIL) ; no type specified ;;; (if (check-kr-type value type) ;;; (values T NIL) ;;; (let* ((readable-type (get-type-documentation type)) ;;; (message ;;; (format ;;; nil ;;; "bad KR type: value ~S~:[~*~;, a ~S,~] is not valid for slot ~S in~% object ~S. The slot is declared of type ~S~@[,~% i.e., ~A~].~@[~% The value was computed by a formula.~]~%" ;;; value value (type-of value) slot object ;;; (code-to-type type) ;;; readable-type ;;; (formula-p (get-value object slot))))) ;;; (if error-p ;;; (error message) ;;; message)))) ;;; (values T NIL)))) ;;; ;; Allow the user to specify different continuation strategies if we ;;; ;; get an error and enter the debugger. ;;; (nil (arg) ;;; :report "Retain old value in the slot" ;;; :interactive (lambda () ;;; (list value)) ;;; arg ;;; (return (values NIL T))) ;;; (nil (arg) ;;; :report "Enter replacement value for the slot" ;;; :interactive (lambda () ;;; (format t "New value for ~S slot ~S: " object slot) ;;; (force-output) ;;; (list (read))) ;;; (multiple-value-bind (new-value result) ;;; (check-slot-type object slot arg T) ;;; (cond ((null result) ;;; ;; no error in replacement value ;;; (return (values arg :REPLACE))) ;;; ((eq result :REPLACE) ;;; (return (values new-value :REPLACE))))))))) (defun set-slot-type (object slot type) (let ((entry (or (slot-accessor object slot) (set-slot-accessor object slot *no-value* type NIL)))) (setf (sl-bits entry) (logior (logand (sl-bits entry) *all-bits-mask*) type)))) (defun s-type (object slot type &optional (check-p T)) "Adds a type declaration to the given , eliminating any previous type declarations. If is true, checks whether the value already in the satisfies the new type declaration." (set-slot-type object slot (if type (encode-type type) ;; 0 means "no type declarations" 0)) (when check-p (let* ((entry (slot-accessor object slot)) (value (if entry (sl-value entry)))) (unless (or (eq value *no-value*) (formula-p value)) (multiple-value-bind (new-value result) (check-slot-type object slot value T entry) (cond ((eq result :REPLACE) (s-value object slot new-value))))))) type) ;;; DECLARATION ACCESSORS (defun get-declarations (schema declaration) "RETURNS: a list of all slots in the which are declared as . Example: (get-declarations A :type)" (let ((slots nil)) (case declaration (:CONSTANT nil) (:TYPE nil) ((:IGNORED-SLOTS :SORTED-SLOTS :MAYBE-CONSTANT :PARAMETERS :OUTPUT :UPDATE-SLOTS) (return-from get-declarations (g-value schema declaration))) (:LOCAL-ONLY-SLOTS (return-from get-declarations (g-local-value schema declaration))) (t (return-from get-declarations NIL))) ;; Visit all slots, construct information (iterate-slot-value (schema T T NIL) value ;; suppress warning (let ((bits (sl-bits iterate-slot-value-entry))) (case declaration (:CONSTANT (when (is-constant bits) (push slot slots))) (:TYPE (let ((type (extract-type-code bits))) (unless (zerop type) (push (list slot (code-to-type type)) slots))))))) slots)) (defun get-slot-declarations (schema slot) (let* ((entry (slot-accessor schema slot)) (bits (if entry (sl-bits entry) 0)) (declarations nil)) (if (is-constant bits) (push :CONSTANT declarations)) (if (memberq slot (g-value schema :update-slots)) (push :UPDATE-SLOTS declarations)) (if (memberq slot (g-value schema :local-only-slots)) (push :LOCAL-ONLY-SLOTS declarations)) (if (memberq slot (g-value schema :maybe-constant)) (push :MAYBE-CONSTANT declarations)) (let ((type (extract-type-code bits))) (unless (zerop type) (push (list :type (code-to-type type)) declarations))) ;; Now process all declarations that are not stored in the slot bits. (dolist (s-slot '(:IGNORED-SLOTS :PARAMETERS :OUTPUT :SORTED-SLOTS)) (let ((values (g-value schema s-slot))) (if (memberq slot values) (push s-slot declarations)))) declarations)) ;;; Define support fns for basic builtin types. These definitions must come ;; before the file CONSTRAINTS.LISP is compiled. (defun T-P (value) (declare #.*special-kr-optimization* (ignore value)) T) (defun no-type-error-p (value) (cerror "Return T" "KR typechecking called on value ~S with no type" value) T) (defun get-type-definition (type-descriptor) "Given the symbol which names a KR type (e.g., 'KR-BOOLEAN), this function returns the type expression that was used to define the type. Example: (base-type-for 'bitmap-or-nil) ==> (OR NULL (IS-A-P OPAL:BITMAP)) " (let* ((name (if (symbolp type-descriptor) (symbol-name type-descriptor))) (code (gethash name kr::types-table))) (when name (maphash #'(lambda (key value) (when (and (eq value code) (not (stringp key))) (return-from get-type-definition key))) kr::types-table)))) ;;; FORMULA META-SLOTS (defun find-meta (formula) "Returns, or inherits, the meta-schema associated with the , or NIL if none exists." (let ((meta (a-formula-meta formula))) (unless meta ;; Try to inherit the meta-information from the formula's parent(s). (do ((f (a-formula-is-a formula) (a-formula-is-a f))) ((null f)) (if (setf meta (a-formula-meta f)) ;; Do not copy down the meta-schema, to reduce storage. (return)))) meta)) (defun g-formula-value (formula slot) "RETURNS: the value of the meta-slot in the , or NIL if none exists. The value may be inherited from the 's parent(s), but no new meta-schema is created as a result of this operation." (when (formula-p formula) (case slot (:NUMBER (a-formula-number formula)) (:VALID (cache-is-valid formula)) (:DEPENDS-ON (a-formula-depends-on formula)) (:SCHEMA (a-formula-schema formula)) (:SLOT (a-formula-slot formula)) (:CACHED-VALUE (a-formula-cached-value formula)) (:PATH (a-formula-path formula)) (:IS-A (a-formula-is-a formula)) (:FUNCTION (a-formula-function formula)) (:LAMBDA (a-formula-lambda formula)) (:IS-A-INV (a-formula-is-a-inv formula)) (:META (a-formula-meta formula)) (T ;; Normal case: this is not a built-in formula slot. Use meta-slots. (let ((meta (find-meta formula))) (if meta (g-value meta slot))))))) (defun s-formula-value (formula slot value) "Sets the value of the meta-slot in the to be . If no meta-schema exists for the , creates one." (when (formula-p formula) (let ((meta (a-formula-meta formula))) (unless meta (setf meta (find-meta formula)) (if meta ;; We do have a meta-schema that can be inherited. (let ((new (create-schema nil))) (s-value new :is-a (list meta)) (setf meta new)) ;; Create a brand new, non-inheriting meta-schema. (setf meta (create-schema nil))) ;; Install the new meta-schema. (setf (a-formula-meta formula) meta)) (s-value meta slot value)))) ;;; READER MACROS (eval-when (:execute :compile-toplevel :load-toplevel) (defun k-reader (stream subchar arg) "Modify the readtable so #k is read as the KR object NAME, if defined. This allows objects written with the *print-as-structure* notation to be read back in." (declare (ignore subchar arg)) (let ((next-char (read-char stream))) (if (char= next-char #\<) ;; This is a KR #k<...> object name (let ((string "")) (do ((c (read-char stream) (read-char stream))) ((char= c #\>)) (setf string (format nil "~A~C" string c))) (setf string (read-from-string string)) (if (and (boundp string) (or (schema-p (symbol-value string)) (formula-p (symbol-value string)))) (symbol-value string) (cerror "Ignore the object" "Non-existing KR object: ~S" string))) ;; This is something else (cerror "Ignore the token" " Illegal character ~S after reader macro #k (expecting \"<\")" next-char))))) ;; Install this reader macro in the standard readtable. (eval-when (:execute :compile-toplevel :load-toplevel) (set-dispatch-macro-character #\# #\k (function k-reader))) (defun o-formula-reader (stream subchar arg) "Modify the readtable to #f(...) is read as (o-formula ...). For example, this allows you to write (S-VALUE A :LEFT #F(GVL :TOP))" (declare (ignore subchar arg)) `(o-formula ,(read stream t nil t))) (set-dispatch-macro-character #\# #\f #'o-formula-reader)