;;; **********************************************************************
;;; This code was written as part of the Spice Lisp project at
;;; Carnegie-Mellon University, and has been placed in the public domain.
;;; Spice Lisp is currently incomplete and under active development.
;;; If you want to use this code or any part of Spice Lisp, please contact
;;; Scott Fahlman (FAHLMAN@CMUC). 
;;; **********************************************************************
;;;
;;; Spice Lisp Function Stepper
;;;    these functions are part of the standard Spice Lisp environment.
;;;
;;; Written by Jim Large
;;;
;;; Maintained by Steven Handerson
;;;
;;; **********************************************************************
(proclaim '(special
	    *evalhook*			;function which subs for eval
	    Step-state		;On, off (aborted), or sleeping
	    step-indentation-level	;# of spaces to indent step output
	    ))

(defvar *step-print-level* 4
  "*Print-level* is bound to this when stepper prints forms.")

(defvar *step-print-length* 5
  "*Print-length* is bound to this when stepper prints forms.")

(defvar *max-step-indentation* 40
  "The maximum number of spaces that step output may be indented.")

(defvar *terminal-line-mode* nil
  "When nil, step will not require a terminating end-of-line for commands.")

(defvar step-state nil
  "Step's memory.  Nil means off, T means on. A list of functions is a
search list.  A non-symbol means step is asleep.")

(defvar step-indentation-level 0
  "Makes the step facilities prinout nicer.")

(eval-when (compile eval)
  (defmacro step-off-p ()
    '(null step-state))

  (defmacro step-on-p ()
    '(eq step-state t))

  (defmacro awaken-stepper ()
    '(setq step-state t
	   *evalhook* #'step-command-loop
	   step-indentation-level 0))

  (defmacro abort-stepper ()
    '(setq Step-state nil))

  (defmacro sleep-stepper (&optional (functions 0))
    `(setq step-state ,functions
	   *evalhook* #'step-command-loop
	   step-indentation-level 0))

  (defmacro step-search-list ()
    `(and (listp step-state) step-state))

  )




;;; Flushes whitespace.
(eval-when (compile load)
  (defmacro step-get-char-CR ()
    `(do ((char (read-char *query-io*) (read-char *query-io*)))
	 ((not (member char '(#\return #\tab #\space #\linefeed)))
	  char))))

#-perq
(defmacro step-get-char () '(step-get-char-CR))

#+perq
(defmacro step-get-char ()
  `(cond (*terminal-line-mode* (step-get-char-CR))
	 (t (finish-output *standard-output*)
	    (multiple-value-bind (ignore char) (get-event 2)
	      (declare (ignore ignore))
	      (terpri *standard-output*)
	      char))))

(defmacro step-step-form (form environment)
  `(let ((results
	  (multiple-value-list
	   (apply #'evalhook ,form #'step-command-loop () ,environment))))
     (step-print-values results)
     (values-list results)))

(defmacro step-eval-form (form environment)
  `(let ((results
	  (multiple-value-list
	   (apply #'*eval ,form ,environment))))
     (step-print-values results)
     (values-list results)))

;;; Step-Help
;;;
;;; Usage of return, linefeed differs from that of maclisp.

;;; Step help is called to print a help message on the console.
(defun step-help ()
  (princ "

Commands are single characters.  If you don't like (linmode nil), then
(setq *terminal-line-mode* t).

 N (next)    also space	    evaluate current expression in step mode.
 S (skip)    CR, tab           ''      ''        ''     without stepping.
 M (macro)		    steps a macroexpansion, signaled by a :: prompt.
 Q (quit)    linefeed       finish evaluation, but turn stepper off.
 P (print)		    print current exp
			     (ignore *step-print-level* & *step-print-length*.)
 ↑P			    pretty-print current exp
 B (break)		    enter break loop.
 E (eval)		    evaluate an arbitrary expression,
			      in the current environment.
 ? (help)		    print this text.
 R (return)		    prompt for an arbitrary value to return
			      as result of current exp.
 G			    throw to top level.

" *standard-output*))
;;; Step-Print

;;; Step-print is called to print a form according to the current indentation
;;;  level, and according to *step-print-level* and *step-print-length*.

(defun step-print (form)
  (do ((*print-level* *step-print-level*)
       (*print-length* *step-print-length*)
       (i (min step-indentation-level *max-step-indentation*)
	  (1- i)))
      ((zerop i) (prin1 form *standard-output*))
    (princ " " *standard-output*)))


;;; Step-print-values is called to print a list of values which were returned
;;;  from an evaluation.

(defun step-print-values (value-list)
  (fresh-line *standard-output*)		;In case of prints.
  (if (not (null value-list)) (step-print (car value-list)))
  (do ((*print-level* *step-print-level*)
       (*print-length* *step-print-length*)
       (vlist (cdr value-list) (cdr vlist)))
      ((null vlist) (terpri *standard-output*))
    (princ "  " *standard-output*)
    (prin1 (car vlist) *standard-output*)))





;;; Step-Command-Loop
;;;
;;; Step-command-loop is a substitute for *eval.  It prints the form, and
;;;  then enters a command loop.  The commands are read as single characters
;;;  from the terminal.  If the stepper has subsequently been turned off,
;;;  do the equivalent of the s command without printing.

(defun step-command-loop (form &rest environment)
  (cond
   ;; If aborted, just eval it.
   ((step-off-p) (apply #'*eval form environment))
   ((or (step-on-p)
	(And (listp form)
	     (member (car form) (step-search-list))))
    ;;Otherwise, bind indent level, print form, and enter command loop.
    (let ((step-indentation-level (1+ step-indentation-level)))

      (cond ((or (symbolp form)
		 (constantp form))	  ;Could be quoted.
	     (step-print form)
	     (princ " = " *standard-output*)
	     (prog1
	      (prin1 (apply #'*eval form environment) *standard-output*)
	      (terpri *standard-output*)))
	    (t (prog ()
		TOP
		 (step-print form)
		NO-PRINT
		 (if (and (listp form)
			  (macrop (car form)))
		     (princ " :: " *standard-output*)	;Notify the user for expansion.
		     (princ " : " *standard-output*))
		NO-PROMPT
		 (case (step-get-char)
		   ((#\m #\M)
		    (return (step-step-form form environment)))
		   ((#\n #\N #\space)
		    (cond ((and (listp form)
				(macrop (car form)))
			   (setq form
				 (macroexpand form environment))
			   (go TOP))
			  (t (return
			      (step-step-form form environment)))))
		   ((#\s #\S #\return #\tab)
		    (return (step-eval-form form environment)))
		   ((#\q #\Q #\linefeed) (abort-stepper)
			      (return (apply #'*eval form
					     environment)))
		   ((#\p) (prin1 form *standard-output*) (go NO-PRINT))
		   ((#\P) (pprint form *standard-output*) (go NO-PRINT))
		   ((#\b #\B) (break "Step") (terpri *standard-output*))
		   ((#\e #\E) (princ "eval: " *query-io*)
			      (let ((*evalhook* ()))
				(prin1 (apply #'*eval (read *query-io*)
					      environment)
				       *standard-output*)
				(terpri *standard-output*)))
		   ((#\?) (step-help))
		   ((#\r #\R)
		    (princ "return: " *standard-output*)
		    (let* ((*evalhook* ())
			   (results (multiple-value-list
				     (apply #'*eval (read) environment))))
		      (step-print-values results)
		      (return (values-list results))))
		   ((#\g #\G) (throw 'top-level-catcher ()))
		   (T (princ "Type ? for help. " *standard-output*)
		      (clear-input *standard-output*)
		      (go NO-PROMPT)))
		 (go TOP))))))

   ;; Haven't found one yet.
   (t (apply #'evalhook form #'step-command-loop () environment))))



;;; Step

;;; Nice to know, but not an error.
(defun step-parse-functions (list)
  "Picks out functions from the list, and tells the user about those that 
weren't."
  (do* ((list list (Cdr list))
	(functions ())
	(non-functions ()))
       ((null list)
	(if non-functions
	    (format *error-output* "Non-functions ignored - ~S" non-functions))
	functions)
    (if (and (symbolp (car list))
	     (fboundp (Car list)))
	(setq functions (cons (car list) functions))
	(setq non-functions (cons (car list) non-functions)))))


;;; (Step form) is a special form which  "invokes the stepper".
;;; If no form, set up the stepper for being turned on inside a break loop.
;;; If form is T, turn the stepper on,
;;;           (),  ''  ''     ''   off,
;;; (step &rest functions) looks for any of the functions and steps on them.
;;;

(Defun step fexpr (forms)
  "With arg T or (), turns stepper on or off.  (Step) at top-level lets
a (step t) in a break loop turn on stepping globally.  With a list of 
functions, turns on stepping when any are called.  Otherwise, the arg
is evaled with stepper bound on."
  (cond
   ((null forms) (sleep-stepper) T)
   ((equal forms '(nil)) (abort-stepper) nil)
   ((equal forms '(t)) (awaken-stepper) T)
   ((symbolp (car forms))		;Check if function
    (let ((functions (step-parse-functions forms)))
      (sleep-stepper functions)
      functions))
   (T (let ((*evalhook* #'step-command-loop)
	    (step-state t)
	    (step-indentation-level 0))
	(eval (car forms))))))