;;; DTRACE is a portable alternative to the Common Lisp TRACE and UNTRACE ;;; macros. It offers a more detailed display format than the tracing ;;; tool most Common Lisp implementations provide. ;;; ;;; From the book "Common Lisp: A Gentle Introduction to ;;; Symbolic Computation" by David S. Touretzky. ;;; The Benjamin/Cummings Publishing Co., 1990. ;;; ;;; This version of the software is for Golden Common Lisp 1.01 and 1.1. ;;; ;;; User-level routines: ;;; DTRACE - same syntax as TRACE ;;; DUNTRACE - same syntax as UNTRACE ;;; ;;; Copyright (c) 1988,1989 Symbolic Technology, Ltd. ;;; This software may not be sold or used for commercial purposes without ;;; prior written permission from the copyright holder. (trace) ; Autoload Gold Hill's TRACE so that UNTRACE will be defined. ;;; DTRACE and related routines. (defparameter *dtrace-print-length* 7) (defparameter *dtrace-print-level* 4) (defparameter *dtrace-print-circle* t) (defparameter *dtrace-print-pretty* nil) ;;;(defparameter *dtrace-print-array* *print-array*) (trace) ; so Gold Hill's UNTRACE will be loaded (defvar *traced-functions* nil) (defvar *trace-level* 0) (defmacro dtrace (&rest function-names) "Turns on detailed tracing for specified functions. Undo with DUNTRACE." (if (null function-names) (list 'quote *traced-functions*) (list 'quote (mapcan #'dtrace1 function-names)))) (defun dtrace1 (name) (unless (symbolp name) (format *error-output* "~&~S is an invalid function name." name) (return-from dtrace1 nil)) (unless (fboundp name) (format *error-output* "~&~S undefined function." name) (return-from dtrace1 nil)) (when (special-form-p name) (format *error-output* "~&Can't trace ~S because it's a special form." name) (return-from dtrace1 nil)) (eval `(untrace ,name)) ;if they're tracing it, undo their trace (duntrace1 name) ;if we're already tracing it, undo our trace (if (macro-function name) (trace-macro name) (trace-function name)) (setf *traced-functions* (nconc *traced-functions* (list name))) (list name)) ; The functions below reference DISPLAY-... routines that are implementation-specific. ; These routines are defined at the end of the file. (defun trace-function (name) (let* ((formal-arglist (fetch-arglist name)) (old-defn (symbol-function name)) (new-defn `(lambda (&rest argument-list) (let ((result nil)) (display-function-entry ',name) (let ((*trace-level* (1+ *trace-level*))) (with-dtrace-printer-settings (show-function-args argument-list ',formal-arglist)) (setf result (multiple-value-list (apply ',old-defn argument-list)))) (display-function-return ',name result) (values-list result))))) (setf (get name 'original-definition) old-defn) (setf (get name 'traced-definition) new-defn) ;in case function was re-DEFUN'ed while traced (setf (get name 'traced-type) 'defun) (setf (symbol-function name) new-defn))) (defun trace-macro (name) (let* ((formal-arglist (fetch-arglist name)) (old-defn (symbol-function name)) (new-defn `(macro lambda (macro-args) (let ((result nil)) (display-function-entry ',name 'macro) (let ((*trace-level* (1+ *trace-level*))) (with-dtrace-printer-settings (show-function-args macro-args ',formal-arglist)) (setf result (eval (append '(funcall ',old-defn) (cdr macro-args))))) (display-function-return ',name (list result) 'macro) (values result))))) (setf (get name 'original-definition) old-defn) (setf (get name 'traced-definition) new-defn) ;in case function was re-DEFUN'ed while traced (setf (get name 'traced-type) 'defmacro) (setf (symbol-function name) new-defn))) (defun show-function-args (actuals formals &optional (argcount 0)) (cond ((null actuals) nil) ((null formals) (handle-args-numerically actuals argcount)) (t (case (first formals) (&optional (show-function-args actuals (rest formals) argcount)) (&rest (show-function-args (list actuals) (rest formals) argcount)) (&key (handle-keyword-args actuals)) (&aux (show-function-args actuals nil argcount)) (t (handle-one-arg (first actuals) (first formals)) (show-function-args (rest actuals) (rest formals) (1+ argcount))))))) (defun handle-args-numerically (actuals argcount) (dolist (x actuals) (incf argcount) (display-arg-numeric x argcount))) (defun handle-one-arg (val varspec) (cond ((atom varspec) (display-one-arg val varspec)) (t (display-one-arg val (first varspec)) (if (third varspec) (display-one-arg t (third varspec)))))) (defun handle-keyword-args (actuals) (cond ((null actuals)) ((keywordp (first actuals)) (display-one-arg (second actuals) (first actuals)) (handle-keyword-args (rest (rest actuals)))) (t (display-one-arg actuals "Extra args:")))) ;;; DUNTRACE and related routines. (defmacro duntrace (&rest function-names) "Turns off tracing for specified functions. With no args, turns off all tracing." (setf *trace-level* 0) ;safety precaution in case things got broken (list 'quote (mapcan #'duntrace1 (or function-names *traced-functions*)))) (defun duntrace1 (name) (unless (symbolp name) (format *error-output* "~&~S is an invalid function name." name) (return-from duntrace1 nil)) (setf *traced-functions* (delete name *traced-functions*)) (let ((orig-defn (get name 'original-definition 'none)) (traced-defn (get name 'traced-definition)) (traced-type (get name 'traced-type 'none))) (unless (or (eq orig-defn 'none) (not (fboundp name)) (not (equal traced-defn (case traced-type (defun (symbol-function name)) (defmacro (symbol-function name)))))) ;in case redefined (case traced-type (defun (setf (symbol-function name) orig-defn)) (defmacro (setf (symbol-function name) orig-defn))))) (remprop name 'traced-definition) (remprop name 'traced-type) (remprop name 'original-definition) (list name)) ;;; Display routines. ; The code below generates vanialla character output for ordinary terminals. ; It can be replaced with special graphics code if the implementation permits, ; e.g., on a PC you can use the IBM graphic character set to draw nicer looking ; arrows. On a color PC you can use different colors for arrows, for function, ; names, for argument values, and so on. (unless (boundp '*monitor-is-color*) ;version 1.01 vs. version 1.1 (setq *monitor-is-color* sys::*monitor-is-color*)) (defconstant normal-att #x07) (defconstant arrow-att (if *monitor-is-color* #x0e normal-att)) (defconstant enter-att (if *monitor-is-color* #x0f normal-att)) (defconstant args-att (if *monitor-is-color* #x0e normal-att)) (defconstant fname-att (if *monitor-is-color* #x0b normal-att)) (macro with-attribute (form) (let ((x (if (atom (second form)) (second form) (caadr form))) (stream (or (and (consp (second form)) (cdr (second form)) (second (second form))) '*terminal-io*)) (body (cddr form))) (rplacb form `(let ((save-att (send ,stream :attribute))) (unwind-protect (progn (send ,stream :set-attribute ,x) . ,body) (send ,stream :set-attribute save-att)))))) (defmacro with-dtrace-printer-settings (&body body) `(let ((*print-length* *dtrace-print-length*) (*print-level* *dtrace-print-level*) (*print-circle* *dtrace-print-circle*) ;; (*print-array* *dtrace-print-array*) (*print-pretty* *dtrace-print-pretty*)) ,@body)) (defparameter *entry-arrow-string* '(201. 205. 205. 175.)) (defparameter *vertical-arrow* 186.) (defparameter *exit-arrow-string* '(200. 205. 205. 175.)) (defun send-char (stream x) (if (listp x) (dolist (i x) (with-attribute (arrow-att *trace-output*) (send stream :write-char i))) (send stream :write-char x))) (defparameter *trace-wraparound* 15) (defun display-function-entry (name &optional ftype) (space-over) (draw-entry-arrow) (with-attribute (enter-att *trace-output*) (format *trace-output* "Enter ")) (with-attribute (fname-att *trace-output*) (format *trace-output* "~S" name)) (if (eq ftype 'macro) (format *trace-output* " macro"))) (defun display-one-arg (val name) (space-over) (with-attribute (args-att *trace-output*) (if (and (listp (type-of name)) (equal (butlast (type-of name)) '(vector string-char))) (format *trace-output* " ~A ~S" name val) (format *trace-output* (case (type-of name) (keyword " ~S ~S") (string " ~A ~S") (t " ~S = ~S")) name val)))) (defun display-arg-numeric (val num) (space-over) (with-attribute (args-att *trace-output*) (format *trace-output* " Arg-~D = ~S" num val))) (defun display-function-return (name results &optional ftype) (with-dtrace-printer-settings (space-over) (draw-exit-arrow) (with-attribute (fname-att *trace-output*) (format *trace-output* "~S " name)) (with-attribute (enter-att *trace-output*) (format *trace-output* "~A" (if (eq ftype 'macro) "expanded to" "returned"))) (with-attribute (args-att *trace-output*) (cond ((null results)) ((null (rest results)) (format *trace-output* " ~S" (first results))) (t (format *trace-output* " values ~{~S, ~}~s" (butlast results) (car (last results)))))))) (defun space-over () (format *trace-output* "~&") (dotimes (i (mod *trace-level* *trace-wraparound*)) (send-char *trace-output* *vertical-arrow*) (format *trace-output* " "))) (defun draw-entry-arrow () (send-char *trace-output* *entry-arrow-string*)) (defun draw-exit-arrow () (send-char *trace-output* *exit-arrow-string*)) ;;; Other implementation-specific routines go here. ; The function FETCH-ARGLIST is implementation-specific. It returns the ; formal argument list of a function, exactly as the list would appear in ; a DEFUN or lambda expression. ; ; The version below is for Golden Common Lisp version 1.01 and up. (defun fetch-arglist (x) (if (macro-function x) ;; then x is defmacro '(&rest "Form =") ;; else it is normal (lambda-list x))) ; Some versions of GCLisp don't have MOD built in. (unless (fboundp 'mod) (defun mod (x y) (multiple-value-bind (q r) (truncate x y) r))) ; Note: if you're using the $50 educational version of Golden Common Lisp, ; you'll need this ersatz version of LAMBDA-LIST. The real version is more ; powerful: it looks up lambda lists in the on-line doc. if necessary. ; (unless (fboundp 'lambda-list) (defun lambda-list (fn) (let ((x (symbol-function fn))) (and (consp x) (eq (first x) 'lambda) (second x)))))