1
;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: "CONDITIONS"; Base: 10 -*-
3
(IN-PACKAGE "CONDITIONS")
7
(DEFVAR *UNIQUE-ID-TABLE* (MAKE-HASH-TABLE))
8
(DEFVAR *UNIQUE-ID-COUNT* -1)
10
(DEFUN UNIQUE-ID (OBJ)
11
"Generates a unique integer ID for its argument."
12
(OR (GETHASH OBJ *UNIQUE-ID-TABLE*)
13
(SETF (GETHASH OBJ *UNIQUE-ID-TABLE*) (INCF *UNIQUE-ID-COUNT*))))
15
;;; Miscellaneous Utilities
17
(EVAL-WHEN (EVAL COMPILE LOAD)
19
(DEFUN PARSE-KEYWORD-PAIRS (LIST KEYS)
20
(DO ((L LIST (CDDR L))
21
(K '() (LIST* (CADR L) (CAR L) K)))
22
((OR (NULL L) (NOT (MEMBER (CAR L) KEYS)))
23
(VALUES (NREVERSE K) L))))
25
(DEFMACRO WITH-KEYWORD-PAIRS ((NAMES EXPRESSION &OPTIONAL KEYWORDS-VAR) &BODY FORMS)
26
(LET ((TEMP (MEMBER '&REST NAMES)))
27
(UNLESS (= (LENGTH TEMP) 2) (ERROR "&REST keyword is ~:[missing~;misplaced~]." TEMP))
28
(LET ((KEY-VARS (LDIFF NAMES TEMP))
29
(KEY-VAR (OR KEYWORDS-VAR (GENSYM)))
30
(REST-VAR (CADR TEMP)))
31
(LET ((KEYWORDS (MAPCAR #'(LAMBDA (X) (INTERN (STRING X) (FIND-PACKAGE "KEYWORD")))
33
`(MULTIPLE-VALUE-BIND (,KEY-VAR ,REST-VAR)
34
(PARSE-KEYWORD-PAIRS ,EXPRESSION ',KEYWORDS)
35
(LET ,(MAPCAR #'(LAMBDA (VAR KEYWORD) `(,VAR (GETF ,KEY-VAR ,KEYWORD)))
43
(DEFVAR *RESTART-CLUSTERS* '())
44
;;; An ALIST (condition . restarts) which records the restarts currently
45
;;; associated with Condition.
47
(defvar *condition-restarts* ())
50
(DEFUN COMPUTE-RESTARTS (&optional condition)
51
; #+kcl (nconc (mapcan #'copy-list *RESTART-CLUSTERS*) (kcl-top-restarts))
52
; #-kcl (mapcan #'copy-list *RESTART-CLUSTERS*))
55
(dolist (alist *condition-restarts*)
56
(if (eq (car alist) condition)
57
(setq associated (cdr alist))
58
(setq other (append (cdr alist) other))))
60
(dolist (restart-cluster *restart-clusters*)
61
(dolist (restart restart-cluster)
62
(when (and (or (not condition)
63
(member restart associated)
64
(not (member restart other)))
65
(funcall (restart-test-function restart) condition))
67
(nconc (nreverse res) (kcl-top-restarts)))))
70
(defmacro with-condition-restarts (condition-form restarts-form &body body)
71
"WITH-CONDITION-RESTARTS Condition-Form Restarts-Form Form*
72
Evaluates the Forms in a dynamic environment where the restarts in the list
73
Restarts-Form are associated with the condition returned by Condition-Form.
74
This allows FIND-RESTART, etc., to recognize restarts that are not related
75
to the error currently being debugged. See also RESTART-CASE."
76
(let ((n-cond (gensym)))
77
`(let ((*condition-restarts*
78
(cons (let ((,n-cond ,condition-form))
80
(append ,restarts-form
81
(cdr (assoc ,n-cond *condition-restarts*)))))
82
*condition-restarts*)))
85
(DEFUN RESTART-PRINT (RESTART STREAM DEPTH)
86
(DECLARE (IGNORE DEPTH))
88
(FORMAT STREAM "#<~S.~D>" (TYPE-OF RESTART) (UNIQUE-ID RESTART))
89
(RESTART-REPORT RESTART STREAM)))
91
(DEFSTRUCT (RESTART (:PRINT-FUNCTION RESTART-PRINT))
96
(test-function #'(lambda (cond) (declare (ignore cond)) t)))
100
(defvar *kcl-top-restarts* nil)
102
(defun make-kcl-top-restart (quit-tag)
103
;; FIXME need this restart for :q, but invoke-restarts must signal
104
;; a control error if abort called outside a defined restart
105
(make-restart :name 'abort1
106
:function #'(lambda () (throw (car (list quit-tag)) quit-tag))
109
(let ((b-l (if (eq quit-tag si::*quit-tag*)
111
(car (or (find quit-tag si::*quit-tags*
114
(cond ((eq b-l :not-found)
115
(format stream "Return to ? level."))
117
(format stream "Return to top level."))
119
(format stream "Return to break level ~D."
121
:interactive-function nil))
123
(defun find-kcl-top-restart (quit-tag)
124
(cdr (or (assoc quit-tag *kcl-top-restarts*)
125
(car (push (cons quit-tag (make-kcl-top-restart quit-tag))
126
*kcl-top-restarts*)))))
128
(defun kcl-top-restarts ()
129
(let* ((old-tags (mapcan #'(lambda (e) (when (cdr e) (list (cdr e))))
131
(tags (if si::*quit-tag* (cons si::*quit-tag* old-tags) old-tags))
132
(restarts (mapcar #'find-kcl-top-restart tags)))
133
(setq *kcl-top-restarts* (mapcar #'cons tags restarts))
137
(DEFUN RESTART-REPORT (RESTART STREAM)
138
(FUNCALL (OR (RESTART-REPORT-FUNCTION RESTART)
139
(LET ((NAME (RESTART-NAME RESTART)))
141
(IF NAME (FORMAT STREAM "~S" NAME)
142
(FORMAT STREAM "~S" RESTART)))))
145
(DEFMACRO RESTART-BIND (BINDINGS &BODY FORMS)
146
`(LET ((*RESTART-CLUSTERS* (CONS (LIST ,@(MAPCAR #'(LAMBDA (BINDING)
148
:NAME ',(CAR BINDING)
149
:FUNCTION ,(CADR BINDING)
152
*RESTART-CLUSTERS*)))
155
(DEFUN FIND-RESTART (NAME &optional condition)
156
(let ((rl (compute-restarts condition)))
158
(when (or (eq restart name) (eq (restart-name restart) name))
159
(return-from find-restart restart)))))
160
; (declare (ignore condition))
161
; (DOLIST (RESTART-CLUSTER *RESTART-CLUSTERS*)
162
; (DOLIST (RESTART RESTART-CLUSTER)
163
; (WHEN (OR (EQ RESTART NAME) (EQ (RESTART-NAME RESTART) NAME))
164
; (RETURN-FROM FIND-RESTART RESTART))))
166
; (let ((RESTART-CLUSTER (kcl-top-restarts)))
167
; (DOLIST (RESTART RESTART-CLUSTER)
168
; (WHEN (OR (EQ RESTART NAME) (EQ (RESTART-NAME RESTART) NAME))
169
; (RETURN-FROM FIND-RESTART RESTART)))))
171
(DEFUN INVOKE-RESTART (RESTART &REST VALUES)
172
(LET ((REAL-RESTART (OR (FIND-RESTART RESTART)
173
(specific-ERROR :control-error "Restart ~S is not active." RESTART))))
174
(APPLY (RESTART-FUNCTION REAL-RESTART) VALUES)))
176
(DEFUN INVOKE-RESTART-INTERACTIVELY (RESTART)
177
(LET ((REAL-RESTART (OR (FIND-RESTART RESTART)
178
(ERROR "Restart ~S is not active." RESTART))))
179
(APPLY (RESTART-FUNCTION REAL-RESTART)
180
(LET ((INTERACTIVE-FUNCTION
181
(RESTART-INTERACTIVE-FUNCTION REAL-RESTART)))
182
(IF INTERACTIVE-FUNCTION
183
(FUNCALL INTERACTIVE-FUNCTION)
186
(eval-when (compile load eval)
187
;;; Wrap the restart-case expression in a with-condition-restarts if
188
;;; appropriate. Gross, but it's what the book seems to say...
190
(defmacro once-only (specs &body body)
191
"Once-Only ({(Var Value-Expression)}*) Form*
192
Create a Let* which evaluates each Value-Expression, binding a temporary
193
variable to the result, and wrapping the Let* around the result of the
194
evaluation of Body. Within the body, each Var is bound to the corresponding
196
(LABELS ((FROB (SPECS BODY)
199
(LET ((SPEC (FIRST SPECS)))
200
(WHEN (/= (LENGTH SPEC) 2)
201
(ERROR "Malformed Once-Only binding spec: ~S." SPEC))
202
(LET ((NAME (FIRST SPEC)) (EXP-TEMP (GENSYM)))
203
`(LET ((,EXP-TEMP ,(SECOND SPEC)) (,NAME (GENSYM "OO-")))
204
`(LET ((,,NAME ,,EXP-TEMP))
205
,,(FROB (REST SPECS) BODY))))))))
208
(defun munge-restart-case-expression (expression data)
209
(let ((exp (macroexpand expression)))
211
(let* ((name (car exp))
212
(args (if (eq name 'cerror) (cddr exp) (cdr exp))))
213
(if (member name '(signal error cerror warn))
214
(once-only ((n-cond `(coerce-to-condition
218
(warn 'simple-warning)
219
(signal 'simple-condition)
222
`(with-condition-restarts
224
(list ,@(mapcar #'(lambda (da)
225
`(find-restart ',(nth 0 da)))
227
,(if (eq name 'cerror)
228
`(cerror ,(second expression) ,n-cond)
235
(DEFMACRO RESTART-CASE (EXPRESSION &BODY CLAUSES)
236
(FLET ((TRANSFORM-KEYWORDS (&KEY REPORT INTERACTIVE TEST)
239
(SETQ RESULT (LIST* (IF (STRINGP REPORT)
241
(WRITE-STRING ,REPORT STREAM))
246
(SETQ RESULT (LIST* `#',INTERACTIVE
247
:INTERACTIVE-FUNCTION
250
(setq result (list* `#',test
254
(LET ((BLOCK-TAG (GENSYM))
257
(MAPCAR #'(LAMBDA (CLAUSE)
258
(WITH-KEYWORD-PAIRS ((REPORT INTERACTIVE TEST &REST FORMS)
260
(LIST (CAR CLAUSE) ;Name=0
262
(TRANSFORM-KEYWORDS :REPORT REPORT ;Keywords=2
263
:INTERACTIVE INTERACTIVE
269
(LET ((,TEMP-VAR NIL))
272
,(MAPCAR #'(LAMBDA (DATUM)
273
(LET ((NAME (NTH 0 DATUM))
275
(KEYS (NTH 2 DATUM)))
276
`(,NAME #'(LAMBDA (&REST TEMP)
277
#+LISPM (SETQ TEMP (COPY-LIST TEMP))
278
(SETQ ,TEMP-VAR TEMP)
282
(RETURN-FROM ,BLOCK-TAG ,(munge-restart-case-expression EXPRESSION data)))
283
,@(MAPCAN #'(LAMBDA (DATUM)
284
(LET ((TAG (NTH 1 DATUM))
286
(BODY (NTH 4 DATUM)))
288
`(RETURN-FROM ,BLOCK-TAG
289
(APPLY #'(LAMBDA ,BVL ,@BODY)
293
(DEFMACRO WITH-SIMPLE-RESTART ((RESTART-NAME FORMAT-STRING
294
&REST FORMAT-ARGUMENTS)
296
`(RESTART-CASE (PROGN ,@FORMS)
298
:REPORT (LAMBDA (STREAM)
299
(FORMAT STREAM ,FORMAT-STRING ,@FORMAT-ARGUMENTS))
302
;(DEFUN ABORT (&optional condition) (INVOKE-RESTART (find-restart 'ABORT condition))
303
; (ERROR 'ABORT-FAILURE))
304
;(DEFUN CONTINUE () (INVOKE-RESTART 'CONTINUE))
305
;(DEFUN MUFFLE-WARNING () (INVOKE-RESTART 'MUFFLE-WARNING))
306
;(DEFUN STORE-VALUE (VALUE) (INVOKE-RESTART 'STORE-VALUE VALUE))
307
;(DEFUN USE-VALUE (VALUE) (INVOKE-RESTART 'USE-VALUE VALUE))
309
;;; ABORT signals an error in case there was a restart named abort that did
310
;;; not tranfer control dynamically. This could happen with RESTART-BIND.
312
(defun abort (&optional condition)
313
"Transfers control to a restart named abort, signalling a control-error if
315
(invoke-restart (find-restart 'abort condition))
316
(error 'abort-failure))
319
(defun muffle-warning (&optional condition)
320
"Transfers control to a restart named muffle-warning, signalling a
321
control-error if none exists."
322
(invoke-restart (find-restart 'muffle-warning condition)))
325
;;; DEFINE-NIL-RETURNING-RESTART finds the restart before invoking it to keep
326
;;; INVOKE-RESTART from signalling a control-error condition.
328
(defmacro define-nil-returning-restart (name args doc)
329
`(defun ,name (,@args &optional condition)
331
(if (find-restart ',name condition) (invoke-restart ',name ,@args))))
333
(define-nil-returning-restart continue ()
334
"Transfer control to a restart named continue, returning nil if none exists.")
336
(define-nil-returning-restart store-value (value)
337
"Transfer control and value to a restart named store-value, returning nil if
340
(define-nil-returning-restart use-value (value)
341
"Transfer control and value to a restart named use-value, returning nil if