
; trace of a procedure

; list of traced  procedures : global variable, empty at first
(define *traced-proc-list* '(()))
; value of indent for trace : global variable, null at first
(define *trace-indent* 0)

;***********************************************************************
; UNTRACE of an object
;***********************************************************************
(define (delete! proc)
  ; "proc" is in *traced-proc-list*
  (let ((before *traced-proc-list*)
	(now (cdr *traced-proc-list*)))
    (while (not (eq? proc (caar now)))
	   (set! before now)
	   (set! now (cdr now)))
    ; "now" <==> proc, ==> remove it
    (set-cdr! before (cdr now))))

(define-macro (untrace-one obj)
  `(let ((already (assoc ',obj *traced-proc-list*)))
     (cond ((pair? already)  ; the name of this procedure is in *traced-proc-list*
	    (delete! ',obj)  ; remove it in *traced-proc-list*
	    (if (eq? (cdr already) 'var) ; the obj traced is a variable
		(untrace-var ',obj)
		; else the obj traced is a procedure
		(if (and (procedure? ,obj)
			 (eq? (cadr (procedure-body ,obj))
			      '**arguments-de-trace-mf**))
		    ; obj was a procedure already traced ==> restore its body
		    (set! ,obj (cdr already)))))
	   (else   ; "obj" is not "traced on" ==> message without error
	    (format #t "~S is not traced on~%" ',obj)))))

(define-macro (untrace . args)
  `(if (not (null? ',args))
       (begin
	 ,@(map (lambda (x)
		  `(untrace-one ,x))
		args))
       (error "untrace: too few arguments")))

;***********************************************************************
; UNTRACE-ALL <==> UNTRACE ALL the objects traced
; ==> restore the bodies of all traced procedures
;***********************************************************************

(define-macro (untrace-all)
  `(if (not (null? (cdr *traced-proc-list*)))
      (begin 
	 ,@(map (lambda (x)
		  `(untrace-one ,(car x)))
		(cdr *traced-proc-list*)))))

;***********************************************************************
; TRACE
;***********************************************************************

(define (indent x)            ; displays "x" periods on current output-port
  (format #t "~A" (make-string x #\.)))

(define (display-arguments form-list act-list)
  ; "form-list" contains formal parameters
  ; "act-list" contains actual parameters
  (cond ((and (null? form-list) (null? act-list))
	 ; two empty lists ==> nothing to do, go to new line
	 (newline))
	((not (list? form-list)) ; x or improper list (x a b c . y)
	 (if (not (pair? form-list)) ; x only
	     (format #t "~S=~S~%" form-list act-list)
	     ; improper list :
	     (if (not (null? act-list))
		 (begin   
		   (format #t "~S=~S "    ; display x
			   (car form-list) (car act-list))
		   (if (not (list? (cdr form-list))) 
		       ; form-list = (a b c . y)
		       (display-arguments (cdr form-list)
					  (cdr act-list))
		       ; else form-list was (x . y)
		       (format #t "~S=~S~%"    ; display y
			       (cdr form-list) (cdr act-list))))
		 ; else, form-list = (x . y) and act-list = () ==> error
		 (begin (newline)
			(set! *trace-indent* 0) 
			(error "Too few actual parameters")))))
	((null? form-list)  ; error
	 (newline)
	 (set! *trace-indent* 0) 	
	 (error "Too many actual parameters"))
	((null? act-list) ; error
	 (newline)
	 (set! *trace-indent* 0) 	
	 (error "Too few actual parameters"))
	(else ; form-list and act-list are "proper lists" and not empty
	 (format #t "~S=~S " (car form-list) (car act-list))
	 (display-arguments (cdr form-list) (cdr act-list)))))


(define-macro (trace-one obj)
  `(let ((last-proc ,obj) ; body of procedure to trace
	 (res '())         ; result of procedure to trace
	 (already (assoc ',obj *traced-proc-list*)))
     (cond ((primitive? ,obj) ; on ne peut pas.....
	    (error "the primitive ~S can't be traced~%" ',obj))
	   ((not (procedure? ,obj))
	    ; obj is a variable
	    (if (pair? already) 
		; name of the obj is already in *traced-proc-list*
		(if (not (eq? (cdr already) 'var))
		    ; this variable is already traced on but as a procedure
		    (begin
		      (untrace-one ,obj)  ; remove the last trace
		      (trace-one ,obj))  ; trace the new variable
		    ; else, this variable is already traced on as a variable
		    ; ==> display a message, without error
		    (format #t "~S already traced on~%" ',obj))
		; else it's the first trace on this variable
		(begin     ; ==> put it in *traced-proc-list*
		  (set! *traced-proc-list*
			(cons '()
			      (cons (cons ',obj 'var)
				    (cdr *traced-proc-list*))))
		  (trace-var ',obj
			     '(format #t "~S ==> ~S~%" ',obj ,obj)))))
	   ; obj is a procedure, not a primitive
	   ((pair? already)   ; name of obj is already in *traced-proc-list*
	    (if (eq? (cadr (procedure-body ,obj))
		     '**arguments-de-trace-mf**)
		; this obj is already traced on as a procedure
		; ==> display a message, without error
		(format #t "~S already traced on~%" ',obj)
		; this procedure has the same name of an obj
		; already traced on ==> perhaps a new definition... ==>
		(begin
		  (untrace-one ,obj)  ; remove the last
		  (trace-one ,obj))))  ; trace the new
	   (else ; this procedure is not already traced on
	    ; ==> put it in *traced-proc-list*
	    (set! *traced-proc-list*
		  (cons '()
			(cons (cons ',obj last-proc)
			      (cdr *traced-proc-list*))))
	    (set! ,obj
		  (lambda **arguments-de-trace-mf**
		    (dynamic-wind 

		     (lambda ()
		        ; indent more
		       (set! *trace-indent* (+ *trace-indent* 2))
		       (indent *trace-indent*)
		       ; display entering in procedure (its name)
		       (format #t "Entering ~S " ',obj)
		       ; display formal and actual parameters
		       (display-arguments (cadr (procedure-body last-proc))
					  **arguments-de-trace-mf**))
		     (lambda ()
		       ; eval the result of the procedure
		       (set! res 
			     (apply last-proc **arguments-de-trace-mf**))
		       ; exiting of procedure
		       (indent *trace-indent*)
		       (format #t "Exiting ~S result = ~S~%" ',obj res))
		     (lambda ()
		       (set! *trace-indent* (- *trace-indent* 2)))) ; indent less
		    res))))))

(define-macro (trace . args)
  `(if (not (null? ',args))
       (begin
	 ,@(map (lambda (x)
		  `(trace-one ,x))
		args))
       (error "trace: too few arguments")))


