Skip to content

Commit e6ac0b9

Browse files
committed
trace-macroexpand tries to handle bad states
There is both detection and ways of recovering in the form of restarts.
1 parent 31296ab commit e6ac0b9

File tree

2 files changed

+70
-6
lines changed

2 files changed

+70
-6
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ CL-USER 52 > (trace-macro)
731731
```
732732

733733
### How it works, caveats
734-
All `trace-macroexpand` does is to install a hook on `*macroexpand-hook` and use this to drive the tracing. It is careful to call any preexisting hook as well, so it does not interfere with anything else. However, don't unilaterally change `*macroexpand-hook*` while macro tracing is active: turn it off first, as things will become confused otherwise.
734+
All `trace-macroexpand` does is to install a hook on `*macroexpand-hook` and use this to drive the tracing. It is careful to call any preexisting hook as well, so it does not interfere with anything else. However, don't unilaterally change `*macroexpand-hook*` while macro tracing is active: turn it off first, as things will become confused otherwise. If it detects bad states (for instance if tracing is off but the wrapped hook isn't `nil`, or if tracing seems to be on but the wrapped hook *is* `nil`) it will signal errors and there are restarts which may help recover. But it's best to not get into these states.
735735

736736
Tracing output goes to `*trace-output*`.
737737

trace-macroexpand.lisp

+69-5
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,24 @@ The return value is ignored.")
165165
;; restore, and also to call the wrapped hook.
166166
nil)
167167

168+
(define-condition insane-state (simple-error)
169+
())
170+
168171
(defun trace-macroexpand-hook (macro-function macro-form environment)
169172
;; Trace macros: this is installed as the value of *macroexpand-hook*
170173
(unless *wrapped-macroexpand-hook*
171-
(error "No wrapped *MACROEXPAND-HOOK*?"))
174+
(restart-case
175+
(error 'insane-state
176+
:format-control "No wrapped *MACROEXPAND-HOOK*?")
177+
(continue ()
178+
:report "Install FUNCALL as the wrapped hook"
179+
(setq *wrapped-macroexpand-hook* 'funcall))
180+
(store-value (v)
181+
:report "Set the wrapped hook to a value"
182+
:interactive (lambda ()
183+
(format *query-io* "~&Value for wrapped hook: ")
184+
(list (read *query-io*)))
185+
(setq *wrapped-macroexpand-hook* v))))
172186
(if (trace-macroexpand-trace-p macro-function macro-form environment)
173187
(let ((expanded-form (funcall *wrapped-macroexpand-hook*
174188
macro-function macro-form environment)))
@@ -184,24 +198,73 @@ The return value is ignored.")
184198
(funcall *wrapped-macroexpand-hook*
185199
macro-function macro-form environment)))
186200

201+
(defvar *should-be-tracing-p*
202+
;; This is what we think the state is
203+
nil)
204+
205+
(defun state-sanity-check ()
206+
;; Do some kind of sanity check, returning OK and recovered. T and
207+
;; NIL is fine, NIL and T means we may be fine anything else should
208+
;; never happen
209+
(if *should-be-tracing-p*
210+
(unless *wrapped-macroexpand-hook*
211+
(restart-case
212+
(error 'insane-state
213+
:format-control "Tracing on but no wrapped *MACROEXPAND-HOOK*?") ;
214+
(continue ()
215+
:report "Install FUNCALL as the wrapped hook"
216+
(setq *wrapped-macroexpand-hook* 'funcall)
217+
(values nil t))
218+
(store-value (v)
219+
:report "Set the wrapped hook to a value"
220+
:interactive (lambda ()
221+
(format *query-io* "~&Value for wrapped hook: ")
222+
(list (read *query-io*)))
223+
(setq *wrapped-macroexpand-hook* v)
224+
(values nil t))))
225+
(when *wrapped-macroexpand-hook*
226+
(restart-case
227+
(error
228+
'insane-state
229+
:format-control "Tracing off but there is a wrapped *MACROEXPAND-HOOK*?")
230+
(continue ()
231+
:report "Set the wrapped hook to NIL"
232+
(setf *wrapped-macroexpand-hook* nil)
233+
(values nil t)))))
234+
(values t nil))
235+
187236
(defun trace-macroexpand (&optional (tracep t))
188237
"Trace or untrace macroexpansion.
189238
190239
If called with no argument, or an argument which is true, ensure that
191240
macroexpansion is on. Otherwise ensure it is off.
192241
193242
Return the previous state."
194-
(let ((currently-tracing (if *wrapped-macroexpand-hook* t nil)))
243+
(multiple-value-bind (ok recovered) (state-sanity-check)
244+
(when (not ok)
245+
(if recovered
246+
(warn "Perhaps recovered from an insane state")
247+
(error 'insane-state
248+
:format-control "Not OK and not recovered: this should not happen"))))
249+
(let ((currently-tracing *should-be-tracing-p*))
195250
(cond ((and tracep (not currently-tracing))
196251
(setf *wrapped-macroexpand-hook* *macroexpand-hook*
197-
*macroexpand-hook* #'trace-macroexpand-hook))
252+
*macroexpand-hook* #'trace-macroexpand-hook
253+
*should-be-tracing-p* t))
198254
((and (not tracep) currently-tracing)
199255
(setf *macroexpand-hook* *wrapped-macroexpand-hook*
200-
*wrapped-macroexpand-hook* nil)))
256+
*wrapped-macroexpand-hook* nil
257+
*should-be-tracing-p* nil)))
201258
currently-tracing))
202259

203260
(defun macroexpand-traced-p ()
204261
"Is macroexpansion currently traced?"
262+
(multiple-value-bind (ok recovered) (state-sanity-check)
263+
(when (not ok)
264+
(if recovered
265+
(warn "Perhaps recovered from an insane state")
266+
(error 'insane-state
267+
:format-control "Not OK and not recovered: this should not happen"))))
205268
(if *wrapped-macroexpand-hook* t nil))
206269

207270
(defun call/macroexpand-tracing (f &optional (state t))
@@ -210,7 +273,8 @@ Return the previous state."
210273
This is useful for compiling files, say, where you want to see what
211274
happens."
212275
(let ((*macroexpand-hook* *macroexpand-hook*)
213-
(*wrapped-macroexpand-hook* *wrapped-macroexpand-hook*))
276+
(*wrapped-macroexpand-hook* *wrapped-macroexpand-hook*)
277+
(*should-be-tracing-p* *should-be-tracing-p*))
214278
(trace-macroexpand state)
215279
(funcall f)))
216280

0 commit comments

Comments
 (0)