; .EnTete "Le-Lisp (c) version 15.2" " " "Pisteur et pas a` pas"
; .EnPied "trace.ll" "K-%" " "
; .Annexe K "Le Pisteur et le Mode Pas-a`-pas"
; .nr % 1
;
; .Centre "*****************************************************************"
; .Centre " Ce fichier est en lecture seule hors du projet ALE de l'INRIA.  "
; .Centre " Il est maintenu par ILOG SA, 2 Avenue Gallie'ni, 94250 Gentilly "
; .Centre " (c) Le-Lisp est une marque de'pose'e de l'INRIA                 "
; .Centre "*****************************************************************"

; .Centre "$Header: trace.ll,v 4.11 88/11/23 09:53:27 nuyens Exp $"

(unless (>= (version) 15.2)
        (error 'load 'erricf 'trace))

; Tous les symboles pre'ce'de's de : seront cre'e's dans le package TRACE.

(defvar #:sys-package:colon 'trace)

(add-feature 'debug)

; .Section "Les variables globales"

(defvar #:system:debug ())
(defvar *trace-input* ())       ; le canal ou lire les commandes
(defvar *trace-output* ())	; le canal de sortie des messages du traceur.
(defvar #:step:auto-step ())    ; pour stepper automatiquement.

(defmacro :output body
  `(with ((outchan *trace-output*))
	 ,@body)))

; la liste des fonctions trace'es
(defvar :trace ())

; la liste des dernie`res fonctions de'trace'es
(defvar :last-untraced ())

; la liste des fonctions intracables
(defvar :untracable '(cstep step unstep quote arg =))

; .Section "Les fonctions auxiliaires"
 
(de :flat (l)
    (let ((r)) (:flat-aux l) (reverse r)))
 
(de :flat-aux (l)
    (cond ((null l) ())
          ((atom l) (newl r l))
          (t (:flat-aux (car l)) (:flat-aux (cdr l)))))
 
; .Section "Les fonctions de tracage"

(defvar tracewindow ())

(df trace l
    ; trace la liste de fonctions "l"
    (mapc ':trace-one l)
    (when (and :trace (featurep 'window) (null tracewindow))
          (setq tracewindow
                (create-window '#:window:tty 
                       (div (bitxmax) 2) (mul (height-space) 3)
                       (div (bitxmax) 2) (bitymax) "Le←Lisp : Trace" 0 1)))
    l)

(df untrace l
    ; enle`ve la trace de toutes les fonctions
    ; de la liste 'l' ou de toutes les fonctions
    ; trace'es si 'l' = ()
    (mapc ':untrace-one (setq :last-untraced (or l (setq l :trace))))
    (when (and (null :trace) (featurep 'window) tracewindow)
          (kill-window tracewindow)
          (setq tracewindow ()))
    l)

; Pour l'avenir...
;(de retrace ()
;    ;; retrace les dernie`res fonctions untrace'es
;    (prog1
;	(mapc ':trace-one :last-untraced)
;        (setq :last-untraced nil)))

(de :untrace-one (f)
    (let ((val (getprop f 'trace)))
         (if (atom val)
             (printerror 'untrace 'ERRNTF f)
	   (cond
	    ((getprop f 'resetfn&trace)     ; Il y avait un resetfn avant
	     (resetfn f (car val)(cdr val)) ;  la trace
	     (remprop f 'resetfn&trace))
	    ((getprop f 'resetfn)       ; C'est la trace qui a mis le
	     (remprop f 'resetfn)       ;  resetfn
	     (setfn f (car val) (cdr val)))
	    (t                          ; Sinon, cas simple
	     (setfn f (car val) (cdr val))))
	   (remprop f 'trace)
	   (setq :trace (delq f :trace)))))

; Un spe'cification de trace (argument de trace-one) a le format suivant
;   trace ::= fct
;          |  (fct [trace-spec]*)
;          |  ((fct*) [trace-spec]*)  ;  pas encore
; trace-spec ::= (wherein fct) | (wherein (fct*)) ;  pas encore
;             |  (entry expr*)
;             |  (exit  expr*)
;             |  (when  expr)   ; trace conditionnelle
;             |  (break expr)   ; break conditionnel
;             |  (step expr)    ; pas a` pas conditionnel
; 
; La spe'cif par defaut (correspondant a` (trace foo)) est
;    (foo (entry (prin 'foo "--->") (print-parameters 'foo))
;         (exit  (prin 'foo "<---") (print #:trace:value))
;         (when t)
;         (break ())
;         (step ())
;    )

(defun default-specif (item)
  (selectq item
	   (entry
	    `((:output
	       (print
		',:fct " ---> "
		,@(if (index "subr" :ftype)
		      ; pas de ` a cause des mapcan!
		      (mapcan (lambda (u) (list u " ")) (:flat :larg))
		    (mapcan (lambda (u) (list (kwote u) "=" u " "))
			    (:flat :larg)))))))
	   (exit
	    `((:output (print ',:fct " <--- " :value))))
	   (when
	    '(t))			; pas de when (when t)
	   (break
	    ())				; pas de break
	   (step
	    ())				; pas de step
	   ))          

(defun parse-specif (specif)
    (mapcar (lambda (item) (or (assq item specif)
                               (cons item (default-specif item))))
            '(entry exit when break step)))

(defvar :not-in-trace-flag t)
(defvar :step-in-trace-flag ())

; Les fonctions utilise'es par la trace interne

(synonymq :if if)
(synonymq :let let)
(synonymq :when when)
(synonymq :with with)
(synonymq :progn progn)
(synonymq :itsoft itsoft)
(synonymq :and and)
(synonymq :or or)
(synonymq :call call)
(synonymq :calln calln)
(synonymq :boundp boundp)
(synonymq :eval eval)
(synonymq :traceval traceval)
(synonymq :eprogn eprogn)

(defun build-tracing-fval (specif)
  `(,:larg
    (unstep
     (:if :not-in-trace-flag
	  (:let ((:not-in-trace-flag ()))
		(setq :step-in-trace-flag ())
		(:when ,(car (cassq 'when specif))
		       ,(if (featurep 'window)
			    `(:with (,(list 'current-window 'tracewindow))
				    ,@(cassq 'entry specif))
                          `(:progn ,@(cassq 'entry specif)))
		       (:when ,(car (cassq 'break specif))
			      (let ((:not-in-trace-flag t) 
				    (#:system:debug t))
				(:itsoft 'syserror
					 '(,:fct break tracebreak)))))
		(:let ((:value 
			(:if (:and ,(car (cassq 'when specif))
				   (:or :step-in-trace-flag
					,(car (cassq 'step specif))))
			     (:let ((:not-in-trace-flag t))
				   (step ,:call))
			     (:let ((:not-in-trace-flag t))
				   (cstep ,:call)))))
		      (:when ,(car (cassq 'when specif))
			     ,(if (featurep 'window)
				  `(:with (,(list 'current-window
						  'tracewindow))
					  ,@(cassq 'exit specif))
				`(:progn
				  ,@(cassq 'exit specif))))
		      :value))
	  (cstep ,:call)))))

(defvar :fct)                   ; la fonction tracee
(defvar :ftype)                 ; son ftype
(defvar :larg)                  ; ses arguments, (pseudos pour subr)
(defvar :call)                  ; la forme pour lancer la fct

(de :trace-one (:fct)
    (let ((specif)
	  (props))	; les indicateurs a positionner apres le setfn
      (when (consp :fct)
	    (setq specif (cdr :fct) :fct (car :fct)))
      (when (memq :fct :untracable)
	    (error 'trace 'errcnt :fct))
      (when (and (consp (valfn :fct))
		 (eq '&nobind (car (valfn :fct))))
	    (error 'trace 'errtnb :fct))
      (when (memq :fct :trace) (:untrace-one :fct))
      ; liaison des spe'ciales
      (let ((:ftype (or (car (getprop :fct 'resetfn)) (typefn :fct)))
	    :larg :call
	    (fval (or (cdr (getprop :fct 'resetfn)) (valfn :fct))))
	(when (getprop :fct 'resetfn)
	      ;; on n'a plus besoin d'enlever l'indicateur resetfn, parce
	      ;; que setfn le fait.
	      (newl props '(t resetfn&trace)))
	(selectq :ftype
		 ((expr fexpr macro dmacro)
		  (setq :larg  (car fval)
			:call `(:progn ,@(cdr fval))))
		 (subr0
		  (setq :larg  ()
			:call `(:call ',fval () () ())))
		 ((subr1)
		  (setq :larg '(:arg1)
			:call `(:call ',fval :arg1 () ())))
		 (subr2
		  (setq :larg '(:arg1 :arg2)
			:call `(:call ',fval :arg1 :arg2 ())))
		 (subr3
		  (setq :larg '(:arg1 :arg2 :arg3)
			:call `(:call ',fval :arg1 :arg2 :arg3)))
		 ((fsubr msubr dmsubr)
		  (setq :larg ':arg1
			:call `(:call ',fval :arg1 () ())))
		 (nsubr
		  (setq :larg ':arg1
                                :call `(:calln ',fval :arg1))))
	(cond ((null :ftype)
	       (printerror 'trace 'ERRUNK :fct))  
	      ((not (memq :ftype '(expr fexpr macro dmacro
					msubr dmsubr subr0 subr1 subr2
					subr3 fsubr nsubr)))
	       (printerror 'trace 'ERRKNT (list :fct :ftype)))
	      (t
	       (newl props (list (cons :ftype fval) 'trace))
	       (resetfn :fct
			(or (car (memq :ftype '(expr fexpr macro dmacro)))
			    (cassq :ftype
				   '((subr1 . expr) (subr2 . expr)
				     (subr3 . expr) (subr0 . expr)
				     (fsubr . fexpr) (nsubr . expr)
				     (msubr . macro) (dmsubr . dmacro))))
			(build-tracing-fval (parse-specif specif)))
	       ;; maintenant on peut positionner les indicateurs
	       (newl :trace :fct)
	       (mapc (lambda ((value prop))
		       (putprop :fct value prop))
		     props))))))


(de tracend ()
    (untrace)
    (mapc 'remob (oblist 'trace))
    (libautoload trace trace untrace)
    'tracend)

; .Section "Le mode pas-a`-pas"

; Le STEPPER : permet d'exe'cuter une expression en PAS A PAS
;  ex : (step (fib 10)).
; Le signal d'invite du pas a` pas est   "step>"


(setq #:sys-package:colon 'step)

; TEMPORAIRE: le temps de pouvoir les declarer &DYNAMIC.

(defvar :depth 0)
(defvar :value ())
(defvar :speak t)
(defvar :history ())
(defvar :exp)
(makunbound ':speak)

(defvar :ERRNBD
  #- #:system:foreign-language "je ne peux pas suivre une fonction &NOBIND"
  #+ #:system:foreign-language "I can't step an &NOBIND function")

(df step (:exp)
  (#:trace:let ((#:sys-package:itsoft
         (cons '#.#:sys-package:colon #:sys-package:itsoft))
        (:depth 0)                         ; la profondeur
        (:value)                           ; la valeur retourne'e
        (:speak t)                         ; la trace parle.
        (:history))                        ; l'histoire
        (tag step
	     (#:trace:traceval
	         `(progn ,:exp)); avec PROGN car sinon le toplevel STEP anihile
	     )))                ; plusieurs cas -&nobind; unstep-.

(de :nobind? (:form)
   (and (consp :form)
     (cond
      ((symbolp (car :form))
       (cond
	((and (consp (valfn (car :form)))
	      (eq '&nobind (car (valfn (car :form))))))
	((and (eq (car :form) 'send)
	      (eq (typefn 'send) 'nsubr))) ))
      ((and (consp (car :form))
	    (eq 'lambda (caar :form))
	    (eq '&nobind (cadar :form)))))
    ))
  
(de :stepeval (:forme :env)
  ; lance' par IT SOFT    
  (let ((:depth (1+ :depth)) 
        (:history (cons :forme :history)))
       (cond ((and (consp :forme)
                   (eq (car :forme) 'unstep))
              (eval :forme :env))
             ((null :speak) (stepeval :forme :env))
             (t (:steploop #/.)))))

(df unstep :exp
    (#:trace:eprogn :exp))

; to compile traced functions with complice
(putprop 'unstep 'parano '#:complice:fvar)

(df cstep (:exp)
    (#:trace:if (#:trace:and (#:trace:boundp ':speak) :speak)
        (#:trace:traceval :exp)
        (#:trace:eval :exp)))

(de :stepmargin (:n)
    (repeat (if (< :n 20) :n 21) (princn #\sp)))

(de :steploop (:cmd)
    (when :auto-step (setq :cmd t))
    (selectq :cmd
        (#/. 
	 (#:trace:output
	  (with ((printlevel 3) (printline 1))
		(:stepmargin :depth)
		(prinflush :depth " -> " :forme " step>")))
	 (let ((response
		(with ((inchan *trace-input*))
		      (teread)
		      (car (readline)))))
	   (:steploop response)))
	(#/=
	 (let ((#:system:debug t))
	   (itsoft 'syserror (list 'step 'break :forme)))
	 (:steploop #/.))
	(#/< (setq :value (let ((:speak ()))
			    (eval :forme :env)))
	     (#:trace:output
	      (:stepmargin :depth) 
	      (print :depth " <- " :value)))
	(#/q (exit #:system:toplevel-tag))
	(#/h (let ((:n 0) 
		   (:history (reverse :history)))
	       (#:trace:output
		(while :history
		  (:stepmargin :n)
		  (print (incr :n) " " (nextl :history)))))
	     (:steploop #/.))
	(#/?
	 (#:trace:output
	  #- #:system:foreign-language
	  (progn
	    (print ";Les commandes de pas a pas sont : ")
	    (print ";   CR  pour passer a l'expression suivante")
	    (print ";   .   pour voir l'expression courante")
	    (print ";   <   pour evaluer sans pas a pas et y revenir")
	    (print ";   q   retour au toplevel")
	    (print ";   h   pour avoir l'historique du pas a pas")
	    (print ";   ?   pour avoir ce texte ...."))
	  #+ #:system:foreign-language
	  (progn
	    (print ";Commands for step are : ")
	    (print ";   CR  go to next expression")
	    (print ";   .   see current expression")
	    (print ";   <   evaluate without step and come back")
	    (print ";   q   return to toplevel")
	    (print ";   h   view history")
	    (print ";   ?   this message ..."))
	  )
	 (:steploop #/.))
	(t 
	 (#:trace:output
	       (setq :value 
		     (if (not (:nobind? :forme)) ; On exclue le cas des &nobind
			 (traceval :forme :env)
		       (:stepmargin :depth)
		       (printerror 'step
				   :ERRNBD
				   :forme)
		       (eval :forme :env)))
	       (:stepmargin :depth) 
	       (print :depth " <- " :value)))))

#|
	 (#:trace:output
	  (setq :value 
		(if (not (:nobind? :forme)) ; On exclue le cas des &nobind
		    (traceval :forme :env)
		  (:stepmargin :depth)
		  (printerror 'step :ERRNBD :forme)
		  (eval :forme :env)))
	  (:stepmargin :depth) 
	  (print :depth " <- " :value)))))
|#
(de stepend ()
    (mapc 'remob (oblist 'step))
    (libautoload trace step unstep cstep)
    'stepend)

; .Section "Re'cupe'ration de l'espace"

(de debugend ()
    ; re'cupe`re la place des fonctions de mise au point
    (tracend)
    (stepend)
    (rem-feature 'debug)
    'debugend)