; XLISP INSPECTOR/EDITOR by TOM ALMY
; This is a rewritten and improved version of "REPAIR"
; Revised 10/96 to include functions INSPECT and DESCRIBE

; Use as (ins <symbol>)  or (insf <symbol>) to repair only the function
; binding, with the capability of changing the argument list and type
; (MACRO or LAMBDA). Functions INS and INSF are interned in package TOOLS.

; Common Lisp standard functions (inspect <object>) and (describe <object>)
; do not quote their arguements and work with any arbitrary object.

; Editor alters the "selection" by copying so that aborting  all changes
;  is generally posible.
; Exception: when editing a closure, if the closure is Backed out of, the
;   change is permanent.

; Do not create new closures, because the environment will be incorrect.

; Closures become LAMBDA or MACRO expressions when edited.  Only
; the closure body may be changed; the argument list cannot be successfully
; modified, nor can the environment.

; For all commands taking a numeric argument, the first element of the
; selection is the 0th (as in NTH function).

; Structure elements, class methods, instance variables, and properties
; are selected by name, using the E command.

; For class objects, only the methods, selectors and class variables
; can be edited. Class variables can only be changed if instance variables
; of class instances can be changed (see next paragraph).

; For instance objects, instance variables can be examine
; (if the object understands the message :<ivar> for the particular ivar),
; and changed if :SET-IVAR is defined for that class (as it is if CLASSES.LSP
; is used)

