Change naming convention around.
[sod] / src / parser / floc-proto.lisp
diff --git a/src/parser/floc-proto.lisp b/src/parser/floc-proto.lisp
new file mode 100644 (file)
index 0000000..9e246ab
--- /dev/null
@@ -0,0 +1,299 @@
+;;; -*-lisp-*-
+;;;
+;;; Protocol for file locations
+;;;
+;;; (c) 2009 Straylight/Edgeware
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of the Sensble Object Design, an object system for C.
+;;;
+;;; SOD is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; SOD is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with SOD; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(cl:in-package #:sod-parser)
+
+;;;--------------------------------------------------------------------------
+;;; File location objects.
+
+(export '(file-location make-file-location file-location-p
+         file-location-filename file-location-line file-location-column))
+(defstruct (file-location
+            (:constructor make-file-location
+                          (%filename line column
+                           &aux (filename
+                                 (etypecase %filename
+                                   ((or string null) %filename)
+                                   (pathname (namestring %filename)))))))
+  "A simple structure containing file location information.
+
+   Construct using MAKE-FILE-LOCATION; the main useful function is
+   ERROR-FILE-LOCATION."
+  (filename nil :type (or string null) :read-only t)
+  (line nil :type (or fixnum null) :read-only t)
+  (column nil :type (or fixnum null) :read-only t))
+
+(defgeneric file-location (thing)
+  (:documentation
+   "Convert THING into a FILE-LOCATION, if possible.
+
+   A THING which can be converted into a FILE-LOCATION is termed a
+   `file-location designator'.")
+  (:method ((thing file-location)) thing))
+
+;;;--------------------------------------------------------------------------
+;;; Enclosing conditions.
+
+(export '(enclosing-condition enclosed-condition))
+(define-condition enclosing-condition (condition)
+  ((enclosed-condition :initarg :condition :type condition
+                      :reader enclosed-condition))
+  (:documentation
+   "A condition which encloses another condition
+
+   This is useful if one wants to attach additional information to an
+   existing condition.  The enclosed condition can be obtained using the
+   ENCLOSED-CONDITION function.")
+  (:report (lambda (condition stream)
+            (princ (enclosed-condition condition) stream))))
+
+;;;--------------------------------------------------------------------------
+;;; Conditions with location information.
+
+(export 'condition-with-location)
+(define-condition condition-with-location (condition)
+  ((location :initarg :location :reader file-location :type file-location))
+  (:documentation
+   "A condition which has some location information attached."))
+
+(export 'enclosing-condition-with-location)
+(define-condition enclosing-condition-with-location
+    (condition-with-location enclosing-condition)
+  ())
+
+(export 'error-with-location)
+(define-condition error-with-location (condition-with-location error)
+  ())
+
+(export 'warning-with-location)
+(define-condition warning-with-location (condition-with-location warning)
+  ())
+
+(export 'enclosing-error-with-location)
+(define-condition enclosing-error-with-location
+    (enclosing-condition-with-location error)
+  ())
+
+(export 'enclosing-warning-with-location)
+(define-condition enclosing-warning-with-location
+    (enclosing-condition-with-location warning)
+  ())
+
+(export 'simple-condition-with-location)
+(define-condition simple-condition-with-location
+    (condition-with-location simple-condition)
+  ())
+
+(export 'simple-error-with-location)
+(define-condition simple-error-with-location
+    (error-with-location simple-error)
+  ())
+
+(export 'simple-warning-with-location)
+(define-condition simple-warning-with-location
+    (warning-with-location simple-warning)
+  ())
+
+;;;--------------------------------------------------------------------------
+;;; Reporting errors.
+
+(export 'make-condition-with-location)
+(defun make-condition-with-location (default-type floc datum &rest arguments)
+  "Construct a CONDITION-WITH-LOCATION given a condition designator.
+
+   The returned condition will always be a CONDITION-WITH-LOCATION.  The
+   process consists of two stages.  In the first stage, a condition is
+   constructed from the condition designator DATUM and ARGUMENTS with default
+   type DEFAULT-TYPE (a symbol).  The precise behaviour depends on DATUM:
+
+     * If DATUM is a condition, then it is used as-is; ARGUMENTS should be an
+       empty list.
+
+     * If DATUM is a symbol, then it must name a condition type.  An instance
+       of this class is constructed using ARGUMENTS as initargs, i.e., as
+       if (apply #'make-condition ARGUMENTS); if the type is a subtype of
+       CONDITION-WITH-LOCATION then FLOC is attached as the location.
+
+     * If DATUM is a format control (i.e., a string or function), then the
+       condition is constructed as if, instead, DEFAULT-TYPE had been
+       supplied as DATUM, and the list (:format-control DATUM
+       :format-arguments ARGUMENTS) supplied as ARGUMENTS.
+
+   In the second stage, the condition constructed by the first stage is
+   converted into a CONDITION-WITH-LOCATION.  If the condition already has
+   type CONDITION-WITH-LOCATION then it is returned as is.  Otherwise it is
+   wrapped in an appropriate subtype of ENCLOSING-CONDITION-WITH-LOCATION:
+   if the condition was a subtype of ERROR or WARNING then the resulting
+   condition will also be subtype of ERROR or WARNING as appropriate."
+
+  (labels ((wrap (condition)
+            (make-condition
+             (etypecase condition
+               (error 'enclosing-error-with-location)
+               (warning 'enclosing-warning-with-location)
+               (condition 'enclosing-condition-with-location))
+             :condition condition
+             :location (file-location floc)))
+          (make (type &rest initargs)
+            (if (subtypep type 'condition-with-location)
+                (apply #'make-condition type
+                       :location (file-location floc)
+                       initargs)
+                (wrap (apply #'make-condition type initargs)))))
+    (etypecase datum
+      (condition-with-location datum)
+      (condition (wrap datum))
+      (symbol (apply #'make arguments))
+      ((or string function) (make default-type
+                                 :format-control datum
+                                 :format-arguments arguments)))))
+
+(export 'error-with-location)
+(defun error-with-location (floc datum &rest arguments)
+  "Report an error with attached location information."
+  (error (apply #'make-condition-with-location
+               'simple-error-with-location
+               floc datum arguments)))
+
+(export 'warn-with-location)
+(defun warn-with-location (floc datum &rest arguments)
+  "Report a warning with attached location information."
+  (warn (apply #'make-condition-with-location
+              'simple-warning-with-location
+              floc datum arguments)))
+
+(export 'cerror-with-location)
+(defun cerror-with-location (floc continue-string datum &rest arguments)
+  "Report a continuable error with attached location information."
+  (cerror continue-string
+         (apply #'make-condition-with-location
+                'simple-error-with-location
+                floc datum arguments)))
+
+(export 'cerror*)
+(defun cerror* (datum &rest arguments)
+  (apply #'cerror "Continue" datum arguments))
+
+(export 'cerror*-with-location)
+(defun cerror*-with-location (floc datum &rest arguments)
+  (apply #'cerror-with-location floc "Continue" datum arguments))
+
+;;;--------------------------------------------------------------------------
+;;; Stamping errors with location information.
+
+(defun with-default-error-location* (floc thunk)
+  "Invoke THUNK in a dynamic environment which attaches FLOC to errors (and
+   other conditions) which do not have file location information attached to
+   them already.
+
+   See the WITH-DEFAULT-ERROR-LOCATION macro for more details."
+
+  (if floc
+      (handler-bind
+         ((condition-with-location
+           (lambda (condition)
+             (declare (ignore condition))
+             :decline))
+          (condition
+           (lambda (condition)
+             (signal (make-condition-with-location nil floc condition)))))
+       (funcall thunk))
+      (funcall thunk)))
+
+(export 'with-default-error-location)
+(defmacro with-default-error-location ((floc) &body body)
+  "Evaluate BODY, as an implicit progn, in a dynamic environment which
+   attaches FLOC to errors (and other conditions) which do not have file
+   location information attached to them already.
+
+   If a condition other than a CONDITION-WITH-LOCATION is signalled during
+   the evaluation of the BODY, then an instance of an appropriate subcalass
+   of ENCLOSING-CONDITION-WITH-LOCATION is constructed, enclosing the
+   original condition, and signalled.  In particular, if the original
+   condition was a subtype of ERROR or WARNING, then the new condition will
+   also be a subtype of ERROR or WARNING as appropriate.
+
+   The FLOC argument is coerced to a FILE-LOCATION object each time a
+   condition is signalled.  For example, if FLOC is a lexical analyser object
+   which reports its current position in response to FILE-LOCATION, then each
+   condition will be reported as arising at the lexer's current position at
+   that time, rather than all being reported at the same position.
+
+   If the new enclosing condition is not handled, the handler established by
+   this macro will decline to handle the original condition.  Typically,
+   however, the new condition will be handled by COUNT-AND-REPORT-ERRORS.
+
+   As a special case, if FLOC is nil, then no special action is taken, and
+   BODY is simply evaluated, as an implicit progn."
+
+  `(with-default-error-location* ,floc (lambda () ,@body)))
+
+;;;--------------------------------------------------------------------------
+;;; Front-end error reporting.
+
+(defun count-and-report-errors* (thunk)
+  "Invoke THUNK in a dynamic environment which traps and reports errors.
+
+   See the COUNT-AND-REPORT-ERRORS macro for more detais."
+
+  (let ((errors 0)
+       (warnings 0))
+    (handler-bind
+       ((error (lambda (error)
+                 (let ((fatal (not (find-restart 'continue error))))
+                   (format *error-output* "~&~A: ~:[~;Fatal error: ~]~A~%"
+                           (file-location error)
+                           fatal
+                           error)
+                   (incf errors)
+                   (if fatal
+                       (return-from count-and-report-errors*
+                         (values nil errors warnings))
+                       (invoke-restart 'continue)))))
+        (warning (lambda (warning)
+                   (format *error-output* "~&~A: Warning: ~A~%"
+                         (file-location warning)
+                         warning)
+                   (incf warnings)
+                   (invoke-restart 'muffle-warning))))
+      (values (funcall thunk)
+             errors
+             warnings))))
+
+(export 'count-and-report-errors)
+(defmacro count-and-report-errors (() &body body)
+  "Evaluate BODY in a dynamic environment which traps and reports errors.
+
+   The BODY is evaluated.  If an error or warning is signalled, it is
+   reported (using its report function), and counted.  Warnings are otherwise
+   muffled; continuable errors (i.e., when a CONTINUE restart is defined) are
+   continued; non-continuable errors cause an immediate exit from the BODY.
+
+   The final value consists of three values: the primary value of the BODY
+   (or NIL if a non-continuable error occurred), the number of errors
+   reported, and the number of warnings reported."
+  `(count-and-report-errors* (lambda () ,@body)))
+
+;;;----- That's all, folks --------------------------------------------------