e3dca325d8b28565fc0a07676a2541ecd61b66fe
[sod] / src / parser / floc-proto.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; Protocol for file locations
4 ;;;
5 ;;; (c) 2009 Straylight/Edgeware
6 ;;;
7
8 ;;;----- Licensing notice ---------------------------------------------------
9 ;;;
10 ;;; This file is part of the Sensible Object Design, an object system for C.
11 ;;;
12 ;;; SOD is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
16 ;;;
17 ;;; SOD is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;;; GNU General Public License for more details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with SOD; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 (cl:in-package #:sod-parser)
27
28 ;;;--------------------------------------------------------------------------
29 ;;; File location objects.
30
31 (export '(file-location make-file-location file-location-p
32 file-location-filename file-location-line file-location-column))
33 (defstruct (file-location
34 (:constructor make-file-location
35 (%filename &optional line column
36 &aux (filename
37 (etypecase %filename
38 ((or string null) %filename)
39 (pathname (namestring %filename)))))))
40 "A simple structure containing file location information.
41
42 Construct using `make-file-location'; the main useful function is
43 `error-file-location'."
44 (filename nil :type (or string null) :read-only t)
45 (line nil :type (or fixnum null) :read-only t)
46 (column nil :type (or fixnum null) :read-only t))
47
48 (defgeneric file-location (thing)
49 (:documentation
50 "Convert THING into a `file-location', if possible.
51
52 A THING which can be converted into a `file-location' is termed a
53 `file-location designator'.")
54 (:method ((thing file-location)) thing))
55
56 ;;;--------------------------------------------------------------------------
57 ;;; Enclosing conditions.
58
59 (export '(enclosing-condition enclosed-condition))
60 (define-condition enclosing-condition (condition)
61 ((%enclosed-condition :initarg :condition :type condition
62 :reader enclosed-condition))
63 (:documentation
64 "A condition which encloses another condition
65
66 This is useful if one wants to attach additional information to an
67 existing condition. The enclosed condition can be obtained using the
68 `enclosed-condition' function.")
69 (:report (lambda (condition stream)
70 (princ (enclosed-condition condition) stream))))
71
72 ;;;--------------------------------------------------------------------------
73 ;;; Conditions with location information.
74
75 (export 'condition-with-location)
76 (define-condition condition-with-location (condition)
77 ((location :initarg :location :reader file-location :type file-location))
78 (:documentation
79 "A condition which has some location information attached."))
80
81 (export 'enclosing-condition-with-location)
82 (define-condition enclosing-condition-with-location
83 (condition-with-location enclosing-condition)
84 ())
85
86 (export 'error-with-location)
87 (define-condition error-with-location (condition-with-location error)
88 ())
89
90 (export 'warning-with-location)
91 (define-condition warning-with-location (condition-with-location warning)
92 ())
93
94 (export 'enclosing-error-with-location)
95 (define-condition enclosing-error-with-location
96 (enclosing-condition-with-location error)
97 ())
98
99 (export 'enclosing-warning-with-location)
100 (define-condition enclosing-warning-with-location
101 (enclosing-condition-with-location warning)
102 ())
103
104 (export 'simple-condition-with-location)
105 (define-condition simple-condition-with-location
106 (condition-with-location simple-condition)
107 ())
108
109 (export 'simple-error-with-location)
110 (define-condition simple-error-with-location
111 (error-with-location simple-error)
112 ())
113
114 (export 'simple-warning-with-location)
115 (define-condition simple-warning-with-location
116 (warning-with-location simple-warning)
117 ())
118
119 ;;;--------------------------------------------------------------------------
120 ;;; Reporting errors.
121
122 (export 'enclosing-condition-with-location-type)
123 (defgeneric enclosing-condition-with-location-type (condition)
124 (:documentation
125 "Return a class suitable for attaching location information to CONDITION.
126
127 Specifically, return the name of a subclass of `enclosing-condition-
128 with-location' suitable to enclose CONDITION.")
129 (:method ((condition error)) 'enclosing-error-with-location)
130 (:method ((condition warning)) 'enclosing-warning-with-location)
131 (:method ((condition condition)) 'enclosing-condition-with-location))
132
133 (export 'make-condition-with-location)
134 (defun make-condition-with-location (default-type floc datum &rest arguments)
135 "Construct a `condition-with-location' given a condition designator.
136
137 The returned condition will always be a `condition-with-location'. The
138 process consists of two stages. In the first stage, a condition is
139 constructed from the condition designator DATUM and ARGUMENTS with default
140 type DEFAULT-TYPE (a symbol). The precise behaviour depends on DATUM:
141
142 * If DATUM is a condition, then it is used as-is; ARGUMENTS should be an
143 empty list.
144
145 * If DATUM is a symbol, then it must name a condition type. An instance
146 of this class is constructed using ARGUMENTS as initargs, i.e., as
147 if (apply #'make-condition ARGUMENTS); if the type is a subtype of
148 `condition-with-location' then FLOC is attached as the location.
149
150 * If DATUM is a format control (i.e., a string or function), then the
151 condition is constructed as if, instead, DEFAULT-TYPE had been
152 supplied as DATUM, and the list (:format-control DATUM
153 :format-arguments ARGUMENTS) supplied as ARGUMENTS.
154
155 In the second stage, the condition constructed by the first stage is
156 converted into a `condition-with-location'. If the condition already has
157 type `condition-with-location' then it is returned as is. Otherwise it is
158 wrapped in an appropriate subtype of `enclosing-condition-with-location':
159 if the condition was a subtype of ERROR or WARNING then the resulting
160 condition will also be subtype of ERROR or WARNING as appropriate."
161
162 (labels ((check-no-args ()
163 (unless (null arguments)
164 (error "Argument list provided with specific condition")))
165 (wrap (condition)
166 (make-condition
167 (enclosing-condition-with-location-type condition)
168 :condition condition
169 :location (file-location floc)))
170 (make (type &rest initargs)
171 (if (subtypep type 'condition-with-location)
172 (apply #'make-condition type
173 :location (file-location floc)
174 initargs)
175 (wrap (apply #'make-condition type initargs)))))
176 (typecase datum
177 (condition-with-location (check-no-args) datum)
178 (condition (check-no-args) (wrap datum))
179 (symbol (apply #'make datum arguments))
180 ((or string function) (make default-type
181 :format-control datum
182 :format-arguments arguments))
183 (t (error "Unexpected condition designator datum ~S" datum)))))
184
185 (export 'error-with-location)
186 (defun error-with-location (floc datum &rest arguments)
187 "Report an error with attached location information."
188 (error (apply #'make-condition-with-location
189 'simple-error-with-location
190 floc datum arguments)))
191
192 (export 'warn-with-location)
193 (defun warn-with-location (floc datum &rest arguments)
194 "Report a warning with attached location information."
195 (warn (apply #'make-condition-with-location
196 'simple-warning-with-location
197 floc datum arguments)))
198
199 (defun my-cerror (continue-string datum &rest arguments)
200 "Like standard `cerror', but robust against sneaky changes of conditions.
201
202 It seems that `cerror' (well, at least the version in SBCL) is careful
203 to limit its restart to the specific condition it signalled. But that's
204 annoying, because `with-default-error-location' substitutes different
205 conditions carrying the error-location information."
206 (restart-case (apply #'error datum arguments)
207 (continue ()
208 :report (lambda (stream)
209 (apply #'format stream continue-string datum arguments))
210 nil)))
211
212 (export 'cerror-with-location)
213 (defun cerror-with-location (floc continue-string datum &rest arguments)
214 "Report a continuable error with attached location information."
215 (my-cerror continue-string
216 (apply #'make-condition-with-location
217 'simple-error-with-location
218 floc datum arguments)))
219
220 (export 'cerror*)
221 (defun cerror* (datum &rest arguments)
222 (apply #'my-cerror "Continue" datum arguments))
223
224 (export 'cerror*-with-location)
225 (defun cerror*-with-location (floc datum &rest arguments)
226 (apply #'cerror-with-location floc "Continue" datum arguments))
227
228 ;;;--------------------------------------------------------------------------
229 ;;; Stamping errors with location information.
230
231 (defun with-default-error-location* (floc thunk)
232 "Invoke THUNK in a dynamic environment which attaches FLOC to errors (and
233 other conditions) which do not have file location information attached to
234 them already.
235
236 See the `with-default-error-location' macro for more details."
237
238 (if floc
239 (handler-bind
240 ((condition-with-location
241 (lambda (condition)
242 (declare (ignore condition))
243 :decline))
244 (condition
245 (lambda (condition)
246 (signal (make-condition-with-location nil floc condition)))))
247 (funcall thunk))
248 (funcall thunk)))
249
250 (export 'with-default-error-location)
251 (defmacro with-default-error-location ((floc) &body body)
252 "Evaluate BODY, as an implicit progn, in a dynamic environment which
253 attaches FLOC to errors (and other conditions) which do not have file
254 location information attached to them already.
255
256 If a condition other than a `condition-with-location' is signalled during
257 the evaluation of the BODY, then an instance of an appropriate subcalass
258 of `enclosing-condition-with-location' is constructed, enclosing the
259 original condition, and signalled. In particular, if the original
260 condition was a subtype of ERROR or WARNING, then the new condition will
261 also be a subtype of ERROR or WARNING as appropriate.
262
263 The FLOC argument is coerced to a `file-location' object each time a
264 condition is signalled. For example, if FLOC is a lexical analyser object
265 which reports its current position in response to `file-location', then
266 each condition will be reported as arising at the lexer's current position
267 at that time, rather than all being reported at the same position.
268
269 If the new enclosing condition is not handled, the handler established by
270 this macro will decline to handle the original condition. Typically,
271 however, the new condition will be handled by `count-and-report-errors'.
272
273 As a special case, if FLOC is nil, then no special action is taken, and
274 BODY is simply evaluated, as an implicit progn."
275
276 `(with-default-error-location* ,floc (lambda () ,@body)))
277
278 ;;;--------------------------------------------------------------------------
279 ;;; Front-end error reporting.
280
281 (defun count-and-report-errors* (thunk)
282 "Invoke THUNK in a dynamic environment which traps and reports errors.
283
284 See the `count-and-report-errors' macro for more details."
285
286 (let ((errors 0)
287 (warnings 0))
288 (restart-case
289 (let ((our-continue-restart (find-restart 'continue)))
290 (handler-bind
291 ((error (lambda (error)
292 (let ((fatal (eq (find-restart 'continue error)
293 our-continue-restart)))
294 (format *error-output*
295 "~&~A: ~:[~;Fatal error: ~]~A~%"
296 (file-location error)
297 fatal
298 error)
299 (incf errors)
300 (if fatal
301 (return-from count-and-report-errors*
302 (values nil errors warnings))
303 (invoke-restart 'continue)))))
304 (warning (lambda (warning)
305 (format *error-output* "~&~A: Warning: ~A~%"
306 (file-location warning)
307 warning)
308 (incf warnings)
309 (invoke-restart 'muffle-warning))))
310 (values (funcall thunk)
311 errors
312 warnings)))
313 (continue ()
314 :report (lambda (stream) (write-string "Exit to top-level" stream))
315 (values nil errors warnings)))))
316
317 (export 'count-and-report-errors)
318 (defmacro count-and-report-errors (() &body body)
319 "Evaluate BODY in a dynamic environment which traps and reports errors.
320
321 The BODY is evaluated. If an error or warning is signalled, it is
322 reported (using its report function), and counted. Warnings are otherwise
323 muffled; continuable errors (i.e., when a `continue' restart is defined)
324 are continued; non-continuable errors cause an immediate exit from the
325 BODY.
326
327 The final value consists of three values: the primary value of the BODY
328 (or nil if a non-continuable error occurred), the number of errors
329 reported, and the number of warnings reported."
330 `(count-and-report-errors* (lambda () ,@body)))
331
332 ;;;----- That's all, folks --------------------------------------------------