; COMMANDS (case is significant):
;	A -- select the CAR of the current selection.
;	D -- select the CDR of the current selection.
;	e n -- select element n
;	r n x -- replaces element n with (quoted) x.
;	X -- exit, saving all changes
;	Q -- exit, without changes
;	b -- go back one level (as before A, D or e commands)
;       B n -- go back n levels.
;	l -- display selection using pprint; if selection is symbol, give
;	     short description
;	v -- verbosity toggle
;       . n -- change maximum print length (default 10)
;       # n -- change maximum print depth (default 3)    
;	! x -- evaluates x and prints result
;		The symbol tools:@ is bound to the selection
;	R x -- replaces the selection with evaluated x.
;		The symbol tools:@ is bound to the selection
; additional commands if selection is a list or array:
;	( n m -- inserts parenthesis starting with the nth element,
;		for m elements.
;	) n -- removes parenthesis surrounding nth element of selection,
;              which may be array or list
;	[ n m -- as in (, but makes elements into an array
;	i n x -- inserts (quoted) x before nth element in selection.
;	d n -- deletes nth element in selection.
; additional command if selection is a list:
;	S x y -- all occurances of (quoted) y are replaced with 
;		(quoted) x.  EQUAL is used for the comparison.


#+:packages
(unless (find-package "TOOLS")
	(make-package "TOOLS" :use '("XLISP")))

(in-package "XLISP")
(export '(inspect describe)) ;; Common Lisp standard functions we'll define

(in-package "TOOLS")

(export '(ins insf @))

; Global variable used by inspect functions

(defparameter *ins-exit* 0)   ; "returning" flag
(defparameter *ins-name* nil) ; name of what we are editing

(defvar *ins-plev* 3)	; initial print level used
(defvar *ins-plen* 10)	; initial print length used
(defvar *verbosity* t)  ; printing verbosity flag					   
(defconstant *LPAR* #\()
(defconstant *RPAR* #\))

			     
; inspect a symbol -- the generic entry point

(defmacro ins (a)
    (unless (symbolp a) (error "~s is not a symbol" a))
    (let
     ((*breakenable* t)
      (*ins-exit* 0)
      (*ins-name* (cons "symbol" a))
      (*print-level* *ins-plev*)
      (*print-length* *ins-plen*))
     (catch 'abort (ins-ins a)))
     `',a)

; inspect a function, with editable arguments

(defmacro insf (a)
    (let
     ((*breakenable* nil)
      (*ins-exit* 0) 
      (*ins-name* (cons "function" a)) 
      (*print-level* *ins-plev*)
      (*print-length* *ins-plen*))
     (catch 'abort
	    (if (and (fboundp a) (typep (symbol-function a) 'closure))
		(let ((x (ins-ins(get-lambda-expression(symbol-function a)))))
		     (case (first x)
			   (lambda `(defun ,a ,@(rest x)))
			   (macro  `(defmacro ,a ,@(rest x)))
			   (t (error "not a closure!"))))
		(error "can't repair")))))

; Inspect anything

(defun xlisp::inspect (a)
       (let
	((*breakenable* t)
	 (*ins-exit* 0)
	 (*ins-name* (cons (if (symbolp a) "symbol" "expression") a))
	 (*print-level* *ins-plev*)
	 (*print-length* *ins-plen*))
	(catch 'abort (ins-ins a)))
       a)


; Describe anything

(defun xlisp::describe (a)
       (let ((*print-level* *ins-plev*)
	     (*print-length* *ins-plen*))
	    (format t "~&~s is " a)
	    (if	(or (typep a 'struct)
		    (objectp a))
		(ins-display a)
		(case (type-of a)
		      (list (format t "NIL"))
		      (cons (if (list-length a)
				(format t "a list of length ~s" (length a))
				(format t "a circular list")))
		      (array (format t "an array of length ~s" (length a)))
		      (string (format t "a string of length ~s" (length a)))
		      (hash-table (format t "a hash table with ~s entries"
					  (hash-table-count a)))
		      (symbol
		       (ins-display a)
		       (if (symbol-package a)
			   (format t "Home package: ~a~%" (package-name (symbol-package a)))
			   (format t "No home package~%"))
		       (format t "Visible in: ")
		       (map nil
			    (lambda (x)
				    (multiple-value-bind
				     (val type)
				     (find-symbol (symbol-name a) x)
				     (when (and type (eq val a))
					   (format t "~a (~a)  "
						   (package-name x)
						   (case type
							 (:external "ext")
							 (:internal "int")
							 (t "inh"))))))
			    (list-all-packages))
		       (terpri))
		      (t (format t "a ~(~a~)" (type-of a)))))))

; ins-propp returns T if p is a property of a

(defun ins-propp (a p)
	(do 	((plist (symbol-plist a) (cddr plist)))
		((or (null plist) (eq (car plist) p))
		 (not (null plist)))))

; terminate input line

(defun ins-teread (error) 
       (fresh-line)
       (if (not (eq (peek-char) #\Newline))
	   (read-line))
       (if error
	   (format t "Try again:")
	   (format t "~a ~a>" (car *ins-name*) (cdr *ins-name*))))

(defmacro ins-protread () ;;Protected read -- we handle errors
	  '(do ((val (errset (read)) 
		     (progn (ins-teread t) (errset (read)))))
	       ((consp val) (car val))))

(defmacro ins-proteval () ;;protected eval -- we handle errors
			  ;; we also use evalhook so environment is global
			  ;;  plus a local @, which cannot be changed!
	'(do* ((env (cons (list (list (cons '@ list))) nil))
	       (val (errset (evalhook (read) nil nil env))
		    (progn (ins-teread t) 
			   (errset (evalhook (read) nil nil env)))))
	      ((consp val) (car val))))


; New methods so that we can inspect and repair messages.

(send Class :answer :messages '() '(messages))

; new methods so that we can inspect and repair instance variables

(send Class :answer :ivars '() '(ivars))

(send Class :answer :cvars '() '((map 'list #'cons cvars cvals)))

(send Class :answer :superclass '() '(superclass))

#+:packages (import '(xlisp::%struct-ref xlisp::%struct-set))

(defun ins-struct (struct name)  ; get structure element
       (%struct-ref struct
		    (1+ (position name
				  (get (type-of struct) '*struct-slots*)
				  :key #'first))))

(defun ins-set-struct (struct name value)  ; set structure element
       (%struct-set struct
		    (1+ (position name
				  (get (type-of struct) '*struct-slots*)
				  :key #'first))
		    value))

#+:packages (unintern 'xlisp::%struct-ref)
#+:packages (unintern 'xlisp::%struct-set)


(defun ins-ivar (obj name)
       (funcall #'send obj
#+:packages	(intern (string name) :keyword)
#-:packages      (intern (strcat ":" name))
		))

(defun ins-set-ivar (obj name value)
       (funcall #'send obj :set-ivar
#+:packages	(intern (string name) :keyword)
#-:packages     (intern (strcat ":" name))
		value))

; help function
(defun ins-help (list)
       (format t "~%Available commands:~2%")
       (format t "e n~8tselect element n~%")
       (format t "r n x~8treplaces element n with (quoted) x.~%")
       (format t "X~8texit, saving all changes~%")
       (format t "Q~8texit, without changes~%")
       (format t "b~8tgo back one level (as before A, D or e commands)~%")
       (format t "B n~8tgo back n levels.~%")
       (format t (if (symbolp list)
		     "l~8tshow symbol~%"
		     "l~8tdisplay selection using pprint~%"))
       (format t "v~8tverbosity toggle~%")
       (format t ". n~8tchange maximum print length (default 10)~%")
       (format t "# n~8tchange maximum print depth (default 3)~%")
       (format t "! x~8tevaluates x and prints result.~%~8tThe symbol tools:@ is bound to the selection~%")
       (format t "R x~8treplaces the selection with evaluated x.~%~8tThe symbol tools:@ is bound to the selection~%")
       (unless (typep list '(or cons array)) (return-from ins-help list))
       (format t "A~8tselect the CAR of the current selection.~%")
       (format t "D~8tselect the CDR of the current selection.~%")
       (format t "( n m~8tinserts parens from nth element for m elements.~%")
       (format t ") n~8tremoves parens around nth element of selection.~%")
       (format t "[ n m~8tas in [, but makes elements into an array.~%")
       (format t "i n x~8tinserts (quoted) x before nth element in selection.~%")
       (format t "d n~8tdeletes nth element in selection.~%")
       (unless (typep list 'cons) (return-from ins-help list))
       (format t "S x y~8tall occurances of (quoted) y are replaced with~%~8t(quoted) x.  EQUAL is used for the comparison.~%")
       list)


; Display current selection
(defun ins-display (list)
       (fresh-line)
       (cond ((typep list '(or cons array))
	      (let ((n 0))
		   (if (or (arrayp list) (list-length list))
		       (map nil #'(lambda (l)
					  (format t "~3@s ~s~%" n l)
					  (setq n (1+ n)))
			    list)
		       (format t "circular list ~s~%" list))))
	     ((typep list 'struct)
	      (format t "~a structure ~%" (type-of list))
	      (mapc #'(lambda (n)
			      (format t
				      "~10s~s~%"
				      (car n)
				      (ins-struct list (car n))))
		    (get (type-of list) '*struct-slots*)))
	     ((classp list)
	      (format t "Class ~s, messages:~%" (send list :pname))
	      (mapc #'(lambda (n) (format t " ~s" (car n)))
		    (send list :messages))
	      (terpri)
	      (when (send list :cvars)
		    (format t "~%cvars:~%")
		    (mapc #'(lambda (n) (format t " ~s ~s~%" (car n) (cdr n)))
			  (send list :cvars))))
	     ((objectp list)
	      (format t "A ~s, ivars:~%" (send (send list :class) :pname))
	      (mapc #'(lambda (n) (format t " ~s ~s~%" n (ins-ivar list n)))
		    (send (send list :class) :ivars))
	      (terpri))
	     ((symbolp list)
	      (format t "Symbol ~s:~%" (symbol-name list))
	      (when (fboundp list) (format t "Function binding~%"))
	      (when (boundp list)
		    (format t "Value binding~a: ~s~%"
			    (cond ((constantp list) " (constant)")
				  ((specialp list) "  (special)")
				  (t ""))
			    (symbol-value list)))
	      (when (symbol-plist list)
		    (format t "Properties:~%")
		    (do ((l (symbol-plist list) (cddr l)))
			((null l) nil)
			(format t " ~s ~s~%" (first l) (second l)))))
	     (t (pprint list)))
       list)


; Bad command
(defun ins-bad (list)
       (format t "~&What??~%")
       list)
	  
;; Expects number >=min and <max (if max non-nil)
;; returns valid number, or prints message and returns nil
(defun ins-number (min max err &aux (n (ins-protread)))
       (if (and (numberp n)
		(>= n min)
		(or (null max) (< n max)))
	   n
	   (if err (ins-bad nil) nil)))

; inspect and replace list/array/structure/object/symbol elements
(defun ins-list-spec (list)
       (princ " element #? ")
       (ins-number 0 (length list) nil))

(defun ins-struct-spec (list &aux name)
       (princ " element name? ")
       (when (assoc (setq name (ins-protread))
		    (get (type-of list) '*struct-slots*))
	     name))

(defun ins-class-spec (list &aux name)
       (princ " message/cvar name? ")
       (cond ((assoc (setq name (ins-protread))
		     (send list :messages))
	      (cons t name))
	     ((assoc name (send list :cvars))
	      (cons nil name))))
       
(defun ins-object-spec (list &aux name)
       (princ " ivar name? ")
       (when (member (setq name (ins-protread))
		    (send (send list :class) :ivars))
	     name))

(defun ins-symbol-spec (list &aux name)
       (if (and (boundp list) (not (fboundp list)) (not (symbol-plist list)))
	   :v
	   (if (and (not (boundp list)) (fboundp list)
		    (not (symbol-plist list)))
	       :f
	       (progn
		(princ " :f :v or propname?")
		(if (ins-propp list (setq name (ins-protread)))
		    name
		    (case name ((:f :v) name)))))))

(defun ins-enter (list &aux val)
       (cond ((typep list '(or cons array))
	      (if (setq val (ins-list-spec list))
		  (concatenate (type-of list)
			       (subseq list 0 val)
			       (list (ins-ins (elt list val)))
			       (subseq list (1+ val)))
		  (ins-bad list)))
	     ((typep list 'struct)
	      (if (setq val (ins-struct-spec list))
		  (progn (ins-set-struct list val
					 (ins-ins (ins-struct list val)))
			 list)
		  (ins-bad list)))
	     ((classp list)
	      (if (setq val (ins-class-spec list))
		  (if (car val)
		      (let ((closure (cdr (assoc (cdr val)
						 (send list :messages))))
			    closure2 result)
			   (unless (typep closure 'closure)
				   (ins-bad list)
				   (return-from ins-enter list))
			   (setq closure2 (get-lambda-expression closure))
			   (setq result (ins-ins closure2))
			   (setf (cdr (cddr closure2)) (cdddr result))
			   (setf (car (cddr closure2)) (caddr result))
			   list)
		      (progn (ins-set-ivar (send list :new)
					   (cdr val)
					   (ins-ins (ins-ivar (send list :new)
							      (cdr val))))
			     list))
		  (ins-bad list)))
	     ((objectp list)
	      (if (setq val (ins-object-spec list))
		  (progn
		   (ins-set-ivar list val (ins-ins (ins-ivar list val)))
		   list)
		  (ins-bad list)))
	     ((typep list 'closure)
	      (let* ((x (get-lambda-expression list))
		     (y (ins-ins x)))
		    (setf (cdr (cddr x)) (cdddr y))
		    (setf (car (cddr x)) (caddr y))
		    list))
	     ((symbolp list)
	      (if (setq val (ins-symbol-spec list))
		  (case val
			(:f
			 (if (and (fboundp list)
				  (typep (symbol-function list) 'closure))
			     (let* ((x
				     (get-lambda-expression
				      (symbol-function list)))
				    (y (ins-ins x)))
				   (setf (cdr (cddr x)) (cdddr y))
				   (setf (car (cddr x)) (caddr y))
				   list)
			     (ins-bad list)))
			(:v
			 (if (boundp list)
			     (let* ((*ins-name* (cons "symbol" list))
				    (result (ins-ins (symbol-value list))))
				   (if (constantp list)
				       list
				       (progn (set list result) list)))
			     (ins-bad list)))
			(t (setf (get list val)
				 (ins-ins (get list val)))
			   list))))))
			      
(defun ins-repwith (list)
       (format t "~&Replace with: ")
       (ins-protread))

(defun ins-replace (list &aux val)
       (cond ((typep list '(or cons array))
	      (if (setq val (ins-list-spec list))
		  (concatenate (type-of list)
			       (subseq list 0 val)
			       (list (ins-repwith list))
			       (subseq list (1+ val)))
		  (ins-bad list)))
	     ((typep list 'struct)
	      (if (setq val (ins-struct-spec list))
		  (progn (ins-set-struct list val
					 (ins-repwith list))
			 list)
		  (ins-bad list)))
	     ((classp list)	; gotta catch this error here
	      (ins-bad list))
	     ((objectp list)
	      (if (setq val (ins-object-spec list))
		  (progn
		   (ins-set-ivar list val (ins-repwith list))
		   list)
		  (ins-bad list)))
	     ((symbolp list)
	      (if (setq val (ins-symbol-spec list))
		  (case val
			(:f
			 (ins-bad list))
			(:v
			 (if (not (constantp list))
			     (progn (setf (symbol-value list)
					  (ins-repwith list))
				    list)
			     (ins-bad list)))
			(t (setf (get list val)
				 (ins-repwith list))
			   list))))))

; main list repair interface
(defun ins-ins (list) 
       (ins-display list)
       (prog (command n newlist)
	     y (ins-teread nil)
	     (setq command (int-char (get-key)))  ;; Works with most systems
	     (princ command)
	     (setq
	      newlist  ;; new list value, if any
	      (case
	       command
	       (#\? (ins-help list))
	       (#\v (if (setq *verbosity* (not *verbosity*))
			(ins-display list)
			list))
	       (#\X (setq *ins-exit* -1) list)
	       (#\Q (throw 'abort))
	       (#\b (format t "ack") (return list))
	       (#\B (format t "ack #? ")
		    (when (setq n (ins-number 1 nil t))
			  (setq *ins-exit* n))
		    list)
	       (#\l (if *verbosity*
			(if (symbolp list)
			    (ins-display list)
			    (progn (terpri) (pprint list)))
			(format t "~%~s~%" list))
		    list)
	       (#\! (format t " Eval:~%")
		    (print (ins-proteval))
		    list)
	       (#\R (format t "eplace w. evaled:~%")
		    (ins-proteval))
	       (#\# (format t " print-level? ")
		    (when (setq n (ins-number 1 nil t))
			  (format t "Was ~s\n" *print-level*)
			  (setq *print-level* n))
		    list)
	       (#\. (format t " print-length? ")
		    (when (setq n (ins-number 1 nil t))
			  (format t "Was ~s\n" *print-length*)
			  (setq *print-length* n))
		    list)
; cons only commands
	       (#\A (if (consp list)
			(cons (ins-ins (car list)) (cdr list))
			(ins-bad list)))
	       (#\D (if (consp list)
			(cons (car list) (ins-ins (cdr list)))
			(ins-bad list)))
; various special commands
	       (#\e (if (typep list '(or cons struct array object symbol closure))
			(ins-enter list)
			(ins-bad list)))
	       ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
		(setq n (- (char-int command) (char-int #\0)))
		(if (and (typep list '(or cons array))
			 (< n (length list)))
		    (concatenate (type-of list)
				 (subseq list 0 n)
				 (list (ins-ins (elt list n)))
				 (subseq list (1+ n)))
		    (ins-bad list)))
	       (#\r (if (typep list '(or cons struct array object symbol))
			(ins-replace list)
			(ins-bad list)))
	       (#.*RPAR* (if (and (typep list '(or cons array))
			      (princ " remove nesting at #? ")
			      (setq n (ins-number 0 (length list) nil))
			      (typep (elt list n) '(or cons array)))
			 (concatenate (type-of list)
				      (subseq list 0 n)
				      (elt list n)
				      (subseq list (1+ n)))
			 (ins-bad list)))
	       ((#.*LPAR* #\[)
		(if (and (typep list '(or cons array))
			 (princ " insert nesting starting at # and length? ")
			 (setq n (ins-number 0 nil nil))
			 (setq n2 (ins-number 1
					      (- (1+ (length list)) n)
					      nil)))
		    (concatenate (type-of list)
				 (subseq list 0 n)
				 (list (coerce (subseq list n (+ n n2))
					       (if (eq command *LPAR*)
						   'list
						   'array)))
				 (subseq list (+ n n2)))
		    (ins-bad list)))
	       (#\i (if (and (typep list '(or array cons))
			     (princ "nsert before # and value? ")
			     (setq n (ins-number 0 (1+ (length list)) nil)))
			(concatenate (type-of list)
				     (subseq list 0 n)
				     (list (ins-protread))
				     (subseq list n))
			(ins-bad list)))
	       (#\d (if (and (typep list '(or array cons))
			     (princ "elete #? ")
			     (setq n (ins-number 0 (length list) nil)))
			(concatenate (type-of list)
				     (subseq list 0 n)
				     (subseq list (1+ n)))
			(ins-bad list)))
	       (#\S (if (typep list 'cons)
			(progn
			 (princ "ubstitute expr with expr\n")
			 (subst (ins-protread) 
				(ins-protread) 
				list
				:test #'equal))
			(ins-bad list)))
		   (#\Newline list)
		   (t (ins-bad list))))
	     (when (not (eq list newlist))	    ;; show any changes
		   (setq list newlist)
		   (when (and (zerop *ins-exit*) *verbosity*)
			 (ins-display list)))
	     (when (zerop *ins-exit*) (go y))
	     (setq *ins-exit* (1- *ins-exit*)) ;; return a level
	     (return list)))


syntax highlighted by Code2HTML, v. 0.9.1