[comp.lang.scheme] trace in xscheme

grunwald@foobar.colorado.edu (Dirk Grunwald) (10/27/90)

I'm using xscheme-0.22 for a class in programming languages.  There's
no trace facility, so I wrote one. I'm wondering if anyone has written
any other debugging facilities; my students are having a tough time
with debugging xscheme programs.

In case others find it useful, here's the trace package I wrote:

;;
;; Tracing package (of a sorts) by Dirk Grunwald, Oct. 1990
;;
;; (trace list of function names) will trace the execution of those
;; functions.
;;
;; Redefining a function will cause it to loose its tracing information.
;; You can say (trace ...) again to resume tracing of it.
;;
;; (untrace list of function names) will disable tracing for those functions.
;; (untrace) will disable tracing on all functions.
;;
;; Untracing a function will restore the function as it was when you
;; traced it.  Thus, you should be leary of untracing things. You should
;; probably just untrace everything, reload your file and then continue.
;;
;; E.g.,, if you execute:
;;
;;		(define (foo a b) (+ a b))
;;		(trace foo)
;;		(define (foo a b) (- a b))
;;		(untrace foo)
;;		(foo 20 10)
;;
;; The output will be ``30'' not ``10'' as you might expect.
;;

(set! *trace-alist* nil)

(define (*trace-mkassoc* x y alist)
  (if (null? alist)
      (list (list x y))
      (if (eqv? x (caar alist))
	  ;; then
	  (cons (list x y) (cdr alist))
	  ;; else
	  (cons (car alist) (*trace-mkassoc* x y (cdr alist)))
	  )
      )
  )

(define (*trace-delassoc* x alist)
  (if (null? alist)
      nil
      (if (eqv? x (caar alist))
	  ;; then
	  (cdr alist)
	  ;; else
	  (cons (car alist) (*trace-delassoc* x (cdr alist)))
	  )
      )
  )

(set! *trace-levels* 0)

(define (*trace-space*)
  (let
      ((i *trace-levels*))
    (while (> i 0)
	   (begin
	     (display " ")
	     (set! i (-1+ i))
	     )
	   )
    )
  )


