;;; trace.lisp ;;; ;;; Copyright (C) 2003-2007 Peter Graves ;;; $Id$ ;;; ;;; This program is free software; you can redistribute it and/or ;;; modify it under the terms of the GNU General Public License ;;; as published by the Free Software Foundation; either version 2 ;;; of the License, or (at your option) any later version. ;;; ;;; This program is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program; if not, write to the Free Software ;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ;;; ;;; As a special exception, the copyright holders of this library give you ;;; permission to link this library with independent modules to produce an ;;; executable, regardless of the license terms of these independent ;;; modules, and to copy and distribute the resulting executable under ;;; terms of your choice, provided that you also meet, for each linked ;;; independent module, the terms and conditions of the license of that ;;; module. An independent module is a module which is not derived from ;;; or based on this library. If you modify this library, you may extend ;;; this exception to your version of the library, but you are not ;;; obligated to do so. If you do not wish to do so, delete this ;;; exception statement from your version. (in-package "SYSTEM") (export 'untraced-function) ;; For FIND-GENERIC-FUNCTION in clos.lisp. (require "FORMAT") (defvar *trace-info-hashtable* (make-hash-table :test #'equal)) (defstruct trace-info name untraced-function breakp) (defvar *trace-depth* 0 "Current depth of stack push for use of TRACE facility.") (defun list-traced-functions () (copy-list *traced-names*)) (defmacro trace (&rest args) (if args (expand-trace args) `(list-traced-functions))) (defun expand-trace (args) (let ((results ()) (breakp nil)) (let ((index (position :break args))) (when index (setf breakp (nth (1+ index) args)) (setf args (append (subseq args 0 index) (subseq args (+ index 2)))))) (dolist (arg args) (push `(trace-1 ',arg (make-trace-info :name ',arg :breakp ,breakp)) results)) `(list ,@(nreverse results)))) (defun trace-1 (name info) (unless (fboundp name) (error "~S is not the name of a function." name)) (if (member name *traced-names* :test #'equal) (format t "~S is already being traced." name) (let* ((untraced-function (fdefinition name)) (traced-function (traced-function name info untraced-function))) (setf (trace-info-untraced-function info) untraced-function) (let ((*warn-on-redefinition* nil)) (setf (fdefinition name) traced-function)) (setf (gethash name *trace-info-hashtable*) info) (push name *traced-names*))) name) (defun traced-function (name info untraced-function) (let ((breakp (trace-info-breakp info)) (*trace-depth* *trace-depth*)) (lambda (&rest args) (with-standard-io-syntax (let ((*print-readably* nil) (*print-structure* nil)) (format *trace-output* (indent "~D: ~S~%") *trace-depth* (cons name args)))) (when breakp (break)) (incf *trace-depth*) (let ((results (multiple-value-list (unwind-protect (apply untraced-function args) (decf *trace-depth*))))) (with-standard-io-syntax (let ((*print-readably* nil) (*print-structure* nil)) (format *trace-output* (indent "~D: ~A returned") *trace-depth* name) (if results (dolist (result results) (format *trace-output* " ~S" result)) (format *trace-output* " no values")) (terpri *trace-output*))) (values-list results))))) (defun untraced-function (name) (let ((info (gethash name *trace-info-hashtable*))) (and info (trace-info-untraced-function info)))) (defun trace-redefined-update (name untraced-function) (when (and *traced-names* (find name *traced-names* :test #'equal)) (let* ((info (gethash name *trace-info-hashtable*)) (traced-function (traced-function name info untraced-function))) (setf (trace-info-untraced-function info) untraced-function) (let ((*traced-names* '())) (setf (fdefinition name) traced-function))))) (defun indent (string) (concatenate 'string (make-string (* (1+ *trace-depth*) 2) :initial-element #\space) string)) (defmacro untrace (&rest args) (cond ((null args) `(untrace-all)) (t `(progn ,@(mapcar (lambda (arg) `(untrace-1 ',arg)) args) t)))) (defun untrace-all () (dolist (arg *traced-names*) (untrace-1 arg)) t) (defun untrace-1 (name) (cond ((member name *traced-names* :test #'equal) (let* ((trace-info (gethash name *trace-info-hashtable*)) (untraced-function (trace-info-untraced-function trace-info)) (*warn-on-redefinition* nil)) (remhash name *trace-info-hashtable*) (setf *traced-names* (remove name *traced-names*)) (setf (fdefinition name) untraced-function))) (t (format t "~S is not being traced.~%" name))) nil)