Mercurial > lasercutter
diff 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 |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/contrib/error_kit.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,289 @@ 1.4 +; Copyright (c) Chris Houser, Jan 2009. All rights reserved. 1.5 +; The use and distribution terms for this software are covered by the 1.6 +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 1.7 +; which can be found in the file epl-v10.html at the root of this distribution. 1.8 +; By using this software in any fashion, you are agreeing to be bound by 1.9 +; the terms of this license. 1.10 +; You must not remove this notice, or any other, from this software. 1.11 + 1.12 +; == EXPERIMENTAL == 1.13 +; System for defining and using custom errors 1.14 +; Please contact Chouser if you have any suggestions for better names 1.15 +; or API adjustments. 1.16 + 1.17 +(ns 1.18 + ^{:author "Chris Houser", 1.19 + :doc "EXPERIMENTAL 1.20 +System for defining and using custom errors 1.21 +Please contact Chouser if you have any suggestions for better names 1.22 +or API adjustments."} 1.23 + clojure.contrib.error-kit 1.24 + (:use [clojure.contrib.def :only (defvar defvar-)] 1.25 + [clojure.stacktrace :only (root-cause)])) 1.26 + 1.27 +(defn- make-ctrl-exception [msg data] 1.28 + "Create an exception object with associated data, used for passing 1.29 + control and data to a dynamically containing handler." 1.30 + (proxy [Error clojure.lang.IDeref] [msg] 1.31 + (toString [] (str "Error Kit Control Exception: " msg ", " (pr-str data))) 1.32 + (deref [] data))) 1.33 + 1.34 +(defvar- ctrl-exception-class 1.35 + (class (make-ctrl-exception nil nil))) 1.36 + 1.37 +(defvar- *handler-stack* () "Stack of bound handler symbols") 1.38 + 1.39 +(defvar- *continues* {} "Map of currently available continue forms") 1.40 + 1.41 + 1.42 +(defmacro throw-msg 1.43 + "Returns a function that throws a Java Exception with the given 1.44 + name. Useful to associate a new error-kit error type with a 1.45 + particular Java Exception class, via the :unhandled error key." 1.46 + [class-name] 1.47 + `(fn [x#] (throw (new ~class-name (:msg x#))))) 1.48 + 1.49 +(defn error 1.50 + "Base type for all error-kit errors" 1.51 + {::args [:msg :unhandled :tag]} 1.52 + [details] 1.53 + (merge {:tag `error :msg "exception via error-kit" 1.54 + :unhandled (throw-msg Exception)} 1.55 + details)) 1.56 + 1.57 +(defn- qualify-sym [sym] 1.58 + (let [v (resolve sym)] 1.59 + (assert v) 1.60 + (apply symbol (map #(str (% (meta v))) [:ns :name])))) 1.61 + 1.62 +(defmacro deferror 1.63 + "Define a new error type" 1.64 + {:arglists '([name [parent-error?] doc-string? [args*] & body] 1.65 + [name [parent-error?] doc-string? args-destruct-map & body])} 1.66 + [err-name pvec & decl] 1.67 + (let [pvec (if (empty? pvec) [`error] pvec) 1.68 + [docstr args & body] (if (string? (first decl)) decl (cons nil decl)) 1.69 + args (or args []) 1.70 + argmap (if (vector? args) `{:keys ~args} args) 1.71 + body (or body {}) 1.72 + qual-err-name (symbol (str *ns*) (name err-name))] 1.73 + (assert (== (count pvec) 1)) ; only support single-inheritance for now 1.74 + (assert (vector? args)) ; only vector (keyword destruct) args for now 1.75 + `(do 1.76 + (defn ~err-name [details#] 1.77 + (let [basedata# ((resolve (first (parents '~qual-err-name))) details#) 1.78 + ~argmap basedata#] 1.79 + (merge basedata# {:tag '~qual-err-name} (do ~@body) details#))) 1.80 + (alter-meta! (var ~err-name) assoc 1.81 + :doc ~docstr ::args ~(vec (map #(keyword (str %)) args))) 1.82 + ~@(for [parent pvec] 1.83 + `(derive '~qual-err-name '~(qualify-sym parent))) 1.84 + (var ~err-name)))) 1.85 + 1.86 +(defn- throw-to [msg target-map args] 1.87 + (throw (make-ctrl-exception msg (assoc target-map :args args)))) 1.88 + 1.89 +(defn raise* 1.90 + "Raise the given error object, best if created by an error 1.91 + constructor defined with deferror. See also 'raise' macro." 1.92 + [err] 1.93 + (let [err-tag (:tag err)] 1.94 + (loop [hs *handler-stack*] 1.95 + (if (empty? hs) 1.96 + ((:unhandled err) err) 1.97 + (let [[{:keys [htag] :as handler}] hs] 1.98 + (if (and htag (not (isa? err-tag htag))) 1.99 + (recur (next hs)) 1.100 + (let [rtn ((:hfunc handler) err)] 1.101 + (if-not (vector? rtn) 1.102 + (throw-to "default" handler (list rtn)) 1.103 + (condp = (rtn 0) 1.104 + ::continue-with (rtn 1) 1.105 + ::continue (if-let [continue (*continues* (rtn 1))] 1.106 + (throw-to "continue" continue (rtn 2)) 1.107 + (do (prn *continues*) (throw 1.108 + (Exception. 1.109 + (str "Unbound continue name " (rtn 1)))))) 1.110 + ::do-not-handle (recur (next hs)) 1.111 + (throw-to "do-not-handle" handler (list rtn))))))))))) 1.112 + 1.113 +(defmacro raise 1.114 + "Raise an error of the type err-name, constructed with the given args" 1.115 + [err-name & args] 1.116 + `(raise* (~err-name ~(zipmap (::args (meta (resolve err-name))) 1.117 + args)))) 1.118 + 1.119 +; It'd be nice to assert that these are used in a tail position of a handler 1.120 +(defmacro do-not-handle 1.121 + "Use in a tail position of a 'handle' form to indicate 'raise' should 1.122 + not consider the error handled, but should continue searching for an 1.123 + appropriate 'handle' form. Allows finer-grain control over catching 1.124 + than just the error type." 1.125 + [] 1.126 + `[::do-not-handle]) 1.127 + 1.128 +(defmacro continue-with [value] 1.129 + "Use in a tail position of a 'handle' form to cause the currently 1.130 + running 'raise' to return the given 'value'." 1.131 + `[::continue-with ~value]) 1.132 + 1.133 +(defmacro continue [continue-name & args] 1.134 + "Use in a tail position of a 'handle' form to pass control to the 1.135 + named 'continue' form, passing in the given args. The 'continue' 1.136 + form with the given name and the smallest dynamic scope surrounding 1.137 + the currently running 'raise' will be used." 1.138 + `[::continue '~continue-name [~@args]]) 1.139 + 1.140 + 1.141 +(def ^{:doc "Special form to be used inside a 'with-handler'. When 1.142 + any error is 'raised' from withing the dynamic scope of 'body' that 1.143 + is of error-name's type or a derived type, the args will be bound 1.144 + and the body executed. If no 'error-name' is given, the body will 1.145 + be executed for regardless of the type of error raised. The body 1.146 + may return a value, in which case that will be the return value of 1.147 + the entire 'with-handler' form, or it may use any of the special 1.148 + return forms, 'do-not-handle', 'continue-with', or 'continue'." 1.149 + :arglists '([error-name? [args*] & body] 1.150 + [error-name? args-destruct-map-args & body])} 1.151 + handle) 1.152 + 1.153 +(def ^{:doc "Special form to be used inside a 'with-handler'. 1.154 + Control can be passed to this 'continue' form from a 'raise' enclosed 1.155 + in this with-handler's dynamic scope, when this 'continue-name' is 1.156 + given to a 'continue' form." 1.157 + :arglists '([continue-name [args*] & body])} 1.158 + bind-continue) 1.159 + 1.160 +(defn- special-form [form] 1.161 + (and (list form) 1.162 + (symbol? (first form)) 1.163 + (#{#'handle #'bind-continue} (resolve (first form))))) 1.164 + 1.165 + 1.166 +(defmacro with-handler 1.167 + "This is error-kit's dynamic scope form. The body will be executed 1.168 + in a dynamic context that includes all of the following 'handle' and 1.169 + 'bind-continue' forms." 1.170 + [& forms] 1.171 + (let [[body special-forms] (split-with (complement special-form) forms)] 1.172 + (assert (every? special-form special-forms)) 1.173 + (let [blockid (gensym) 1.174 + handlers (for [[type & more] special-forms 1.175 + :when (= (resolve type) #'handle)] 1.176 + (let [[htag args & hbody] (if (symbol? (first more)) 1.177 + more 1.178 + (cons nil more)) 1.179 + argmap (if (vector? args) `{:keys ~args} args)] 1.180 + `{:blockid '~blockid 1.181 + :htag ~(when htag (list `quote (qualify-sym htag))) 1.182 + :hfunc (fn [~argmap] ~@hbody) 1.183 + :rfunc identity})) 1.184 + continues (into {} 1.185 + (for [[type & more] special-forms 1.186 + :when (= (resolve type) #'bind-continue)] 1.187 + [(list `quote (first more)) 1.188 + `{:blockid '~blockid 1.189 + :rfunc (fn ~@(next more))}]))] 1.190 + `(try 1.191 + (binding [*handler-stack* (list* ~@handlers @#'*handler-stack*) 1.192 + *continues* (merge @#'*continues* ~@continues)] 1.193 + ~@body) 1.194 + (catch Throwable e# 1.195 + (let [root-cause# (root-cause e#)] 1.196 + (if-not (instance? @#'ctrl-exception-class root-cause#) 1.197 + (throw e#) 1.198 + (let [data# @root-cause#] 1.199 + (if (= '~blockid (:blockid data#)) 1.200 + (apply (:rfunc data#) (:args data#)) 1.201 + (throw e#)))))))))) 1.202 + 1.203 +(defn rebind-fn [func] 1.204 + (let [a *handler-stack*, b *continues*] 1.205 + (fn [& args] 1.206 + (binding [*handler-stack* a 1.207 + *continues* b] 1.208 + (apply func args))))) 1.209 + 1.210 +(comment 1.211 + 1.212 +(alias 'kit 'clojure.contrib.error-kit) 1.213 + 1.214 +; This defines an error and its action if unhandled. A good choice of 1.215 +; unhandled. action is to throw a Java exception so users of your code 1.216 +; who do not want to use error-kit can still use normal Java try/catch 1.217 +; forms to handle the error. 1.218 +(kit/deferror number-error [] [n] 1.219 + {:msg (str "Number error: " n) 1.220 + :unhandled (kit/throw-msg NumberFormatException)}) 1.221 + 1.222 +(kit/deferror odd-number-error [number-error] 1.223 + "Indicates an odd number was given to an operation that is only 1.224 + defined for even numbers." 1.225 + [n] 1.226 + {:msg (str "Can't handle odd number: " n)}) 1.227 + 1.228 +; Raise an error by name with any extra args defined by the deferror 1.229 +(defn int-half [i] 1.230 + (if (even? i) 1.231 + (quot i 2) 1.232 + (kit/raise odd-number-error i))) 1.233 + 1.234 +; Throws Java NumberFormatException because there's no 'handle' form 1.235 +(vec (map int-half [2 4 5 8])) 1.236 + 1.237 +; Throws Java Exception with details provided by 'raise' 1.238 +(kit/with-handler 1.239 + (vec (map int-half [2 4 5 8])) 1.240 + (kit/handle odd-number-error [n] 1.241 + (throw (Exception. (format "Odd number %d in vector." n))))) 1.242 + 1.243 +; The above is equivalent to the more complicated version below: 1.244 +(kit/with-handler 1.245 + (vec (map int-half [2 4 5 8])) 1.246 + (kit/handle {:keys [n tag]} 1.247 + (if (isa? tag `odd-number-error) 1.248 + (throw (Exception. (format "Odd number %d in vector." n))) 1.249 + (kit/do-not-handle)))) 1.250 + 1.251 +; Returns "invalid" string instead of a vector when an error is encountered 1.252 +(kit/with-handler 1.253 + (vec (map int-half [2 4 5 8])) 1.254 + (kit/handle kit/error [n] 1.255 + "invalid")) 1.256 + 1.257 +; Inserts a zero into the returned vector where there was an error, in 1.258 +; this case [1 2 0 4] 1.259 +(kit/with-handler 1.260 + (vec (map int-half [2 4 5 8])) 1.261 + (kit/handle number-error [n] 1.262 + (kit/continue-with 0))) 1.263 + 1.264 +; Intermediate continue: [1 2 :oops 5 4] 1.265 +(defn int-half-vec [s] 1.266 + (reduce (fn [v i] 1.267 + (kit/with-handler 1.268 + (conj v (int-half i)) 1.269 + (kit/bind-continue instead-of-half [& instead-seq] 1.270 + (apply conj v instead-seq)))) 1.271 + [] s)) 1.272 + 1.273 +(kit/with-handler 1.274 + (int-half-vec [2 4 5 8]) 1.275 + (kit/handle number-error [n] 1.276 + (kit/continue instead-of-half :oops n))) 1.277 + 1.278 +; Notes: 1.279 + 1.280 +; It seems likely you'd want to convert a handle clause to 1.281 +; bind-continue, since it would allow higher forms to request what you 1.282 +; used to do by default. Thus both should appear in the same 1.283 +; with-handler form 1.284 + 1.285 +; Should continue-names be namespace qualified, and therefore require 1.286 +; pre-definition in some namespace? 1.287 +; (kit/defcontinue skip-thing "docstring") 1.288 + 1.289 +; Could add 'catch' for Java Exceptions and 'finally' support to 1.290 +; with-handler forms. 1.291 + 1.292 +)