chiark / gitweb /
Work in progress, recovered from old crybaby.
[sod] / src / parser / proto-floc.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 Sensble 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 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 'make-condition-with-location)
123 (defun make-condition-with-location (default-type floc datum &rest arguments)
124   "Construct a CONDITION-WITH-LOCATION given a condition designator.
125
126    The returned condition will always be a CONDITION-WITH-LOCATION.  The
127    process consists of two stages.  In the first stage, a condition is
128    constructed from the condition designator DATUM and ARGUMENTS with default
129    type DEFAULT-TYPE (a symbol).  The precise behaviour depends on DATUM:
130
131      * If DATUM is a condition, then it is used as-is; ARGUMENTS should be an
132        empty list.
133
134      * If DATUM is a symbol, then it must name a condition type.  An instance
135        of this class is constructed using ARGUMENTS as initargs, i.e., as
136        if (apply #'make-condition ARGUMENTS); if the type is a subtype of
137        CONDITION-WITH-LOCATION then FLOC is attached as the location.
138
139      * If DATUM is a format control (i.e., a string or function), then the
140        condition is constructed as if, instead, DEFAULT-TYPE had been
141        supplied as DATUM, and the list (:format-control DATUM
142        :format-arguments ARGUMENTS) supplied as ARGUMENTS.
143
144    In the second stage, the condition constructed by the first stage is
145    converted into a CONDITION-WITH-LOCATION.  If the condition already has
146    type CONDITION-WITH-LOCATION then it is returned as is.  Otherwise it is
147    wrapped in an appropriate subtype of ENCLOSING-CONDITION-WITH-LOCATION:
148    if the condition was a subtype of ERROR or WARNING then the resulting
149    condition will also be subtype of ERROR or WARNING as appropriate."
150
151   (labels ((wrap (condition)
152              (make-condition
153               (etypecase condition
154                 (error 'enclosing-error-with-location)
155                 (warning 'enclosing-warning-with-location)
156                 (condition 'enclosing-condition-with-location))
157               :condition condition
158               :location (file-location floc)))
159            (make (type &rest initargs)
160              (if (subtypep type 'condition-with-location)
161                  (apply #'make-condition type
162                         :location (file-location floc)
163                         initargs)
164                  (wrap (apply #'make-condition type initargs)))))
165     (etypecase datum
166       (condition-with-location datum)
167       (condition (wrap datum))
168       (symbol (apply #'make arguments))
169       ((or string function) (make default-type
170                                   :format-control datum
171                                   :format-arguments arguments)))))
172
173 (export 'error-with-location)
174 (defun error-with-location (floc datum &rest arguments)
175   "Report an error with attached location information."
176   (error (apply #'make-condition-with-location
177                 'simple-error-with-location
178                 floc datum arguments)))
179
180 (export 'warn-with-location)
181 (defun warn-with-location (floc datum &rest arguments)
182   "Report a warning with attached location information."
183   (warn (apply #'make-condition-with-location
184                'simple-warning-with-location
185                floc datum arguments)))
186
187 (export 'cerror-with-location)
188 (defun cerror-with-location (floc continue-string datum &rest arguments)
189   "Report a continuable error with attached location information."
190   (cerror continue-string
191           (apply #'make-condition-with-location
192                  'simple-error-with-location
193                  floc datum arguments)))
194
195 (export 'cerror*)
196 (defun cerror* (datum &rest arguments)
197   (apply #'cerror "Continue" datum arguments))
198
199 (export 'cerror*-with-location)
200 (defun cerror*-with-location (floc datum &rest arguments)
201   (apply #'cerror-with-location floc "Continue" datum arguments))
202
203 ;;;--------------------------------------------------------------------------
204 ;;; Stamping errors with location information.
205
206 (defun with-default-error-location* (floc thunk)
207   "Invoke THUNK in a dynamic environment which attaches FLOC to errors (and
208    other conditions) which do not have file location information attached to
209    them already.
210
211    See the WITH-DEFAULT-ERROR-LOCATION macro for more details."
212
213   (if floc
214       (handler-bind
215           ((condition-with-location
216             (lambda (condition)
217               (declare (ignore condition))
218               :decline))
219            (condition
220             (lambda (condition)
221               (signal (make-condition-with-location nil floc condition)))))
222         (funcall thunk))
223       (funcall thunk)))
224
225 (export 'with-default-error-location)
226 (defmacro with-default-error-location ((floc) &body body)
227   "Evaluate BODY, as an implicit progn, in a dynamic environment which
228    attaches FLOC to errors (and other conditions) which do not have file
229    location information attached to them already.
230
231    If a condition other than a CONDITION-WITH-LOCATION is signalled during
232    the evaluation of the BODY, then an instance of an appropriate subcalass
233    of ENCLOSING-CONDITION-WITH-LOCATION is constructed, enclosing the
234    original condition, and signalled.  In particular, if the original
235    condition was a subtype of ERROR or WARNING, then the new condition will
236    also be a subtype of ERROR or WARNING as appropriate.
237
238    The FLOC argument is coerced to a FILE-LOCATION object each time a
239    condition is signalled.  For example, if FLOC is a lexical analyser object
240    which reports its current position in response to FILE-LOCATION, then each
241    condition will be reported as arising at the lexer's current position at
242    that time, rather than all being reported at the same position.
243
244    If the new enclosing condition is not handled, the handler established by
245    this macro will decline to handle the original condition.  Typically,
246    however, the new condition will be handled by COUNT-AND-REPORT-ERRORS.
247
248    As a special case, if FLOC is nil, then no special action is taken, and
249    BODY is simply evaluated, as an implicit progn."
250
251   `(with-default-error-location* ,floc (lambda () ,@body)))
252
253 ;;;--------------------------------------------------------------------------
254 ;;; Front-end error reporting.
255
256 (defun count-and-report-errors* (thunk)
257   "Invoke THUNK in a dynamic environment which traps and reports errors.
258
259    See the COUNT-AND-REPORT-ERRORS macro for more detais."
260
261   (let ((errors 0)
262         (warnings 0))
263     (handler-bind
264         ((error (lambda (error)
265                   (let ((fatal (not (find-restart 'continue error))))
266                     (format *error-output* "~&~A: ~:[~;Fatal error: ~]~A~%"
267                             (file-location error)
268                             fatal
269                             error)
270                     (incf errors)
271                     (if fatal
272                         (return-from count-and-report-errors*
273                           (values nil errors warnings))
274                         (invoke-restart 'continue)))))
275          (warning (lambda (warning)
276                     (format *error-output* "~&~A: Warning: ~A~%"
277                           (file-location warning)
278                           warning)
279                     (incf warnings)
280                     (invoke-restart 'muffle-warning))))
281       (values (funcall thunk)
282               errors
283               warnings))))
284
285 (export 'count-and-report-errors)
286 (defmacro count-and-report-errors (() &body body)
287   "Evaluate BODY in a dynamic environment which traps and reports errors.
288
289    The BODY is evaluated.  If an error or warning is signalled, it is
290    reported (using its report function), and counted.  Warnings are otherwise
291    muffled; continuable errors (i.e., when a CONTINUE restart is defined) are
292    continued; non-continuable errors cause an immediate exit from the BODY.
293
294    The final value consists of three values: the primary value of the BODY
295    (or NIL if a non-continuable error occurred), the number of errors
296    reported, and the number of warnings reported."
297   `(count-and-report-errors* (lambda () ,@body)))
298
299 ;;;----- That's all, folks --------------------------------------------------