annotate src/clojure/contrib/error_kit.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
rlm@10 1 ; Copyright (c) Chris Houser, Jan 2009. All rights reserved.
rlm@10 2 ; The use and distribution terms for this software are covered by the
rlm@10 3 ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 4 ; which can be found in the file epl-v10.html at the root of this distribution.
rlm@10 5 ; By using this software in any fashion, you are agreeing to be bound by
rlm@10 6 ; the terms of this license.
rlm@10 7 ; You must not remove this notice, or any other, from this software.
rlm@10 8
rlm@10 9 ; == EXPERIMENTAL ==
rlm@10 10 ; System for defining and using custom errors
rlm@10 11 ; Please contact Chouser if you have any suggestions for better names
rlm@10 12 ; or API adjustments.
rlm@10 13
rlm@10 14 (ns
rlm@10 15 ^{:author "Chris Houser",
rlm@10 16 :doc "EXPERIMENTAL
rlm@10 17 System for defining and using custom errors
rlm@10 18 Please contact Chouser if you have any suggestions for better names
rlm@10 19 or API adjustments."}
rlm@10 20 clojure.contrib.error-kit
rlm@10 21 (:use [clojure.contrib.def :only (defvar defvar-)]
rlm@10 22 [clojure.stacktrace :only (root-cause)]))
rlm@10 23
rlm@10 24 (defn- make-ctrl-exception [msg data]
rlm@10 25 "Create an exception object with associated data, used for passing
rlm@10 26 control and data to a dynamically containing handler."
rlm@10 27 (proxy [Error clojure.lang.IDeref] [msg]
rlm@10 28 (toString [] (str "Error Kit Control Exception: " msg ", " (pr-str data)))
rlm@10 29 (deref [] data)))
rlm@10 30
rlm@10 31 (defvar- ctrl-exception-class
rlm@10 32 (class (make-ctrl-exception nil nil)))
rlm@10 33
rlm@10 34 (defvar- *handler-stack* () "Stack of bound handler symbols")
rlm@10 35
rlm@10 36 (defvar- *continues* {} "Map of currently available continue forms")
rlm@10 37
rlm@10 38
rlm@10 39 (defmacro throw-msg
rlm@10 40 "Returns a function that throws a Java Exception with the given
rlm@10 41 name. Useful to associate a new error-kit error type with a
rlm@10 42 particular Java Exception class, via the :unhandled error key."
rlm@10 43 [class-name]
rlm@10 44 `(fn [x#] (throw (new ~class-name (:msg x#)))))
rlm@10 45
rlm@10 46 (defn error
rlm@10 47 "Base type for all error-kit errors"
rlm@10 48 {::args [:msg :unhandled :tag]}
rlm@10 49 [details]
rlm@10 50 (merge {:tag `error :msg "exception via error-kit"
rlm@10 51 :unhandled (throw-msg Exception)}
rlm@10 52 details))
rlm@10 53
rlm@10 54 (defn- qualify-sym [sym]
rlm@10 55 (let [v (resolve sym)]
rlm@10 56 (assert v)
rlm@10 57 (apply symbol (map #(str (% (meta v))) [:ns :name]))))
rlm@10 58
rlm@10 59 (defmacro deferror
rlm@10 60 "Define a new error type"
rlm@10 61 {:arglists '([name [parent-error?] doc-string? [args*] & body]
rlm@10 62 [name [parent-error?] doc-string? args-destruct-map & body])}
rlm@10 63 [err-name pvec & decl]
rlm@10 64 (let [pvec (if (empty? pvec) [`error] pvec)
rlm@10 65 [docstr args & body] (if (string? (first decl)) decl (cons nil decl))
rlm@10 66 args (or args [])
rlm@10 67 argmap (if (vector? args) `{:keys ~args} args)
rlm@10 68 body (or body {})
rlm@10 69 qual-err-name (symbol (str *ns*) (name err-name))]
rlm@10 70 (assert (== (count pvec) 1)) ; only support single-inheritance for now
rlm@10 71 (assert (vector? args)) ; only vector (keyword destruct) args for now
rlm@10 72 `(do
rlm@10 73 (defn ~err-name [details#]
rlm@10 74 (let [basedata# ((resolve (first (parents '~qual-err-name))) details#)
rlm@10 75 ~argmap basedata#]
rlm@10 76 (merge basedata# {:tag '~qual-err-name} (do ~@body) details#)))
rlm@10 77 (alter-meta! (var ~err-name) assoc
rlm@10 78 :doc ~docstr ::args ~(vec (map #(keyword (str %)) args)))
rlm@10 79 ~@(for [parent pvec]
rlm@10 80 `(derive '~qual-err-name '~(qualify-sym parent)))
rlm@10 81 (var ~err-name))))
rlm@10 82
rlm@10 83 (defn- throw-to [msg target-map args]
rlm@10 84 (throw (make-ctrl-exception msg (assoc target-map :args args))))
rlm@10 85
rlm@10 86 (defn raise*
rlm@10 87 "Raise the given error object, best if created by an error
rlm@10 88 constructor defined with deferror. See also 'raise' macro."
rlm@10 89 [err]
rlm@10 90 (let [err-tag (:tag err)]
rlm@10 91 (loop [hs *handler-stack*]
rlm@10 92 (if (empty? hs)
rlm@10 93 ((:unhandled err) err)
rlm@10 94 (let [[{:keys [htag] :as handler}] hs]
rlm@10 95 (if (and htag (not (isa? err-tag htag)))
rlm@10 96 (recur (next hs))
rlm@10 97 (let [rtn ((:hfunc handler) err)]
rlm@10 98 (if-not (vector? rtn)
rlm@10 99 (throw-to "default" handler (list rtn))
rlm@10 100 (condp = (rtn 0)
rlm@10 101 ::continue-with (rtn 1)
rlm@10 102 ::continue (if-let [continue (*continues* (rtn 1))]
rlm@10 103 (throw-to "continue" continue (rtn 2))
rlm@10 104 (do (prn *continues*) (throw
rlm@10 105 (Exception.
rlm@10 106 (str "Unbound continue name " (rtn 1))))))
rlm@10 107 ::do-not-handle (recur (next hs))
rlm@10 108 (throw-to "do-not-handle" handler (list rtn)))))))))))
rlm@10 109
rlm@10 110 (defmacro raise
rlm@10 111 "Raise an error of the type err-name, constructed with the given args"
rlm@10 112 [err-name & args]
rlm@10 113 `(raise* (~err-name ~(zipmap (::args (meta (resolve err-name)))
rlm@10 114 args))))
rlm@10 115
rlm@10 116 ; It'd be nice to assert that these are used in a tail position of a handler
rlm@10 117 (defmacro do-not-handle
rlm@10 118 "Use in a tail position of a 'handle' form to indicate 'raise' should
rlm@10 119 not consider the error handled, but should continue searching for an
rlm@10 120 appropriate 'handle' form. Allows finer-grain control over catching
rlm@10 121 than just the error type."
rlm@10 122 []
rlm@10 123 `[::do-not-handle])
rlm@10 124
rlm@10 125 (defmacro continue-with [value]
rlm@10 126 "Use in a tail position of a 'handle' form to cause the currently
rlm@10 127 running 'raise' to return the given 'value'."
rlm@10 128 `[::continue-with ~value])
rlm@10 129
rlm@10 130 (defmacro continue [continue-name & args]
rlm@10 131 "Use in a tail position of a 'handle' form to pass control to the
rlm@10 132 named 'continue' form, passing in the given args. The 'continue'
rlm@10 133 form with the given name and the smallest dynamic scope surrounding
rlm@10 134 the currently running 'raise' will be used."
rlm@10 135 `[::continue '~continue-name [~@args]])
rlm@10 136
rlm@10 137
rlm@10 138 (def ^{:doc "Special form to be used inside a 'with-handler'. When
rlm@10 139 any error is 'raised' from withing the dynamic scope of 'body' that
rlm@10 140 is of error-name's type or a derived type, the args will be bound
rlm@10 141 and the body executed. If no 'error-name' is given, the body will
rlm@10 142 be executed for regardless of the type of error raised. The body
rlm@10 143 may return a value, in which case that will be the return value of
rlm@10 144 the entire 'with-handler' form, or it may use any of the special
rlm@10 145 return forms, 'do-not-handle', 'continue-with', or 'continue'."
rlm@10 146 :arglists '([error-name? [args*] & body]
rlm@10 147 [error-name? args-destruct-map-args & body])}
rlm@10 148 handle)
rlm@10 149
rlm@10 150 (def ^{:doc "Special form to be used inside a 'with-handler'.
rlm@10 151 Control can be passed to this 'continue' form from a 'raise' enclosed
rlm@10 152 in this with-handler's dynamic scope, when this 'continue-name' is
rlm@10 153 given to a 'continue' form."
rlm@10 154 :arglists '([continue-name [args*] & body])}
rlm@10 155 bind-continue)
rlm@10 156
rlm@10 157 (defn- special-form [form]
rlm@10 158 (and (list form)
rlm@10 159 (symbol? (first form))
rlm@10 160 (#{#'handle #'bind-continue} (resolve (first form)))))
rlm@10 161
rlm@10 162
rlm@10 163 (defmacro with-handler
rlm@10 164 "This is error-kit's dynamic scope form. The body will be executed
rlm@10 165 in a dynamic context that includes all of the following 'handle' and
rlm@10 166 'bind-continue' forms."
rlm@10 167 [& forms]
rlm@10 168 (let [[body special-forms] (split-with (complement special-form) forms)]
rlm@10 169 (assert (every? special-form special-forms))
rlm@10 170 (let [blockid (gensym)
rlm@10 171 handlers (for [[type & more] special-forms
rlm@10 172 :when (= (resolve type) #'handle)]
rlm@10 173 (let [[htag args & hbody] (if (symbol? (first more))
rlm@10 174 more
rlm@10 175 (cons nil more))
rlm@10 176 argmap (if (vector? args) `{:keys ~args} args)]
rlm@10 177 `{:blockid '~blockid
rlm@10 178 :htag ~(when htag (list `quote (qualify-sym htag)))
rlm@10 179 :hfunc (fn [~argmap] ~@hbody)
rlm@10 180 :rfunc identity}))
rlm@10 181 continues (into {}
rlm@10 182 (for [[type & more] special-forms
rlm@10 183 :when (= (resolve type) #'bind-continue)]
rlm@10 184 [(list `quote (first more))
rlm@10 185 `{:blockid '~blockid
rlm@10 186 :rfunc (fn ~@(next more))}]))]
rlm@10 187 `(try
rlm@10 188 (binding [*handler-stack* (list* ~@handlers @#'*handler-stack*)
rlm@10 189 *continues* (merge @#'*continues* ~@continues)]
rlm@10 190 ~@body)
rlm@10 191 (catch Throwable e#
rlm@10 192 (let [root-cause# (root-cause e#)]
rlm@10 193 (if-not (instance? @#'ctrl-exception-class root-cause#)
rlm@10 194 (throw e#)
rlm@10 195 (let [data# @root-cause#]
rlm@10 196 (if (= '~blockid (:blockid data#))
rlm@10 197 (apply (:rfunc data#) (:args data#))
rlm@10 198 (throw e#))))))))))
rlm@10 199
rlm@10 200 (defn rebind-fn [func]
rlm@10 201 (let [a *handler-stack*, b *continues*]
rlm@10 202 (fn [& args]
rlm@10 203 (binding [*handler-stack* a
rlm@10 204 *continues* b]
rlm@10 205 (apply func args)))))
rlm@10 206
rlm@10 207 (comment
rlm@10 208
rlm@10 209 (alias 'kit 'clojure.contrib.error-kit)
rlm@10 210
rlm@10 211 ; This defines an error and its action if unhandled. A good choice of
rlm@10 212 ; unhandled. action is to throw a Java exception so users of your code
rlm@10 213 ; who do not want to use error-kit can still use normal Java try/catch
rlm@10 214 ; forms to handle the error.
rlm@10 215 (kit/deferror number-error [] [n]
rlm@10 216 {:msg (str "Number error: " n)
rlm@10 217 :unhandled (kit/throw-msg NumberFormatException)})
rlm@10 218
rlm@10 219 (kit/deferror odd-number-error [number-error]
rlm@10 220 "Indicates an odd number was given to an operation that is only
rlm@10 221 defined for even numbers."
rlm@10 222 [n]
rlm@10 223 {:msg (str "Can't handle odd number: " n)})
rlm@10 224
rlm@10 225 ; Raise an error by name with any extra args defined by the deferror
rlm@10 226 (defn int-half [i]
rlm@10 227 (if (even? i)
rlm@10 228 (quot i 2)
rlm@10 229 (kit/raise odd-number-error i)))
rlm@10 230
rlm@10 231 ; Throws Java NumberFormatException because there's no 'handle' form
rlm@10 232 (vec (map int-half [2 4 5 8]))
rlm@10 233
rlm@10 234 ; Throws Java Exception with details provided by 'raise'
rlm@10 235 (kit/with-handler
rlm@10 236 (vec (map int-half [2 4 5 8]))
rlm@10 237 (kit/handle odd-number-error [n]
rlm@10 238 (throw (Exception. (format "Odd number %d in vector." n)))))
rlm@10 239
rlm@10 240 ; The above is equivalent to the more complicated version below:
rlm@10 241 (kit/with-handler
rlm@10 242 (vec (map int-half [2 4 5 8]))
rlm@10 243 (kit/handle {:keys [n tag]}
rlm@10 244 (if (isa? tag `odd-number-error)
rlm@10 245 (throw (Exception. (format "Odd number %d in vector." n)))
rlm@10 246 (kit/do-not-handle))))
rlm@10 247
rlm@10 248 ; Returns "invalid" string instead of a vector when an error is encountered
rlm@10 249 (kit/with-handler
rlm@10 250 (vec (map int-half [2 4 5 8]))
rlm@10 251 (kit/handle kit/error [n]
rlm@10 252 "invalid"))
rlm@10 253
rlm@10 254 ; Inserts a zero into the returned vector where there was an error, in
rlm@10 255 ; this case [1 2 0 4]
rlm@10 256 (kit/with-handler
rlm@10 257 (vec (map int-half [2 4 5 8]))
rlm@10 258 (kit/handle number-error [n]
rlm@10 259 (kit/continue-with 0)))
rlm@10 260
rlm@10 261 ; Intermediate continue: [1 2 :oops 5 4]
rlm@10 262 (defn int-half-vec [s]
rlm@10 263 (reduce (fn [v i]
rlm@10 264 (kit/with-handler
rlm@10 265 (conj v (int-half i))
rlm@10 266 (kit/bind-continue instead-of-half [& instead-seq]
rlm@10 267 (apply conj v instead-seq))))
rlm@10 268 [] s))
rlm@10 269
rlm@10 270 (kit/with-handler
rlm@10 271 (int-half-vec [2 4 5 8])
rlm@10 272 (kit/handle number-error [n]
rlm@10 273 (kit/continue instead-of-half :oops n)))
rlm@10 274
rlm@10 275 ; Notes:
rlm@10 276
rlm@10 277 ; It seems likely you'd want to convert a handle clause to
rlm@10 278 ; bind-continue, since it would allow higher forms to request what you
rlm@10 279 ; used to do by default. Thus both should appear in the same
rlm@10 280 ; with-handler form
rlm@10 281
rlm@10 282 ; Should continue-names be namespace qualified, and therefore require
rlm@10 283 ; pre-definition in some namespace?
rlm@10 284 ; (kit/defcontinue skip-thing "docstring")
rlm@10 285
rlm@10 286 ; Could add 'catch' for Java Exceptions and 'finally' support to
rlm@10 287 ; with-handler forms.
rlm@10 288
rlm@10 289 )