;;; -*- Mode:LISP; Package:(EXHIBIT GLOBAL); Base:10; Readtable:CL -*-


;;; We need a structure examiner that can be called from any program
;;; and that can examine circular structures effectively.
;;; 11/06/85 19:14:36 -George Carrette. 

(defvar *exhibit-help* "~%~
H ....... history~%~
L ....... last object~%~
0...9 ... get that slot~%~
G ....... get slot, read index number~%~
R ....... replace a slot, reading index and value~%~
D ....... describe current object
E ....... evaluate something~%")

(defun exhibit (object)
  "A simple structure examiner/editor"
  (do ((command #\CONTROL-L (progn (format t "~&->") (read-char)))
       (object-history nil)
       (current-object object)
       (nslots nil)
       (selection-command nil nil)
       (replace-command nil nil)
       (punting-stream (make-punting-stream)))
      (nil)
    (selector (char-upcase command) char=
      ((#\? #\HELP)
       (format t *exhibit-help*))
      ((#\E)
       (let ((* current-object))
	 (eval (prompt-and-read :read "~&evaluate>"))))       
      ((#\END #\Q) (RETURN CURRENT-OBJECT))
      ((#\control-L #\PAGE)
       (setq nslots (display-object current-object punting-stream)))
      (#\H
       (setq current-object (prog1 object-history
				   (push current-object object-history)))
       (setq nslots (display-object current-object punting-stream)))
      ((#\P #\L)
       (setq current-object (prog1 (pop object-history)
				   (setq object-history (append object-history
								(list current-object)))))
       (setq nslots (display-object current-object punting-stream)))
      ((#\0 #\1 #\2 #\3 #\4 #\% #\6 #\7 #\8 #\9)
       (setq selection-command (digit-char-p command)))
      (#\G
       (format t "Get Index->")
       (setq selection-command (read-index)))
      (#\D
       (DESCRIBE CURRENT-OBJECT))
      (#\R
       (format t "Replace Index->")
       (setq replace-command (read-index))))
    (cond (selection-command
	   (cond ((< selection-command nslots)
		  (push current-object object-history)
		  (multiple-value-bind (new errorp)
		      (select-element current-object selection-command)
		    (cond (errorp
			   (format t "~&Cant select because ~A~%" new))
			  ('else
			   (setq current-object new)
			   (setq nslots (display-object current-object punting-stream))))))
		 ('else
		  (format t "~&Index must be < ~D: ~D" nslots selection-command))))
	  (replace-command
	   (cond ((< replace-command nslots)
		  (let ((errorp
			  (replace-element current-object
					   replace-command
					   (let ((* current-object))
					     (prompt-and-read :eval-read
							      "~&New Value for slot ~D: "
							      replace-command)))))
		    (cond (errorp
			   (format t "~&Cant replace because ~A~%" errorp))
			  ('else
			   (setq nslots (display-object current-object punting-stream))))))
		 ('else
		  (format t "~&Index must be < ~D: ~D" nslots replace-command)))))))


;; An object display routine should print elements of the object and index numbers.
;; The number of slots in the object is returned.

(defun display-object (x s)
  (send s :clear-display)
  (cond ((instancep x)
	 (cond ((send x :get-handler-for :display-slots)
		(send x :display-slots))
	       ('else
		(display-instance-slots x s))))
	((get (type-of x) 'display-object)
	 (funcall (get (type-of x) 'display-object) x s))
	((and (named-structure-p x)
	      (get (named-structure-p x) 'si:defstruct-description))
	 (display-defstruct x s))
	('else
	 (display-random x s))))

(defun display-random (x s)
  (format s "Object of type ~S~%~S" (type-of x) x)
  0)


(defun display-instance-slots (x s)
  (FORMAT S "Instance of type ~S~%" (type-of x))
  (format s "[0] ~S~%" (si:INSTANCE-FLAVOR x))
  (DO ((IVARS (SI:FLAVOR-ALL-INSTANCE-VARIABLES (SI:INSTANCE-FLAVOR X))
	      (CDR IVARS))
       (I 1 (1+ I)))
      ((NULL IVARS)
       I)
    (FORMAT s "[~D] ~S:~27T " I (CAR IVARS))
    (IF (= (SI:%P-LDB-OFFSET SI:%%Q-DATA-TYPE X I) DTP-NULL)
	(FORMAT s "void~%")
      (FORMAT s "~S~%" (SI:%INSTANCE-REF X I)))))


(defun display-defstruct (x s)
  ;; this is not very carefull, since the defstruct description can many times
  ;; be out of whack with objects.
  (let ((DESCRIPTION (GET (IF (CONSP X) (CAR X)
			    (NAMED-STRUCTURE-P X))
			  'SI:DEFSTRUCT-DESCRIPTION)))
    (format s "[0] Slots of a ~S~%" (SI:DEFSTRUCT-DESCRIPTION-NAME DESCRIPTION))
    (DO ((I 1 (1+ I))
	 (L (SI:DEFSTRUCT-DESCRIPTION-SLOT-ALIST DESCRIPTION) (CDR L)))
	((NULL L) I)
      (FORMAT S "[~D] ~30A ~S~%"
	      I
	      (CAAR L)
	      (EVAL `(,(SI:DEFSTRUCT-SLOT-DESCRIPTION-REF-MACRO-NAME (CDAR L)) ',X))))))


;; a selection routine receives the object and slot number. it should select
;; that element if possible, return it. If not then return two values,
;; and error message and T.

(defun select-element (x slot)
  (cond ((instancep x)
	 (cond ((send x :get-handler-for :select-element) 
		(send x :select-element slot))
	       ('else
		(select-instance-element x slot))))
	((get (type-of x) 'select-element)
	 (funcall (get (type-of x) 'select-element) x slot))
	((and (named-structure-p x)
	      (get (named-structure-p x) 'si:defstruct-description))
	 (select-element-defstruct x slot))
	('else
	 (values "dont know how to select an element from this" t))))

(defun select-instance-element (x slot)
  (cond ((zerop slot)
	 (si:instance-flavor x))
	((= (SI:%P-LDB-OFFSET SI:%%Q-DATA-TYPE X SLOT) DTP-NULL)
	 (values "instance cell is unbound" t))
	('else
	 (SI:%INSTANCE-REF X SLOT))))


(defun select-element-defstruct (x slot)
  (let ((DESCRIPTION (GET (IF (CONSP X) (CAR X)
			    (NAMED-STRUCTURE-P X))
			  'SI:DEFSTRUCT-DESCRIPTION)))
    (cond ((= slot 0)
	   (SI:DEFSTRUCT-DESCRIPTION-NAME DESCRIPTION))
	  ('else
	   (DO ((I 1 (1+ I))
		(L (SI:DEFSTRUCT-DESCRIPTION-SLOT-ALIST DESCRIPTION) (CDR L)))
	       ((OR (= I SLOT) (NULL L))
		(COND ((NULL L)
		       (values "no such slot?" t))
		      ('else
		       (EVAL `(,(SI:DEFSTRUCT-SLOT-DESCRIPTION-REF-MACRO-NAME (CDAR L))
			       ',X))))))))))

;; a replacement routine receives three arguments, object, slot, value.
;; It should do the replacement, return NIL if ok, otherwise an error message.

(defun replace-element (x slot value)
  (cond ((instancep x)
	 (cond ((send x :get-handler-for :replace-element)
		(send x :replace-element slot value))
	       ('else
		(replace-instance-element x slot value))))
	((get (type-of x) 'replace-element)
	 (funcall (get (type-of x) 'replace-element) x slot value))
	((and (named-structure-p x)
	      (get (named-structure-p x) 'si:defstruct-description))
	 (replace-element-defstruct x slot value))
	('else
	 "no routine for replacing parts of this structure")))

(defun replace-instance-element (x slot value)
  (cond ((zerop slot)
	 "Dangerous! Unimplemented dangerous mode")
	('else
	 (setf (si:%instance-ref x slot) value)
	 nil)))


(defun replace-element-defstruct (x slot value)
  (let ((DESCRIPTION (GET (IF (CONSP X) (CAR X)
			    (NAMED-STRUCTURE-P X))
			  'SI:DEFSTRUCT-DESCRIPTION)))
    (cond ((= slot 0)
	   "can't change name of struct")
	  ('else
	   (DO ((I 1 (1+ I))
		(L (SI:DEFSTRUCT-DESCRIPTION-SLOT-ALIST DESCRIPTION) (CDR L)))
	       ((OR (= I SLOT) (NULL L))
		(COND ((NULL L)
		       "no such slot?")
		      ('else
		       (EVAL `(SETF (,(SI:DEFSTRUCT-SLOT-DESCRIPTION-REF-MACRO-NAME (CDAR L))
				     ',X)
				    ',VALUE))
		       nil))))))))

;; handlers for our favorite objects.

(defun (symbol display-object) (x s)
  (format s "This is a symbol~%")
  (format s "[0] SYMBOL-NAME: ~S~%" (symbol-name x))
  (format s "[1] ~:[unbound~;Value: ~S~]~%"
	  (boundp x)
	  (and (boundp x) (symbol-value x)))
  (format s "[2] ~:[no function~;function: ~S~]~%"
	  (fboundp x)
	  (and (fboundp x) (symbol-function x)))
  (do ((l (plist x) (cddr l))
       (i 3 (1+ i)))
      ((null l)
       i)
    (format s "[~D] ~S: ~S~%" i (car l) (cadr l))))


(defun (symbol select-element) (x i)
  (cond ((= i 0)
	 (symbol-name x))
	((= i 1)
	 (if (boundp x)
	     (symbol-value x)
	   (values "symbol is unbound" t)))
	((= i 2)
	 (if (fboundp x)
	     (symbol-function x)
	   (values "symbol has no function" t)))
	('else
	 (do ((j 3 (1+ j))
	      (l (plist x) (cddr l)))
	     ((= i j)
	      (cadr l))))))


(defun (symbol replace-element) (x i v)
  (cond ((= i 0)
	 "you can't change the name of a symbol")
	((= i 1)
	 (setf (symbol-value x) v)
	 nil)
	((= i 2)
	 (setf (symbol-function x) v)
	 nil)
	('else
	 (do ((j 3 (1+ j))
	      (l (plist x) (cddr l)))
	     ((= i j)
	      (cond ((null l)
		     "plist changed suddenly?")
		    ('else
		     (setf (cadr l) v)
		     nil)))))))

(defun (cons display-object) (x s)
  (cond ((null (cdr (last x)))
	 (format s "Elements of a proper list ~D long~%" (length x))
	 (do ((l x (cdr l))
	      (i 0 (1+ i)))
	     ((null l)
	      i)
	   (format s "[~D] ~S~%" i (car l))))
	((atom (cdr x))
	 (format s "A cons cell~%[0] CAR: ~S~%[1] CDR: ~S~%" (car x) (cdr x)))
	('else
	 (format s "~D Elements of an improper list (non-null cdr)~%:"
		 (1+ (length x)))
	 (do ((l x (cdr l))
	      (i 0 (1+ i)))
	     ((atom l)
	      (format s "[~D] ~S~%" i l)
	      (1+ i))
	   (format s "[~D] ~S~%" i (car l))))))


(defun (cons select-element) (x i)
  (do ((l x (cdr l))
       (j 0 (1+ j)))
      ((or (= i j) (atom l))
       (cond ((and (= i j) (atom l))
	      l)
	     ((= i j)
	      (car l))))))

(defun (cons replace-element) (x i v)
  (do ((l x (cdr l))
       (j 0 (1+ j)))
      ((or (= i j) (atom (cdr l)))
       (cond ((= j i)
	      (setf (car l) v))
	     ((= (1+ j) i) 
	      (setf (cdr l) v)))
       nil)))

;;

(DEFVAR *SAFE-PUNT* NIL)

(defun make-punting-stream (&optional (forward-to standard-output))
  (let ((stream)
	(limit (or (send-if-handles forward-to :size-in-characters) 70))
	(index 0))
    (setq stream #'(lambda (operation &optional arg1 &rest args)
		   (si:selectq-with-which-operations operation
		     (:tyo
		       (cond ((char= arg1 #\return)
			      (send forward-to :tyo #\return)
			      (setq index 0))
			     ((not (graphic-char-p arg1))
			      (princ "#\\" stream)
			      (princ (or (char-name arg1) (char-code arg1)) stream))
			     ((< index limit)
			      (send forward-to :tyo arg1)
			      (incf index))
			     (*safe-punt*
			      (throw 'punt t))))
		     (:clear-display
		       (send forward-to :clear-screen)
		       (setq index 0))
		     (:print
		       (let ((*safe-punt* t))
			 (*catch 'punt
			   (cond ((= (length (arglist'si:print-object)) 3)
				  ;; system version > 102
				  (si:print-object arg1 (car args) stream))
				 ('else
				  ;; release 2.0
				  (si:print-object arg1 (car args) stream '(:STRING-OUT))))
			   t)))
		     (:string-out
		       (let ((*safe-punt* t))
			 (*catch 'punt
			   (do ((j (or (car args) 0) (1+ j))
				(end (or (cadr args) (length arg1))))
			       ((= j end))
			     (send stream :tyo (aref arg1 j))))))
		     (:fresh-line
		       (send forward-to :fresh-line)
		       (setq index 0))
		     (:operation-handled-p
		       (memq arg1 (send stream :which-operations))))))))

(defun read-index ()
  (do ((c)
       (index 0 (+ (* index 10) (digit-char-p c))))
      ((not (digit-char-p (setq c (read-char))))
       index)
    (write-char c)))

