;;; -*-lisp-*- ;;; ;;; Error types and handling utilities ;;; ;;; (c) 2009 Straylight/Edgeware ;;; ;;;----- Licensing notice --------------------------------------------------- ;;; ;;; This file is part of the Simple Object Definition system. ;;; ;;; 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) ;;;-------------------------------------------------------------------------- ;;; Enclosing conditions. (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. (define-condition condition-with-location (condition) ((location :initarg :location :reader file-location :type file-location)) (:documentation "A condition which has some location information attached.")) (define-condition enclosing-condition-with-location (condition-with-location enclosing-condition) ()) (define-condition error-with-location (condition-with-location error) ()) (define-condition warning-with-location (condition-with-location warning) ()) (define-condition enclosing-error-with-location (enclosing-condition-with-location error) ()) (define-condition enclosing-warning-with-location (enclosing-condition-with-location warning) ()) (define-condition simple-condition-with-location (condition-with-location simple-condition) ()) (define-condition simple-error-with-location (error-with-location simple-error) ()) (define-condition simple-warning-with-location (warning-with-location simple-warning) ()) ;;;-------------------------------------------------------------------------- ;;; Error reporting functions. (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))))) (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))) (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))) (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))) (defun cerror* (datum &rest arguments) (apply #'cerror "Continue" datum arguments)) (defun cerror*-with-location (floc datum &rest arguments) (apply #'cerror-with-location floc "Continue" datum arguments)) (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)))) (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))) (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))) (defmacro with-default-error-location ((floc) &body body) "Evaluate BODY 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 subtype of ENCLOSING-CONDITION-WITH-LOCATION is constructed, enclosing the original condition, and signalled. 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." `(with-default-error-location* ,floc (lambda () ,@body))) ;;;----- That's all, folks --------------------------------------------------