6e35003d |
;;; -*- 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 <schema>."
(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 <slot> of the <schema>."
(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 <demon> 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 <slot> in the <schema>. 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 "<slot ~S value ~S, bits ~S"
(sl-name slot) (sl-value slot) (sl-bits slot))
(if (full-sl-p slot)
(format stream ", dependents ~S>" (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 <value> to <schema>, following the inverse
of <slot>."
(let ((inverse (cadr (assocq slot *relations*)))) ; e.g., is-a-inv
(when inverse
;; If the relation has an INVERSE slot, remove <schema> 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)
;; <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 <slot>."
(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 <schema> 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 <values> are being added to <slot>, see if we need to put in an
inverse link to <schema> from each of the <values>.
This happens when <slot> is a relation with an inverse."
(let ((inverse (if (eq slot :is-a)
:is-a-inv
(cadr (assocq slot *relations*)))))
(when inverse
;; <values> 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 <slot> (a relation) to <values>. Check that the
latter contains valid relation entries.
RETURNS: <values> (or a list of a single value, if <values> 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 <type>."
(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 <value> to be physically copied to the <a-slot> of all
instances of the <schema>, 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:
- <value>: the new (i.e., current) value for the <schema>
- <old-bits>: the setting of the slot bits for the <schema>, before the
current value-setting operation.
- <is-first>: 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 <slot> in the <schema> 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 <position> in the <slot> of the
<schema>. 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 <slot> of the <schema> 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 <slot> of the <schema> 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 <function> is called on all children which actually inherit the
values in the <a-slot> of the <schema>. This is determined by a fast
check (the list of values should be EQ to that of the parent).
Note that the <function> 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 <slot> in the <schema> to be
<new-value>."
(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 <slot> of the <schema> 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 <slot> in the <schema> 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 <value>.
(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 <except-schema> 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 <formula>, since it is no
longer installed on a slot.
INPUTS:
- <formula>: the formula to get rid of
- <hard-p>: if T, do a more thorough job of deleting everything, and
destroy the <formula> schema itself."
(when (a-formula-number formula)
(eliminate-formula-dependencies formula NIL)
(when remove-from-parent
;; Eliminate the <formula> 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 <slot>, and all the values it contains, from the <schema>,
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 <recursive-p>, 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 <expression>, or one of its subexpressions,
directly depends on the <target>. 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 <schema>, 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 <schema> 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 <relation> slot was changed, all children of the <schema> 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 <function> 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 <function> 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 <schema>. 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.
<control> can be one of the following:
- T, which means that the <schema> 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 <schema> is printed in its entirety (i.e. no
schema control.)
If <inherit> is non-nil, slots that have been inherited are also printed.
<indent> 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 <schema> from which the <slot> 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 <name>, making sure to destroy the old
one by that name if it exists. The initial number of slots is
<needed-slots>."
(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 <name>.
(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 <slot> in <schema> 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 <slot> of the <schema> into an :update-slot; add the <slot> to
the contents of :update-slots, and turn on the internal bit. If
<turn-off> is T, make the <slot> 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 <schema>."
(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 <value> has the right type for the <slot> in the <object>.
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 <slot>, eliminating any previous
type declarations. If <check-p> is true, checks whether the value
already in the <slot> 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 <schema> which are declared as
<declaration>.
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 <formula>, 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 <slot> in the <formula>, or NIL if none
exists. The value may be inherited from the <formula>'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 <slot> in the <formula> to be <value>.
If no meta-schema exists for the <formula>, 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<NAME> 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)
|