From f8343c960e53d22586ccb62fcdbd686be7197468 Mon Sep 17 00:00:00 2001 From: Steve Miner Date: Sat, 20 Oct 2012 10:55:46 -0400 Subject: [PATCH] *default-data-reader-fn* for handling unknown tags --- src/clj/clojure/core.clj | 7 +++++++ src/jvm/clojure/lang/LispReader.java | 7 ++++++- src/jvm/clojure/lang/RT.java | 1 + test/clojure/test_clojure/reader.clj | 30 ++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletions(-) diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 17af1d7..baef7c3 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -6816,6 +6816,13 @@ data_readers.clj or by rebinding this Var." {}) +(def ^{:added "1.5" :dynamic true} *default-data-reader-fn* + "When no data reader is found for a tag and *default-data-reader-fn* + is non-nil, it will be called with two arguments, + the tag and the value. If *default-data-reader-fn* is nil (the + default), an exception will be thrown for the unknown tag." + nil) + (defn- data-reader-urls [] (enumeration-seq (.. Thread currentThread getContextClassLoader diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index ccde4c1..ed75004 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -1179,8 +1179,13 @@ public static class CtorReader extends AFn{ if(data_reader == null){ data_readers = (ILookup)RT.DEFAULT_DATA_READERS.deref(); data_reader = (IFn)RT.get(data_readers, tag); - if(data_reader == null) + if(data_reader == null){ + IFn default_reader = (IFn)RT.DEFAULT_DATA_READER_FN.deref(); + if(default_reader != null) + return default_reader.invoke(tag, o); + else throw new RuntimeException("No reader function for tag " + tag.toString()); + } } return data_reader.invoke(o); diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index e854c11..cea1bb5 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -183,6 +183,7 @@ final static Keyword CONST_KEY = Keyword.intern(null, "const"); final static public Var AGENT = Var.intern(CLOJURE_NS, Symbol.intern("*agent*"), null).setDynamic(); final static public Var READEVAL = Var.intern(CLOJURE_NS, Symbol.intern("*read-eval*"), T).setDynamic(); final static public Var DATA_READERS = Var.intern(CLOJURE_NS, Symbol.intern("*data-readers*"), RT.map()).setDynamic(); +final static public Var DEFAULT_DATA_READER_FN = Var.intern(CLOJURE_NS, Symbol.intern("*default-data-reader-fn*"), RT.map()).setDynamic(); final static public Var DEFAULT_DATA_READERS = Var.intern(CLOJURE_NS, Symbol.intern("default-data-readers"), RT.map()); final static public Var ASSERT = Var.intern(CLOJURE_NS, Symbol.intern("*assert*"), T).setDynamic(); final static public Var MATH_CONTEXT = Var.intern(CLOJURE_NS, Symbol.intern("*math-context*"), null).setDynamic(); diff --git a/test/clojure/test_clojure/reader.clj b/test/clojure/test_clojure/reader.clj index 5e8cddc..d12c315 100644 --- a/test/clojure/test_clojure/reader.clj +++ b/test/clojure/test_clojure/reader.clj @@ -505,3 +505,33 @@ (is (= 4 (.version #uuid "550e8400-e29b-41d4-a716-446655440000"))) (is (= (print-str #uuid "550e8400-e29b-41d4-a716-446655440000") "#uuid \"550e8400-e29b-41d4-a716-446655440000\""))) + +(deftest unknown-tag + (let [my-unknown (fn [tag val] {:unknown-tag tag :value val}) + throw-on-unknown (fn [tag val] (throw (RuntimeException. (str "No data reader function for tag " tag)))) + my-uuid (partial my-unknown 'uuid) + u "#uuid \"550e8400-e29b-41d4-a716-446655440000\"" + s "#never.heard.of/some-tag [1 2]" ] + (binding [*data-readers* {'uuid my-uuid} + *default-data-reader-fn* my-unknown] + (testing "Unknown tag" + (is (= (read-string s) + {:unknown-tag 'never.heard.of/some-tag + :value [1 2]}))) + (testing "Override uuid tag" + (is (= (read-string u) + {:unknown-tag 'uuid + :value "550e8400-e29b-41d4-a716-446655440000"})))) + + (binding [*default-data-reader-fn* throw-on-unknown] + (testing "Unknown tag with custom throw-on-unknown" + (are [err msg form] (thrown-with-msg? err msg (read-string form)) + Exception #"No data reader function for tag foo" "#foo [1 2]" + Exception #"No data reader function for tag bar/foo" "#bar/foo [1 2]" + Exception #"No data reader function for tag bar.baz/foo" "#bar.baz/foo [1 2]"))) + + (testing "Unknown tag out-of-the-box behavior (like Clojure 1.4)" + (are [err msg form] (thrown-with-msg? err msg (read-string form)) + Exception #"No reader function for tag foo" "#foo [1 2]" + Exception #"No reader function for tag bar/foo" "#bar/foo [1 2]" + Exception #"No reader function for tag bar.baz/foo" "#bar.baz/foo [1 2]")))) -- 1.7.4.1