(in-package "FRAMES")
;; Created: 10/17/94
;; Author:  Will Fitzgerald
;; Description: based on various versions of frame code developed by Chris
;;              Riesbeck

;; Packages

(eval-when (load eval compile)
  (unless (find-package :frames)
    (make-package :frames)))

(in-package :frames)

(use-package :tables)

(export '(define-frame def-frame 
           frame-of ->frame ->name frame-p
           abstractions specializations all-abstractions features
           attribute-value inherited-attribute-value
           abstp specp part-of whole-of 
           abst-or-whole-of spec-or-part-of
           clear-frame-memory print-frames))

;; data structures for frames: Frames form a class, whose instances are stored
;; in a table keyed by their symbolic names.

(deftable frame-of)

(defclass frame ()
  ((name :initarg :name :accessor name)
   (abstractions :initarg :abstractions :initform nil :accessor abstractions)
   (specializations :initarg :specializations :initform nil :accessor specializations)
   (all-abstractions :initarg :all-abstractions :accessor all-abstractions)
   (features :initarg :features :initform nil :accessor features)))

(defmethod print-object ((frame frame) stream)
  (format stream "[~S]" (name frame)))

(defmethod name ((frame t)) frame)

(defun frame-p (object)
  (typep object (find-class 'frame)))

(defun ->frame (object)
  (if  (frame-p object) object
       (frame-of object)))

(defun ->name (object)
  (if (frame-p object)
    (name object)

(defun force-frame (name)
  (or (frame-of name)
      (setf (frame-of name) 
            (make-instance 'frame 
              :name name
              :all-abstractions (list name)))))

;; Data structure for features (slots). A attribute/value pair.

(defclass feature ()
  ((attribute :initarg :attribute :accessor attribute)
   (value :initarg :value :initform nil :accessor value)))

(defun feature-p (object)
  (typep object (find-class 'feature)))

(defmethod make-feature (attribute value)
  (make-instance 'feature :attribute attribute :value value))

(defun make-features (attribute-value-list)
  (loop for (attribute value) in attribute-value-list
        collect (make-feature attribute value)))

(defmethod print-object ((feature feature) stream)
  (with-slots (attribute value) feature
    (print-unreadable-object (feature stream :type t :identity t)
      (format stream "~S ~S" attribute value))))

(defmethod feature-named ((frame frame) attribute)
  (loop for feature in (features frame)
        when (eq (attribute feature) attribute)
        return feature))
(defmethod attribute-value ((frame frame) attribute)
  (let ((feature (feature-named frame attribute)))
    (if feature (value feature) nil)))

(defmethod (setf attribute-value) (value (frame frame) attribute)
  (let ((feature (feature-named frame attribute)))
    (if feature 
      (setf (value feature) value)
      (let ((new-feature (make-feature :attribute attribute :value value)))
        (push new-feature (features frame))

;; inherited attribute values

(defun inherited-attribute-value (frame attribute)
  (or (attribute-value (->frame frame) attribute)
      (loop for abstraction in (abstractions frame)
            thereis (inherited-attribute-value (->frame abstraction) attribute))))
(defmethod part-of ((part frame) (whole frame))
  (member (name part) (all-features whole)
          :key 'value))

(defmethod part-of ((part t) (whole t))
  (let ((whole (frame-of whole))
        (part (frame-of part)))
    (if (and whole part)
      (part-of part whole)

(defmethod whole-of ((whole t) (part t))
  (part-of whole part))

;; Abstractions and specializations

(defmethod all-abstractions ((frame t))
  (let ((frame-maybe (frame-of frame)))
    (if frame-maybe
      (all-abstractions frame-maybe)
      (list frame))))

(defmethod update-specializations ((frame frame))
  (loop for abstraction in (abstractions frame) doing
        (setf (specializations (force-frame abstraction))
              (pushnew (name frame) (specializations (frame-of abstraction))))))

(defmethod update-abstractions ((frame frame)) 
  (setf (all-abstractions frame)
        (calculate-all-abstractions frame))
  (loop for specialization in (specializations frame) doing
        (update-abstractions (frame-of specialization))))
(defmethod calculate-all-abstractions* ((frame frame))
   ((null (abstractions frame)) nil)
   (t (append (abstractions frame)
              (loop for abstraction in (abstractions frame)
                    (calculate-all-abstractions* (force-frame abstraction)))))))

(defmethod calculate-all-abstractions ((frame frame))
  (cons (name frame) (remove-duplicates (calculate-all-abstractions* frame))))

(defmethod abstp ((abst frame) (spec frame))
  (member (name abst) (all-abstractions spec) :test 'eq))

(defmethod abstp ((abst t) (spec t))
  (let ((af (frame-of abst))
        (sf (frame-of spec)))
    (if (and af sf)
      (abstp af sf)
      (eql abst spec))))

(defmethod specp ((spec t) (abst t))
  (abstp abst spec))

(defun abst-or-whole-of (big small)
  (or (abstp big small)
      (whole-of big small)))

(defun spec-or-part-of (small big)
  (or (specp small big)
      (part-of small big)))

;; Interface to clear memory and define frames

(defun clear-frame-memory ()
  (clear-table (frame-of)))

 #'(lambda(stream char)
     (declare (ignore char))
     `(frames:frame-of ',@(read-delimited-list #\] stream t)))
 nil  ; not non-terminating.  Cannot be embedded w/in symbols

;;; causes a right-bracket w/o a left to signal an error
(set-macro-character #\] (get-macro-character #\) ) nil)

(defun define-frame (name abstractions attribute-value-list)
  (let ((frame (force-frame name)))
    (setf (abstractions frame) (mapcar 'name abstractions))
    (setf (features frame) (make-features attribute-value-list))
    (update-specializations frame)
    (update-abstractions frame)

(defmacro def-frame (name &optional abstractions &rest attribute-value-list)
  `(define-frame ',name ',abstractions ',attribute-value-list))

;; Frame finding

(defmethod all-features ((frame frame))
    (loop for abstraction in (abstractions frame)
          appending (all-features (frame-of abstraction)))
    (features frame))
   :key 'attribute))
(defun find-frame (abst features)
  "Find a frame starting at abst, with the features listed."
  (if (null features) (->frame abst)
      (let ((specs (find-specs abst features)))
        (if (and (null (rest specs))
                 (features-subsetp features (first specs)))
          (->frame (first specs))
                  (gen-frame-name (first specs))
                  specs features)))))

(defun find-specs (abst features)
  "Find the most specific specialization of abst."
  (or (remove-duplicates
       (loop for spec in (specializations (->frame abst))
             when (features-abstp spec features)
             nconc (find-specs spec features)))
      (list abst)))

(defun features-abstp (abst features)
  (loop for (attribute value) in features
        (abstp (inherited-attribute-value (->frame abst) attribute) value)))

(defun features-subsetp (features abst)
  (subsetp features (all-features (->frame abst))
           #'(lambda (feature-list feature)
               (and (eql (first feature-list) (attribute feature))
                    (eql (second feature-list)  (value feature))))))

(defun gen-frame-name (name)
  (gentemp (format nil "~A-" (symbol-name name))))

(defun features->feature-specs (features)
  (loop for feature in features 
        collecting (list (attribute feature) (value feature))))

;;; Printing utilities
;;; ----------------------------------------------------------------------

;;; (DISPLAY-FRAME frame [stream]) => no values

;;; DISPLAY-FRAME prints the frame in a readable fashion on the stream
;;; (which defaults to the standard output). The frame argument
;;; can be either the name of a frame or an internal frame structure.
;;; Nested frames are printed in full form the first time they are
;;; seen.

(defun display-frame (frame &optional (stream *standard-output*))
  (cond ((null frame) nil)
	((not (frame-p frame))
	 (display-frame (frame-of frame)))
	 (let ((*frames-shown* '()))
	   (declare (special *frames-shown*))
	   (format stream "~%~S~%" (name frame))
	   (pprint-frame-info frame stream 4)
	   (format stream "~%")

;;; (PPRINT-frame-INFO frame stream left-margin) => undefined

;;; PPRINT-frame-INFO prints internal frame structures in a readable
;;; fashion on stream, indented left-margin number of spaces.

(defun pprint-frame-info (frame stream left-margin)
  (declare (special *frames-shown*))
  (unless (or (null frame) (member frame *frames-shown*))
    (push frame *frames-shown*)
    (loop for abst in (abstractions frame)
	  do (format stream "~VT:ISA ~S~%" left-margin abst))
    (loop for feature in (features frame)
	  do (format stream "~VT~S ~S~%" 
		     (attribute feature)
		     (value feature))
	     (pprint-frame-info (frame-of (value feature))
			      (+ left-margin 4)))))

(defun display-frames-with-roles (name roles
		                       &optional (stream *standard-output*) 
		                       &aux shown)
  (labels ((show (name prefix)
             (let* ((frame (->frame name))
                    (specs (specializations frame))
                   (features (and roles (features frame))))
               (cond ((member name shown)
                      (format stream
			      (if (or specs features) "~S...~%" "~S~%") name))
                      (format stream "~S~%" name)
                      (push name shown)
                      (when features
                        (let ((bar (if specs "|" " ")))
                          (dolist (feature features)
                            (when (and
                                   (feature-p feature)
                                   (member (attribute feature) roles))
                              (format stream "~A ~A ~S ~S~%" prefix bar
                                      (attribute feature)
                                      (value feature))))))
                      (when specs
                        (do ((next-prefix (format nil "~A |   " prefix))
                             (last-prefix (format nil "~A     " prefix))
                             (l specs (cdr l)))
                            ((null (cdr l))
                             (format stream "~A +-- " prefix)
                             (show (car l) last-prefix))
                          (format stream "~A |-- " prefix)
                          (show (car l) next-prefix))))))))
    (show name "")