(define (trace-handler form)
  (let* (
	 (fcn (cadr form))
	 (fcn-assoc (assoc fcn *trace-alist*))
	 )
    (if (not (bound? fcn))
	(begin
	  (display "Can't find function ") (display fcn) (newline)
	  nil
	  )
	(begin
	  (set! *trace-alist*
		(*trace-mkassoc* fcn (symbol-value fcn) *trace-alist*))
	  (%expand-macros
	   `(set! ,fcn
		  (lambda args 
		    (let ((*value* nil))
		      (begin
			(set! *trace-levels* (+ *trace-levels* 1))
			(*trace-space*)
			(display "Entering ") (display ',fcn)
			(display ": w/args ") (display args) (newline)
			(set! *value* (apply ,(symbol-value fcn) args))
			(*trace-space*)
			(display "Exiting ") (display ',fcn)
			(display " w/value ") (display *value*) (newline)
			(set! *trace-levels* (- *trace-levels* 1))
			*value*
			)
		      )
		    )
		  )
	   )
	  )
	)
    )
  )

(define (trace-pre-handler form)
  (let
      ((return-value nil))
    (cond
     ((= (length form) 1) (begin
			    (display "Trace all") (newline)
			    (set! return-value
				  (cons 'begin
					(map (lambda (x)
					       (trace-handler
						(list 'trace  x)))
					     (map car *trace-alist*))
					)
				  
				  )
			    )
			  )
     (else  (set! return-value (cons
			  'begin
			  (map (lambda (x) (trace-handler (list 'trace  x)))
			       (cdr form)
			       )
			  )
		  )
	    )
     )
    (display "Now tracing: ")
    (display (map car *trace-alist*))
    (newline)
    return-value
    )
  )

(compiler-syntax trace trace-pre-handler)

(define (untrace-handler form)
  (let* (
	 (fcn (cadr form))
	 (fcn-assoc (assoc fcn *trace-alist*))
	 )
    (if (not fcn-assoc)
	(begin
	  (display "Not tracing ") (display fcn) (newline)
	  nil)
	(begin
	  (set! *trace-alist* (*trace-delassoc* fcn *trace-alist*))
	  (display "Untracing ") (display fcn) (newline)
	  `(set! ,fcn ,(cadr fcn-assoc))
	  )
	)
    )
  )

(define (untrace-pre-handler form)
  (let
      ((return-value nil))
    (cond
     ((= (length form) 1) (begin
			    (display "Untrace all") (newline)
			    (set! return-value
				  (cons 'begin
					(map (lambda (x)
					       (untrace-handler
						(list 'untrace  x)))
					     (map car *trace-alist*))
					)
				  )
			    )
			  )

     (else (set! return-value
		 (cons 'begin
		       (map (lambda (x) (untrace-handler (list 'untrace  x)))
			    (cdr form)))
		 )
	   )
     )
    return-value
    )
  )

(compiler-syntax untrace untrace-pre-handler)

(macro top-level
  (lambda ()
    (begin
      (set! *trace-level* 0)
      (reset)
      )
    )
  )

ken-w@is.s.u-tokyo.ac.jp (WAKITA Ken) (10/30/90)

In article <28787@boulder.Colorado.EDU> grunwald@foobar.colorado.edu (Dirk Grunwald) writes:

 > From: grunwald@foobar.colorado.edu (Dirk Grunwald)
 > Newsgroups: comp.lang.scheme
 > Date: 27 Oct 90 12:56:05 JST
 > Sender: news@boulder.Colorado.EDU
 > Reply-To: grunwald@foobar.colorado.edu
 > Distribution: comp
 > Organization: University of Colorado at Boulder
 > Lines: 205
 > 
 > 
 > I'm using xscheme-0.22 for a class in programming languages.  There's
 > no trace facility, so I wrote one. I'm wondering if anyone has written
 > any other debugging facilities; my students are having a tough time
 > with debugging xscheme programs.

     ELK does not provide trace facility.  So, I've also written
similar, shorter, but a bit dangerous program.  Though it contains
several known bugs (it can't trace macros and primitive procedures) it
is useful.  I also have written a toplevel eval-read-print loop to
support transcript-on/off facility.  An interested reader can request
via E-mail to:

        ken-w@is.s.u-tokyo.ac.jp

----------------------------------------------------------------------
(define trace)
(define untrace)

(let ()
  (define trace-list '(()))

  (define (reset-trace) (set! trace-list '(())))

  (define-macro (the-trace func)
    `(let ((the-func (eval ,func))
	   (result #v))
       (if (assoc ',func ,trace-list)
	   (error 'trace "~s already trace on." ,func))
       (if (not (compound? ,func))
	   (error 'trace "wrong argument type ~s (expected compound)"
		  (type ,func)))
       (set! ,trace-list
	     (cons ()
		   (cons (cons ',func the-func)
			 (cdr ,trace-list))))
       (set! ,func
	     (lambda param-list
	       (format #t "# Entering ~s~%"
		       (cons ',func param-list))
	       (set! result (apply the-func param-list))
	       (format #t "# Exiting  ~s ==> ~s~%"
		       (cons ',func param-list)
		       result)
	       result))))
  (print (type the-trace))

  (define-macro (the-untrace func)
    `(let ((the-func (assoc ',func ,trace-list)))
     
       (define (remove! func)
	 (let ((prev ,trace-list)
	       (here (cdr ,trace-list)))
	   (while (and here
		       (not (eq? func (caar here))))
		  (set! prev here)
		  (set! here (cdr here)))
	   (if (not here)
	       (error 'remove "item ~s not found." func)
	       (set-cdr! prev (cdr here)))))
     
       (if the-func
	   (begin (remove! ',func)
		  (set! ,func (cdr the-func))))))
  (print (type the-untrace))
  
  (set! trace the-trace)
  (set! untrace the-untrace))
----------------------------------------------------------------------

--
WAKITA Ken (ken-w@is.s.u-tokyo.ac.jp)		      Masuda Group.,
						Dept. of Info. Sci.,
						     Univ. of Tokyo.

ken-w@is.s.u-tokyo.ac.jp (WAKITA Ken) (10/31/90)

In article <1520@malta.is.s.u-tokyo.ac.jp> WAKITA Ken writes:

 >      ELK does not provide trace facility.  So, I've also written
 > similar, shorter, but a bit dangerous program.  Though it contains
 > several known bugs (it can't trace macros and primitive procedures) it
 > is useful.  I also have written a toplevel eval-read-print loop to
 > support transcript-on/off facility.  An interested reader can request
 > via E-mail to:
 > 
 >         ken-w@is.s.u-tokyo.ac.jp
 >
 > ----------------------------------------------------------------------
 > (define trace)
 > (define untrace)

	.....
	.....

 >   (set! trace the-trace)
 >   (set! untrace the-untrace))
 > ----------------------------------------------------------------------


     After this post, I found a terrible mistake.  The code previously
posted does not work at all.  Actually, that is an older version that
mis-uses macro.  Please forgive me.

     Here is a correct version and sample session.  Several people
sent me a E-mail asking for transcript-on/off function.  So I also
provide together.  The sample session is produced by
transcript-on/off.



----------------------------------------------------------------------
(define trc:trace-list '(()))

(define (reset-trace) (set! trc:trace-list '(())))

(define-macro (trace func)
  `(let ((the-func (eval ,func))
	 (result #v))
     (if (assoc ',func trc:trace-list)
	 (error 'trace "~s already trace on." ,func))
     (if (not (compound? ,func))
	 (error 'trace "wrong argument type ~s (expected compound)"
		(type ,func)))
     (set! trc:trace-list
	   (cons ()
		 (cons (cons ',func the-func)
		       (cdr trc:trace-list))))
     (set! ,func
	   (lambda param-list
	     (format #t "# Entering ~s~%"
		     (cons ',func param-list))
	     (set! result (apply the-func param-list))
	     (format #t "# Exiting  ~s ==> ~s~%"
		     (cons ',func param-list)
		     result)
	     result))))

(define-macro (untrace func)
  `(let ((the-func (assoc ',func trc:trace-list)))
     
     (define (remove! func)
       (let ((prev trc:trace-list)
	     (here (cdr trc:trace-list)))
	 (while (and here
		     (not (eq? func (caar here))))
		(set! prev here)
		(set! here (cdr here)))
	 (if (not here)
	     (error 'remove "item ~s not found." func)
	     (set-cdr! prev (cdr here)))))
     
     (if the-func
	 (begin (remove! ',func)
		(set! ,func (cdr the-func))))))
----------------------------------------------------------------------
;;; -*-Scheme-*-
;;;
;;; Read-eval-print loop and error handler

(define call/cc call-with-current-continuation)

(fluid-let ((autoload-notify? #f))
  (require 'macros))

;;;	(set! load-noisily? #t)

;(require 'usr:set-load-path (tilde-expand "~/lang/elk/lib/set-load-path.scm"))
;(require 'usr:load-all "load-all.scm")

(define ?)
(define ??)
(define ???)
(define !)
(define !!)
(define !!!)
(define &)

(define elk:pre-prompt " User name ")
(define elk:post-prompt "> ")
(define elk:ans-prompt " ==> ")

(define transcript-on)
(define transcript-off)
(define elk:transcript-port #f)

(let ((elk:default-transcript-file "scheme.log")
      (elk:write write)
      (elk:display display)
      (elk:write-char write-char)
      (elk:newline newline)
      (elk:print print)
      (elk:format format))
  
  (set! transcript-on
	(lambda ( . file)
	  (if (output-port? elk:transcript-port)
	      (error 'transcript-on "Transcript file ~s already open"
		     (port-file-name elk:transcript-port)))
	  (set! elk:transcript-port
		(open-output-file (if (null? file)
				      elk:default-transcript-file
				      (car file))))))

  (set! transcript-off
	(lambda ()
	  (if (not (output-port? elk:transcript-port))
	      (error 'transcript-off "Transcript file not open."))
	  (close-port elk:transcript-port)
	  (set! elk:transcript-port #f)))

  (set! write
	(lambda (obj . port)
	  (if port
	      (elk:write obj (car port))
	      (begin (elk:write obj)
		     (if elk:transcript-port
			 (elk:write obj elk:transcript-port))
		     #v))))

  (set! display
	(lambda (obj . port)
	  (if port
	      (elk:display obj (car port))
	      (begin (elk:display obj)
		     (if elk:transcript-port
			 (elk:display obj elk:transcript-port))
		     #v))))

  (set! write-char
	(lambda (char . port)
	  (if port
	      (elk:write-char char (car port))
	      (begin (elk:write-char char)
		     (if elk:transcript-port
			 (elk:write-char char elk:transcript-port))
		     #v))))

  (set! newline
	(lambda ( . port)
	  (if port
	      (elk:newline (car port))
	      (begin (elk:newline)
		     (if elk:transcript-port
			 (elk:newline elk:transcript-port))
		     #v))))

  (set! print
	(lambda (obj . port)
	  (if port
	      (elk:print obj (car port))
	      (begin (elk:print obj)
		     (if elk:transcript-port
			 (elk:print obj elk:transcript-port))
		     #v))))

  (set! format
	(lambda (tty fmt . rest)
	  (let ((res (apply elk:format
			    `(#f ,fmt ,@rest))))
	    (if tty
		(display res)
		res)))))

(define (rep-loop env)
  (define input)
  (define value)
  (let loop ()
    (set! ??? ??)
    (set! ?? ?)
    (set! ? &)
    ;;; X Windows hack
    (if (and (bound? 'display-flush-output) (bound? 'dpy) (display? dpy))
	(display-flush-output dpy))
    (display elk:pre-prompt)
    (if (> rep-level 0)
	(begin (display rep-level)
	       (display " ")))
    (display elk:post-prompt)
    (set! input (read))
    (set! & input)
    (if elk:transcript-port
	(begin (display input elk:transcript-port)
	       (newline elk:transcript-port)))
    (if (not (eof-object? input))
	(begin
	  (set! value (eval input env))
	  (set! !!! !!)
	  (set! !! !)
	  (set! ! value)
	  (display elk:ans-prompt)
	  (if (void? value)
	      (write "#v")
	      (write value))
	  (newline) (newline)
	  (loop)))))

(define rep-frames)
(define rep-level)

(set! interrupt-handler
      (lambda ()
	(display (format #f "~%\7Interrupt!~%"))
	(let ((next-frame (car rep-frames)))
	  (next-frame #t))))

(define-macro (push-frame control-point)
  `(begin
     (set! rep-frames (cons ,control-point rep-frames))
     (set! rep-level (1+ rep-level))))

(define-macro (pop-frame)
  '(begin
     (set! rep-frames (cdr rep-frames))
     (set! rep-level (1- rep-level))))

(define (error-print error-msg)
  (let ((head (format #f "~s: " (car error-msg)))
	(tail (apply format `(#f ,@(cdr error-msg)))))
    (display head)
    (display tail)
    (newline)))

(set! error-handler
  (lambda error-msg
    (error-print error-msg)
    (let loop ((just-called #t))
      (if (call-with-current-continuation
	   (lambda (control-point)
	     (if just-called
		 (push-frame control-point))
	     (rep-loop (the-environment))
	     #f))
	  (loop #f)))
    (newline)
    (pop-frame)
    (let ((next-frame (car rep-frames)))
      (next-frame #t))))

(define top-level-environment (the-environment))

(define (top-level)
  (if (not (call-with-current-continuation
	    (lambda (control-point)
	      (set! rep-frames (list control-point))
	      (set! top-level-control-point control-point)
	      (set! rep-level 0)
	      (rep-loop top-level-environment)
	      #f)))
      (display
       (format #f "     You can't leave toplevel by `^D'.  Use \"(exit)\" instead.~%")))
  (top-level))

(define (the-top-level)
  (top-level))

;(load "logo.scm")

(if (not (bound? 'elk:make-scheme))
    (the-top-level))
----------------------------------------------------------------------
 ==> #f

 Ken > (define (f x) (if (= x 0) 1 (* x (f (- x 1)))))
 ==> f

 Ken > (trace f)
 ==> #[compound f]

 Ken > (f 5)
# Entering (f 5)
# Entering (f 4)
# Entering (f 3)
# Entering (f 2)
# Entering (f 1)
# Entering (f 0)
# Exiting  (f 0) ==> 1
# Exiting  (f 1) ==> 1
# Exiting  (f 2) ==> 2
# Exiting  (f 3) ==> 6
# Exiting  (f 4) ==> 24
# Exiting  (f 5) ==> 120
 ==> 120

 Ken > (transcript-off)
----------------------------------------------------------------------
--
WAKITA Ken (ken-w@is.s.u-tokyo.ac.jp)		      Masuda Group.,
						Dept. of Info. Sci.,
						     Univ. of Tokyo.