trace.lisp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. ;;; trace.lisp --- Standard Lisp on Common Lisp trace facilities
  2. ;; Copyright (C) 2019 Francis J. Wright
  3. ;; Author: Francis J. Wright <https://sourceforge.net/u/fjwright>
  4. ;; Created: 20 February 2019
  5. ;; Based on, and hopefully consistent with, the portable REDUCE
  6. ;; tracing code in "package/rtrace/rtrace.red". But this is a
  7. ;; completely independent Common Lisp implementation.
  8. ;; Must load "sl-on-cl" before loading or compiling this file.
  9. ;; ****************************
  10. ;; Can be loaded into REDUCE by
  11. ;; : lisp load trace
  12. ;; (without quotes) or
  13. ;; : lisp load "trace.lisp"
  14. ;; etc.
  15. ;; ****************************
  16. (declaim (optimize debug))
  17. (defpackage :standard-lisp-trace
  18. (:nicknames :sl-trace)
  19. (:documentation "Lower-case Standard Lisp on Common Lisp trace facilities")
  20. (:use :common-lisp)
  21. (:import-from :sl :eqcar :put)
  22. (:export :tr :untr :trst :untrst))
  23. (in-package :standard-lisp-trace)
  24. ;; The following macros accept a sequence of function names. The
  25. ;; expression `(tr foo bar)' causes the input and output of the
  26. ;; functions `foo' and `bar' to be traced. The expression `(trst foo
  27. ;; bar)' causes both the I/O and the assignments to be traced. The
  28. ;; expression `(untr foo bar)' removes all tracing, and `untrst' is a
  29. ;; synonym for `untr'.
  30. (defvar *traced-functions* nil
  31. "List of currently traced functions.")
  32. (defmacro tr (&rest fns)
  33. "Trace the functions specified.
  34. If no functions are specified then list all traced functions."
  35. (if fns
  36. `(cl:mapcar #'trace1 ',fns)
  37. '*traced-functions*))
  38. (defmacro untr (&rest fns)
  39. "Untrace(set) the functions specified.
  40. Untrace(set) all traced functions if no functions are specified."
  41. `(cl:mapcar #'untrace1 ',(or fns *traced-functions*)))
  42. (defvar *trace-setq* nil)
  43. (defmacro trst (&rest fns)
  44. "Traceset the functions specified.
  45. If no functions are specified then list all traced functions."
  46. (if fns
  47. `(let ((*trace-setq* t))
  48. (cl:mapcar #'trace1 ',fns))
  49. '*traced-functions*))
  50. (setf (macro-function 'untrst) (macro-function 'untr))
  51. (defun trace1 (name)
  52. "Trace or traceset function NAME.
  53. NAME must be quoted when called!"
  54. (let* ((defn (and (symbolp name) (fboundp name) (symbol-function name)))
  55. (olddefn defn) ; saved for reliable untracing
  56. params)
  57. ;; Check function is defined:
  58. (unless defn
  59. (format *trace-output*
  60. "***** ~a not yet defined.~%" name)
  61. (return-from trace1))
  62. ;;
  63. ;; (when sl::*comp
  64. ;; (format *trace-output*
  65. ;; "~a ~a~%"
  66. ;; "Portable tracing does not work reliably with the"
  67. ;; "switch `comp' on, so it has been turned off.")
  68. ;; (sl::compilation (setq sl::*comp nil)))
  69. ;;
  70. ;; Get a lambda expression and extract the parameters if possible:
  71. (if (setq defn (function-lambda-expression defn))
  72. ;; Note that a CL function definition may contain declarations
  73. ;; and a documentation string, and the body may be wrapped in
  74. ;; a block form, i.e.
  75. ;; defn = (lambda params [decls] [doc] body)
  76. ;; or
  77. ;; defn = (lambda params [decls] [doc] (block name body))
  78. (let ((b (car (last defn))))
  79. (setq defn (list 'lambda (cadr defn)
  80. (if (eqcar b 'block) (caddr b) b))))
  81. (if (setq defn (get-fasl-source name))
  82. ;; defn = (de name arglist body)
  83. (setq defn (cons 'lambda (cddr defn)))))
  84. ;;
  85. (if defn
  86. (progn ; defn = (lambda params body)
  87. (if (eqcar (caddr defn) 'run-traced-function)
  88. (return-from trace1
  89. (if (eq (get name 'traced-setq) *trace-setq*)
  90. ;; i.e. both true or both false
  91. (format *trace-output*
  92. "*** ~a already traced.~%" name)
  93. (re-trace1 name)))
  94. ;; wrap params in a list in case params = () = nil!
  95. (setq params (list (cadr defn)))))
  96. (progn ; defn = compiled form
  97. (setq defn olddefn)
  98. (if *trace-setq*
  99. (progn
  100. (format *trace-output*
  101. "*** ~a ~a~%~a~%"
  102. name
  103. "must be interpreted for portable assignment tracing."
  104. "*** Tracing arguments and return value only.")
  105. (setq *trace-setq* nil)))))
  106. ;;
  107. (if params
  108. (setq params (car params)) ; unwrap params
  109. (if (setq params (get name 'sl::number-of-args))
  110. (progn
  111. (setq params
  112. (loop
  113. for i from 1 upto params collect
  114. (intern (format nil "Arg~d" i))))
  115. (format *trace-output*
  116. "*** ~a is compiled: ~a~%"
  117. name
  118. "portable tracing may not show recursive calls."))
  119. (progn
  120. (format *trace-output*
  121. "***** parameters for ~a unavailable so cannot apply portable tracing.~%"
  122. name)
  123. (return-from trace1))))
  124. ;;
  125. (pushnew name *traced-functions*)
  126. (if *trace-setq*
  127. (progn
  128. (setq defn (subst 'traced-setq 'setq defn))
  129. (put name 'traced-setq t))
  130. ;; in case function has been redefined:
  131. (remprop name 'traced-setq))
  132. (put name 'untraced-function olddefn)
  133. (put name 'traced-function defn)
  134. (eval `(defun ,name ,params
  135. (run-traced-function ',name ',params (list . ,params))))))
  136. (defun re-trace1 (name)
  137. "Toggle trace/traceset for function NAME.
  138. NAME must be quoted when called!"
  139. (let ((defn (get name 'traced-function)))
  140. (if *trace-setq*
  141. (if (consp defn)
  142. (progn
  143. (setq defn (subst 'traced-setq 'setq defn))
  144. (put name 'traced-setq t))
  145. (return-from re-trace1
  146. (format *trace-output*
  147. "*** ~a ~a~%~a~%"
  148. name
  149. "must be interpreted for portable assignment tracing."
  150. "*** Tracing arguments and return value only.")))
  151. (progn
  152. (setq defn (subst 'setq 'traced-setq defn))
  153. (remprop name 'traced-setq)))
  154. (put name 'traced-function defn)
  155. (format *trace-output* "*** Trace mode of ~a changed.~%" name)
  156. name))
  157. (defun untrace1 (name)
  158. "Remove all tracing for function NAME.
  159. NAME must be quoted when called!"
  160. (let ((olddefn (get name 'untraced-function)))
  161. (if olddefn (setf (symbol-function name) olddefn))
  162. (remprop name 'untraced-function)
  163. (remprop name 'traced-function)
  164. (remprop name 'traced-setq)
  165. (setq *traced-functions* (remove name *traced-functions*))
  166. name))
  167. (defvar trace-depth 0)
  168. (defvar *trpause nil
  169. "If non-nil then ask whether to continue before each traced execution.
  170. Abort with an error if the answer is no.")
  171. (defun run-traced-function (name params args)
  172. (let ((trace-depth (1+ trace-depth))
  173. (result (get name 'traced-function)))
  174. (format *trace-output* "Enter (~a) ~a~%" trace-depth name)
  175. (loop for param in params for arg in args do
  176. (format *trace-output* " ~a: ~s~%" param arg))
  177. (if (and *trpause (not (y-or-n-p "Continue?")))
  178. (error "Tracing aborted!"))
  179. (setq result
  180. (sl::errorset `(apply ,(eval result) ',args) nil nil))
  181. (if (or (atom result) (cdr result)) ; errorp result
  182. (sl::error 0 sl::emsg*)
  183. (setq result (car result)))
  184. (format *trace-output* "Leave (~a) ~a = ~s~%" trace-depth name result)
  185. result))
  186. (defmacro traced-setq (left right)
  187. "For symbolic assignments.
  188. Must avoid evaluating the lhs of the assignment, and evaluate
  189. the rhs only once in case of side effects (such as a gensym)."
  190. `(progn (format *trace-output* "~a := " ',left)
  191. ,(if (eqcar right 'traced-setq)
  192. `(setq ,left ,right)
  193. `(prog1 (prin1 (setq ,left ,right) *trace-output*)
  194. (terpri *trace-output*)))))
  195. (defun get-fasl-source (name)
  196. "Get DE form for function NAME from \"fasl/modulename.lisp\"."
  197. (let ((*readtable* (copy-readtable nil)) ; read CL syntax
  198. file pos stream form)
  199. (when (and
  200. (setq file (get name 'sl::defined-in-file)) ; e.g. "pgk/mod.red"
  201. (setq pos (position #\/ (setq file (symbol-name file))))) ; e.g. 3
  202. (setq file (subseq file pos (- (length file) 3))) ; e.g. "/mod."
  203. (setq file (concatenate 'string "fasl" file "lisp")) ; e.g. "fasl/mod.lisp"
  204. (when (setq stream (open file :external-format
  205. #+SBCL :UTF-8 #+CLISP charset:UTF-8))
  206. (loop
  207. do
  208. (setq form (read stream nil sl::$eof$))
  209. until
  210. (or (and (eqcar form 'sl::de) (eq (cadr form) name))
  211. (eq form sl::$eof$)))
  212. (close stream)
  213. (unless (eq form sl::$eof$) form)))))
  214. (shadowing-import '(tr untr trst untrst) :sl)
  215. ;; The above import wipes any previous properties, so...
  216. (sl::flag '(tr untr trst untrst) 'sl::noform)
  217. (sl::deflist '((tr sl::rlis) (untr sl::rlis) (trst sl::rlis) (untrst sl::rlis))
  218. 'sl::stat)
  219. ;;; trace.lisp ends here