diff src/clojure/contrib/condition.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/condition.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,147 @@
     1.4 +;;  Copyright (c) Stephen C. Gilardi. All rights reserved.  The use and
     1.5 +;;  distribution terms for this software are covered by the Eclipse Public
     1.6 +;;  License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
     1.7 +;;  be found in the file epl-v10.html at the root of this distribution.  By
     1.8 +;;  using this software in any fashion, you are agreeing to be bound by the
     1.9 +;;  terms of this license.  You must not remove this notice, or any other,
    1.10 +;;  from this software.
    1.11 +;;
    1.12 +;;  condition.clj
    1.13 +;;
    1.14 +;;  scgilardi (gmail)
    1.15 +;;  Created 09 June 2009
    1.16 +
    1.17 +(ns ^{:author "Stephen C. Gilardi"
    1.18 +       :doc "Flexible raising and handling of conditions:
    1.19 +
    1.20 +Functions:
    1.21 +
    1.22 +              raise: raises a condition
    1.23 +       handler-case: dispatches raised conditions to appropriate handlers
    1.24 +  print-stack-trace: prints abbreviated or full condition stack traces
    1.25 +
    1.26 +Data:
    1.27 +
    1.28 +  A condition is a map containing values for these keys:
    1.29 +
    1.30 +    - :type, a condition type specifier, typically a keyword
    1.31 +    - :stack-trace, a stack trace to the site of the raise
    1.32 +    - :message, a human-readable message (optional)
    1.33 +    - :cause, a wrapped exception or condition (optional)
    1.34 +    - other keys given as arguments to raise (optional)
    1.35 +
    1.36 +Note: requires AOT compilation.
    1.37 +
    1.38 +Based on an idea from Chouser:
    1.39 +http://groups.google.com/group/clojure/browse_frm/thread/da1285c538f22bb5"}
    1.40 +  clojure.contrib.condition
    1.41 +  (:require clojure.contrib.condition.Condition)
    1.42 +  (:import clojure.contrib.condition.Condition
    1.43 +           clojure.lang.IPersistentMap)
    1.44 +  (:use (clojure.contrib
    1.45 +         [def :only (defvar)]
    1.46 +         [seq :only (separate)])))
    1.47 +
    1.48 +(defvar *condition*
    1.49 +  "While a handler is running, bound to the condition being handled")
    1.50 +
    1.51 +(defvar *selector*
    1.52 +  "While a handler is running, bound to the selector returned by the
    1.53 +  handler-case dispatch-fn for *condition*")
    1.54 +
    1.55 +(defvar *condition-object*
    1.56 +  "While a handler is running, bound to the Condition object whose metadata
    1.57 +  is the condition")
    1.58 +
    1.59 +(defvar *full-stack-traces* false
    1.60 +  "Bind to true to include clojure.{core,lang,main} frames in stack
    1.61 +  traces")
    1.62 +
    1.63 +(defmacro raise
    1.64 +  "Raises a condition. With no arguments, re-raises the current condition.
    1.65 +  With one argument (a map), raises the argument. With two or more
    1.66 +  arguments, raises a map with keys and values from the arguments."
    1.67 +  ([]
    1.68 +     `(throw *condition-object*))
    1.69 +  ([m]
    1.70 +     `(throw (Condition. ~m)))
    1.71 +  ([key val & keyvals]
    1.72 +     `(raise (hash-map ~key ~val ~@keyvals))))
    1.73 +
    1.74 +(defmacro handler-case
    1.75 +  "Executes body in a context where raised conditions can be handled.
    1.76 +
    1.77 +  dispatch-fn accepts a raised condition (a map) and returns a selector
    1.78 +  used to choose a handler. Commonly, dispatch-fn will be :type to dispatch
    1.79 +  on the condition's :type value.
    1.80 +
    1.81 +  Handlers are forms within body:
    1.82 +
    1.83 +    (handle key
    1.84 +      ...)
    1.85 +
    1.86 +  If a condition is raised, executes the body of the first handler whose
    1.87 +  key satisfies (isa? selector key). If no handlers match, re-raises the
    1.88 +  condition.
    1.89 +
    1.90 +  While a handler is running, *condition* is bound to the condition being
    1.91 +  handled and *selector* is bound to to the value returned by dispatch-fn
    1.92 +  that matched the handler's key."
    1.93 +  [dispatch-fn & body]
    1.94 +  (let [[handlers code]
    1.95 +        (separate #(and (list? %) (= 'handle (first %))) body)]
    1.96 +    `(try
    1.97 +      ~@code
    1.98 +      (catch Condition c#
    1.99 +        (binding [*condition-object* c#
   1.100 +                  *condition* (meta c#)
   1.101 +                  *selector* (~dispatch-fn (meta c#))]
   1.102 +          (cond
   1.103 +           ~@(mapcat
   1.104 +              (fn [[_ key & body]]
   1.105 +                `[(isa? *selector* ~key) (do ~@body)])
   1.106 +              handlers)
   1.107 +           :else (raise)))))))
   1.108 +
   1.109 +(defmulti stack-trace-info
   1.110 +  "Returns header, stack-trace, and cause info from conditions and
   1.111 +  Throwables"
   1.112 +  class)
   1.113 +
   1.114 +(defmethod stack-trace-info IPersistentMap
   1.115 +  [condition]
   1.116 +  [(format "condition: %s, %s" (:type condition)
   1.117 +           (dissoc condition :type :stack-trace :cause))
   1.118 +   (:stack-trace condition)
   1.119 +   (:cause condition)])
   1.120 +
   1.121 +(defmethod stack-trace-info Condition
   1.122 +  [condition]
   1.123 +  (stack-trace-info (meta condition)))
   1.124 +
   1.125 +(defmethod stack-trace-info Throwable
   1.126 +  [throwable]
   1.127 +  [(str throwable)
   1.128 +   (.getStackTrace throwable)
   1.129 +   (.getCause throwable)])
   1.130 +
   1.131 +(defn print-stack-trace
   1.132 +  "Prints a stack trace for a condition or Throwable. Skips frames for
   1.133 +  classes in clojure.{core,lang,main} unless the *full-stack-traces* is
   1.134 +  bound to logical true"
   1.135 +  [x]
   1.136 +  (let [[header frames cause] (stack-trace-info x)]
   1.137 +    (printf "%s\n" header)
   1.138 +    (doseq [frame frames]
   1.139 +      (let [classname (.getClassName frame)]
   1.140 +        (if (or *full-stack-traces*
   1.141 +                (not (re-matches
   1.142 +                      #"clojure.(?:core|lang|main).*" classname)))
   1.143 +          (printf "        at %s/%s(%s:%s)\n"
   1.144 +                  classname
   1.145 +                  (.getMethodName frame)
   1.146 +                  (.getFileName frame)
   1.147 +                  (.getLineNumber frame)))))
   1.148 +    (when cause
   1.149 +      (printf "caused by: ")
   1.150 +      (recur cause))))