diff options
author | Rich Hickey <richhickey@gmail.com> | 2009-10-22 18:33:32 -0400 |
---|---|---|
committer | Rich Hickey <richhickey@gmail.com> | 2009-10-22 18:33:32 -0400 |
commit | c0218cfc80ba9ae4c1808e8f5644e14c464a5268 (patch) | |
tree | c12187f5abc452c9aa64338cbb31663f8bea12d5 | |
parent | 3d69750b26d41d72920264ce8d338c20be7383a9 (diff) | |
parent | d910b3d997e1c40528aab2212fe356a8598bb738 (diff) |
Merge branch 'master' into new
45 files changed, 3028 insertions, 1154 deletions
@@ -85,8 +85,10 @@ <target name="compile-clojure" depends="compile-java" description="Compile Clojure sources."> <java classname="clojure.lang.Compile" - classpath="${build}:${cljsrc}"> + classpath="${build}:${cljsrc}" + failonerror="true"> <sysproperty key="clojure.compile.path" value="${build}"/> + <!-- <sysproperty key="clojure.compile.warn-on-reflection" value="true"/> --> <arg value="clojure.core"/> <arg value="clojure.main"/> <arg value="clojure.set"/> @@ -119,13 +121,13 @@ <target name="test" description="Run clojure tests"> <!-- depends="clojure"> --> - <java classname="clojure.main"> + <java classname="clojure.main" failonerror="true"> <classpath> <path location="${test}"/> <path location="${clojure_jar}"/> </classpath> <arg value="-e"/> - <arg value="(require '(clojure [test-clojure :as main])) (main/run)"/> + <arg value="(require '(clojure [test-clojure :as main])) (main/run-ant)"/> </java> </target> diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj index 3b2ed7b1..c4ab4785 100644 --- a/src/clj/clojure/core.clj +++ b/src/clj/clojure/core.clj @@ -251,7 +251,9 @@ (defn vec "Creates a new vector containing the contents of coll." ([coll] - (. clojure.lang.LazilyPersistentVector (createOwning (to-array coll))))) + (if (instance? java.util.Collection coll) + (clojure.lang.LazilyPersistentVector/create coll) + (. clojure.lang.LazilyPersistentVector (createOwning (to-array coll)))))) (defn hash-map "keyval => key val @@ -355,14 +357,16 @@ (defn symbol "Returns a Symbol with the given namespace and name." - ([name] (if (symbol? name) name (. clojure.lang.Symbol (intern name)))) - ([ns name] (. clojure.lang.Symbol (intern ns name)))) + {:tag clojure.lang.Symbol} + ([name] (if (symbol? name) name (clojure.lang.Symbol/intern name))) + ([ns name] (clojure.lang.Symbol/intern ns name))) (defn keyword "Returns a Keyword with the given namespace and name. Do not use : in the keyword strings, it will be added automatically." - ([name] (if (keyword? name) name (. clojure.lang.Keyword (intern nil name)))) - ([ns name] (. clojure.lang.Keyword (intern ns name)))) + {:tag clojure.lang.Keyword} + ([name] (if (keyword? name) name (clojure.lang.Keyword/intern name))) + ([ns name] (clojure.lang.Keyword/intern ns name))) (defn gensym "Returns a new symbol with a unique name. If a prefix string is @@ -393,11 +397,29 @@ (nil? (next arglist)) (seq (first arglist)) :else (cons (first arglist) (spread (next arglist))))) +(defn list* + "Creates a new list containing the items prepended to the rest, the + last of which will be treated as a sequence." + ([args] (seq args)) + ([a args] (cons a args)) + ([a b args] (cons a (cons b args))) + ([a b c args] (cons a (cons b (cons c args)))) + ([a b c d & more] + (cons a (cons b (cons c (cons d (spread more))))))) + (defn apply "Applies fn f to the argument list formed by prepending args to argseq." {:arglists '([f args* argseq])} - [#^clojure.lang.IFn f & args] - (. f (applyTo (spread args)))) + ([#^clojure.lang.IFn f args] + (. f (applyTo (seq args)))) + ([#^clojure.lang.IFn f x args] + (. f (applyTo (list* x args)))) + ([#^clojure.lang.IFn f x y args] + (. f (applyTo (list* x y args)))) + ([#^clojure.lang.IFn f x y z args] + (. f (applyTo (list* x y z args)))) + ([#^clojure.lang.IFn f a b c d & args] + (. f (applyTo (cons a (cons b (cons c (cons d (spread args))))))))) (defn vary-meta "Returns an object of the same type and value as obj, with @@ -405,10 +427,7 @@ [obj f & args] (with-meta obj (apply f (meta obj) args))) -(defn list* - "Creates a new list containing the item prepended to more." - [item & more] - (spread (cons item more))) + (defmacro lazy-seq "Takes a body of expressions that returns an ISeq or nil, and yields @@ -418,25 +437,56 @@ [& body] (list 'new 'clojure.lang.LazySeq (list* '#^{:once true} fn* [] body))) +(defn #^clojure.lang.ChunkBuffer chunk-buffer [capacity] + (clojure.lang.ChunkBuffer. capacity)) + +(defn chunk-append [#^clojure.lang.ChunkBuffer b x] + (.add b x)) + +(defn chunk [#^clojure.lang.ChunkBuffer b] + (.chunk b)) + +(defn #^clojure.lang.IChunk chunk-first [#^clojure.lang.IChunkedSeq s] + (.chunkedFirst s)) + +(defn #^clojure.lang.ISeq chunk-rest [#^clojure.lang.IChunkedSeq s] + (.chunkedMore s)) + +(defn #^clojure.lang.ISeq chunk-next [#^clojure.lang.IChunkedSeq s] + (.chunkedNext s)) + +(defn chunk-cons [chunk rest] + (if (clojure.lang.Numbers/isZero (clojure.lang.RT/count chunk)) + rest + (clojure.lang.ChunkedCons. chunk rest))) + +(defn chunked-seq? [s] + (instance? clojure.lang.IChunkedSeq s)) + (defn concat "Returns a lazy seq representing the concatenation of the elements in the supplied colls." ([] (lazy-seq nil)) ([x] (lazy-seq x)) ([x y] - (lazy-seq + (lazy-seq (let [s (seq x)] (if s - (cons (first s) (concat (rest s) y)) + (if (chunked-seq? s) + (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) + (cons (first s) (concat (rest s) y))) y)))) ([x y & zs] (let [cat (fn cat [xys zs] (lazy-seq - (let [xys (seq xys)] - (if xys - (cons (first xys) (cat (rest xys) zs)) - (when zs - (cat (first zs) (next zs)))))))] - (cat (concat x y) zs)))) + (let [xys (seq xys)] + (if xys + (if (chunked-seq? xys) + (chunk-cons (chunk-first xys) + (cat (chunk-rest xys) zs)) + (cons (first xys) (cat (rest xys) zs))) + (when zs + (cat (first zs) (next zs)))))))] + (cat (concat x y) zs)))) ;;;;;;;;;;;;;;;;at this point all the support for syntax-quote exists;;;;;;;;;;;;;;;;;;;;;; @@ -492,10 +542,11 @@ (defn compare - "Comparator. Returns 0 if x equals y, -1 if x is logically 'less - than' y, else 1. Same as Java x.compareTo(y) except it also works - for nil, and compares numbers and collections in a type-independent - manner. x must implement Comparable" + "Comparator. Returns a negative number, zero, or a positive number + when x is logically 'less than', 'equal to', or 'greater than' + y. Same as Java x.compareTo(y) except it also works for nil, and + compares numbers and collections in a type-independent manner. x + must implement Comparable" {:tag Integer :inline (fn [x y] `(. clojure.lang.Util compare ~x ~y))} [x y] (. clojure.lang.Util (compare x y))) @@ -545,8 +596,8 @@ bounds, nth throws an exception unless not-found is supplied. nth also works for strings, Java arrays, regex Matchers and Lists, and, in O(n) time, for sequences." - {:inline (fn [c i] `(. clojure.lang.RT (nth ~c ~i))) - :inline-arities #{2}} + {:inline (fn [c i & nf] `(. clojure.lang.RT (nth ~c ~i ~@nf))) + :inline-arities #{2 3}} ([coll index] (. clojure.lang.RT (nth coll index))) ([coll index not-found] (. clojure.lang.RT (nth coll index not-found)))) @@ -569,32 +620,6 @@ {:inline (fn [x] `(. clojure.lang.Numbers (inc ~x)))} [x] (. clojure.lang.Numbers (inc x))) -(defn #^clojure.lang.ChunkBuffer chunk-buffer [capacity] - (clojure.lang.ChunkBuffer. capacity)) - -(defn chunk-append [#^clojure.lang.ChunkBuffer b x] - (.add b x)) - -(defn chunk [#^clojure.lang.ChunkBuffer b] - (.chunk b)) - -(defn #^clojure.lang.IChunk chunk-first [#^clojure.lang.IChunkedSeq s] - (.chunkedFirst s)) - -(defn #^clojure.lang.ISeq chunk-rest [#^clojure.lang.IChunkedSeq s] - (.chunkedMore s)) - -(defn #^clojure.lang.ISeq chunk-next [#^clojure.lang.IChunkedSeq s] - (.chunkedNext s)) - -(defn chunk-cons [chunk rest] - (if (zero? (count chunk)) - rest - (clojure.lang.ChunkedCons. chunk rest))) - -(defn chunked-seq? [s] - (instance? clojure.lang.IChunkedSeq s)) - (defn reduce "f should be a function of 2 arguments. If val is not supplied, returns the result of applying f to the first 2 items in coll, then @@ -1048,6 +1073,16 @@ (list form x))) ([x form & more] `(-> (-> ~x ~form) ~@more))) +(defmacro ->> + "Threads the expr through the forms. Inserts x as the + last item in the first form, making a list of it if it is not a + list already. If there are more forms, inserts the first form as the + last item in second form, etc." + ([x form] (if (seq? form) + `(~(first form) ~@(next form) ~x) + (list form x))) + ([x form & more] `(->> (->> ~x ~form) ~@more))) + ;;multimethods (def global-hierarchy) @@ -1159,28 +1194,58 @@ (let [~form temp#] ~@body))))) +(defn push-thread-bindings + "WARNING: This is a low-level function. Prefer high-level macros like + binding where ever possible. + + Takes a map of Var/value pairs. Binds each Var to the associated value for + the current thread. Each call *MUST* be accompanied by a matching call to + pop-thread-bindings wrapped in a try-finally! + + (push-thread-bindings bindings) + (try + ... + (finally + (pop-thread-bindings)))" + [bindings] + (clojure.lang.Var/pushThreadBindings bindings)) + +(defn pop-thread-bindings + "Pop one set of bindings pushed with push-binding before. It is an error to + pop bindings without pushing before." + [] + (clojure.lang.Var/popThreadBindings)) + +(defn get-thread-bindings + "Get a map with the Var/value pairs which is currently in effect for the + current thread." + [] + (clojure.lang.Var/getThreadBindings)) + (defmacro binding "binding => var-symbol init-expr Creates new bindings for the (already-existing) vars, with the supplied initial values, executes the exprs in an implicit do, then - re-establishes the bindings that existed before." + re-establishes the bindings that existed before. The new bindings + are made in parallel (unlike let); all init-exprs are evaluated + before the vars are bound to their new values." [bindings & body] - (assert-args binding - (vector? bindings) "a vector for its binding" - (even? (count bindings)) "an even number of forms in binding vector") - (let [var-ize (fn [var-vals] - (loop [ret [] vvs (seq var-vals)] - (if vvs - (recur (conj (conj ret `(var ~(first vvs))) (second vvs)) - (next (next vvs))) - (seq ret))))] - `(let [] - (. clojure.lang.Var (pushThreadBindings (hash-map ~@(var-ize bindings)))) - (try - ~@body - (finally - (. clojure.lang.Var (popThreadBindings))))))) + (assert-args binding + (vector? bindings) "a vector for its binding" + (even? (count bindings)) "an even number of forms in binding vector") + (let [var-ize (fn [var-vals] + (loop [ret [] vvs (seq var-vals)] + (if vvs + (recur (conj (conj ret `(var ~(first vvs))) (second vvs)) + (next (next vvs))) + (seq ret))))] + `(let [] + (push-thread-bindings (hash-map ~@(var-ize bindings))) + (try + ~@body + (finally + (pop-thread-bindings)))))) (defn find-var "Returns the global var named by the namespace-qualified symbol, or @@ -1501,13 +1566,65 @@ of those fns. The returned fn takes a variable number of args, applies the rightmost of fns to the args, the next fn (right-to-left) to the result, etc." - [& fs] - (let [fs (reverse fs)] + ([f] f) + ([f g] + (fn + ([] (f (g))) + ([x] (f (g x))) + ([x y] (f (g x y))) + ([x y z] (f (g x y z))) + ([x y z & args] (f (apply g x y z args))))) + ([f g h] + (fn + ([] (f (g (h)))) + ([x] (f (g (h x)))) + ([x y] (f (g (h x y)))) + ([x y z] (f (g (h x y z)))) + ([x y z & args] (f (g (apply h x y z args)))))) + ([f1 f2 f3 & fs] + (let [fs (reverse (list* f1 f2 f3 fs))] (fn [& args] (loop [ret (apply (first fs) args) fs (next fs)] (if fs (recur ((first fs) ret) (next fs)) - ret))))) + ret)))))) + +(defn juxt + "Alpha - name subject to change. + Takes a set of functions and returns a fn that is the juxtaposition + of those fns. The returned fn takes a variable number of args, and + returns a vector containing the result of applying each fn to the + args (left-to-right). + ((juxt a b c) x) => [(a x) (b x) (c x)]" + ([f] + (fn + ([] [(f)]) + ([x] [(f x)]) + ([x y] [(f x y)]) + ([x y z] [(f x y z)]) + ([x y z & args] [(apply f x y z args)]))) + ([f g] + (fn + ([] [(f) (g)]) + ([x] [(f x) (g x)]) + ([x y] [(f x y) (g x y)]) + ([x y z] [(f x y z) (g x y z)]) + ([x y z & args] [(apply f x y z args) (apply g x y z args)]))) + ([f g h] + (fn + ([] [(f) (g) (h)]) + ([x] [(f x) (g x) (h x)]) + ([x y] [(f x y) (g x y) (h x y)]) + ([x y z] [(f x y z) (g x y z) (h x y z)]) + ([x y z & args] [(apply f x y z args) (apply g x y z args) (apply h x y z args)]))) + ([f g h & fs] + (let [fs (list* f g h fs)] + (fn + ([] (reduce #(conj %1 (%2)) [] fs)) + ([x] (reduce #(conj %1 (%2 x)) [] fs)) + ([x y] (reduce #(conj %1 (%2 x y)) [] fs)) + ([x y z] (reduce #(conj %1 (%2 x y z)) [] fs)) + ([x y z & args] (reduce #(conj %1 (apply %2 x y z args)) [] fs)))))) (defn partial "Takes a function f and fewer than the normal arguments to f, and @@ -1685,6 +1802,15 @@ ([s] (drop-last 1 s)) ([n s] (map (fn [x _] x) s (drop n s)))) +(defn take-last + "Returns a seq of the last n items in coll. Depending on the type + of coll may be no better than linear time. For vectors, see also subvec." + [n coll] + (loop [s (seq coll), lead (seq (drop n coll))] + (if lead + (recur (next s) (next lead)) + s))) + (defn drop-while "Returns a lazy sequence of the items in coll starting from the first item for which (pred item) returns nil." @@ -1860,27 +1986,49 @@ (if-not exprs [true `(do ~@body)] (let [k (first exprs) - v (second exprs) - seqsym (when-not (keyword? k) (gensym)) - recform (if (keyword? k) recform `(recur (next ~seqsym))) - steppair (step recform (nnext exprs)) - needrec (steppair 0) - subform (steppair 1)] - (cond - (= k :let) [needrec `(let ~v ~subform)] - (= k :while) [false `(when ~v - ~subform - ~@(when needrec [recform]))] - (= k :when) [false `(if ~v - (do - ~subform - ~@(when needrec [recform])) - ~recform)] - :else [true `(loop [~seqsym (seq ~v)] - (when ~seqsym - (let [~k (first ~seqsym)] - ~subform - ~@(when needrec [recform]))))]))))] + v (second exprs)] + (if (keyword? k) + (let [steppair (step recform (nnext exprs)) + needrec (steppair 0) + subform (steppair 1)] + (cond + (= k :let) [needrec `(let ~v ~subform)] + (= k :while) [false `(when ~v + ~subform + ~@(when needrec [recform]))] + (= k :when) [false `(if ~v + (do + ~subform + ~@(when needrec [recform])) + ~recform)])) + (let [seq- (gensym "seq_") + chunk- (with-meta (gensym "chunk_") + {:tag 'clojure.lang.IChunk}) + count- (gensym "count_") + i- (gensym "i_") + recform `(recur (next ~seq-) nil (int 0) (int 0)) + steppair (step recform (nnext exprs)) + needrec (steppair 0) + subform (steppair 1) + recform-chunk + `(recur ~seq- ~chunk- ~count- (unchecked-inc ~i-)) + steppair-chunk (step recform-chunk (nnext exprs)) + subform-chunk (steppair-chunk 1)] + [true + `(loop [~seq- (seq ~v), ~chunk- nil, + ~count- (int 0), ~i- (int 0)] + (if (< ~i- ~count-) + (let [~k (.nth ~chunk- ~i-)] + ~subform-chunk + ~@(when needrec [recform-chunk])) + (when-let [~seq- (seq ~seq-)] + (if (chunked-seq? ~seq-) + (let [c# (chunk-first ~seq-)] + (recur (chunk-rest ~seq-) c# + (int (count c#)) (int 0))) + (let [~k (first ~seq-)] + ~subform + ~@(when needrec [recform]))))))])))))] (nth (step nil (seq seq-exprs)) 1))) (defn dorun @@ -2586,10 +2734,18 @@ (cons (first s) (take-nth n (drop n s)))))) (defn interleave - "Returns a lazy seq of the first item in each coll, then the second - etc." - [& colls] - (apply concat (apply map list colls))) + "Returns a lazy seq of the first item in each coll, then the second etc." + ([c1 c2] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2)] + (when (and s1 s2) + (cons (first s1) (cons (first s2) + (interleave (rest s1) (rest s2)))))))) + ([c1 c2 & colls] + (lazy-seq + (let [ss (map seq (conj colls c2 c1))] + (when (every? identity ss) + (concat (map first ss) (apply interleave (map rest ss)))))))) (defn var-get "Gets the value in the var object" @@ -2821,7 +2977,7 @@ binding-forms. Supported modifiers are: :let [binding-form expr ...], :while test, :when test. - (take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))" + (take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))" [seq-exprs body-expr] (assert-args for (vector? seq-exprs) "a vector for its binding" @@ -2832,7 +2988,7 @@ (conj (pop groups) (conj (peek groups) [k v])) (conj groups [k v]))) [] (partition 2 seq-exprs))) - err (fn [& msg] (throw (IllegalArgumentException. (apply str msg)))) + err (fn [& msg] (throw (IllegalArgumentException. #^String (apply str msg)))) emit-bind (fn emit-bind [[[bind expr & mod-pairs] & [[_ next-expr] :as next-groups]]] (let [giter (gensym "iter__") @@ -2853,11 +3009,48 @@ (recur (rest ~gxs)))) :else `(cons ~body-expr (~giter (rest ~gxs)))))] - `(fn ~giter [~gxs] - (lazy-seq - (loop [~gxs ~gxs] - (when-first [~bind ~gxs] - ~(do-mod mod-pairs)))))))] + (if next-groups + #_"not the inner-most loop" + `(fn ~giter [~gxs] + (lazy-seq + (loop [~gxs ~gxs] + (when-first [~bind ~gxs] + ~(do-mod mod-pairs))))) + #_"inner-most loop" + (let [gi (gensym "i__") + gb (gensym "b__") + do-cmod (fn do-cmod [[[k v :as pair] & etc]] + (cond + (= k :let) `(let ~v ~(do-cmod etc)) + (= k :while) `(when ~v ~(do-cmod etc)) + (= k :when) `(if ~v + ~(do-cmod etc) + (recur + (unchecked-inc ~gi))) + (keyword? k) + (err "Invalid 'for' keyword " k) + :else + `(do (chunk-append ~gb ~body-expr) + (recur (unchecked-inc ~gi)))))] + `(fn ~giter [~gxs] + (lazy-seq + (loop [~gxs ~gxs] + (when-let [~gxs (seq ~gxs)] + (if (chunked-seq? ~gxs) + (let [c# (chunk-first ~gxs) + size# (int (count c#)) + ~gb (chunk-buffer size#)] + (if (loop [~gi (int 0)] + (if (< ~gi size#) + (let [~bind (.nth c# ~gi)] + ~(do-cmod mod-pairs)) + true)) + (chunk-cons + (chunk ~gb) + (~giter (chunk-rest ~gxs))) + (chunk-cons (chunk ~gb) nil))) + (let [~bind (first ~gxs)] + ~(do-mod mod-pairs)))))))))))] `(let [iter# ~(emit-bind (to-groups seq-exprs))] (iter# ~(second seq-exprs))))) @@ -3587,7 +3780,7 @@ (defmacro with-loading-context [& body] `((fn loading# [] (. clojure.lang.Var (pushThreadBindings {clojure.lang.Compiler/LOADER - (-> loading# .getClass .getClassLoader)})) + (.getClassLoader (.getClass #^Object loading#))})) (try ~@body (finally @@ -4262,9 +4455,9 @@ "clojure/version.properties") properties (doto (new java.util.Properties) (.load version-stream)) prop (fn [k] (.getProperty properties (str "clojure.version." k))) - clojure-version {:major (Integer/valueOf (prop "major")) - :minor (Integer/valueOf (prop "minor")) - :incremental (Integer/valueOf (prop "incremental")) + clojure-version {:major (Integer/valueOf #^String (prop "major")) + :minor (Integer/valueOf #^String (prop "minor")) + :incremental (Integer/valueOf #^String (prop "incremental")) :qualifier (prop "qualifier")}] (def *clojure-version* (if (not (= (prop "interim") "false")) @@ -4315,3 +4508,76 @@ Delivers the supplied value to the promise, releasing any pending derefs. A subsequent call to deliver on a promise will throw an exception." [promise val] (promise val)) + +;;;;;;;;;;;;;;;;;;;;; editable collections ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn transient + "Returns a new, transient version of the collection, in constant time." + [#^clojure.lang.IEditableCollection coll] + (.asTransient coll)) + +(defn persistent! + "Returns a new, persistent version of the transient collection, in + constant time. The transient collection cannot be used after this + call, any such use will throw an exception." + [#^clojure.lang.ITransientCollection coll] + (.persistent coll)) + +(defn conj! + "Adds x to the transient collection, and return coll. The 'addition' + may happen at different 'places' depending on the concrete type." + [#^clojure.lang.ITransientCollection coll x] + (.conj coll x)) + +(defn assoc! + "When applied to a transient map, adds mapping of key(s) to + val(s). When applied to a transient vector, sets the val at index. + Note - index must be <= (count vector). Returns coll." + ([#^clojure.lang.ITransientAssociative coll key val] (.assoc coll key val)) + ([#^clojure.lang.ITransientAssociative coll key val & kvs] + (let [ret (.assoc coll key val)] + (if kvs + (recur ret (first kvs) (second kvs) (nnext kvs)) + ret)))) + +(defn dissoc! + "Returns a transient map that doesn't contain a mapping for key(s)." + ([#^clojure.lang.ITransientMap map key] (.without map key)) + ([#^clojure.lang.ITransientMap map key & ks] + (let [ret (.without map key)] + (if ks + (recur ret (first ks) (next ks)) + ret)))) + +(defn pop! + "Removes the last item from a transient vector. If + the collection is empty, throws an exception. Returns coll" + [#^clojure.lang.ITransientVector coll] + (.pop coll)) + +(defn disj! + "disj[oin]. Returns a transient set of the same (hashed/sorted) type, that + does not contain key(s)." + ([set] set) + ([#^clojure.lang.ITransientSet set key] + (. set (disjoin key))) + ([set key & ks] + (let [ret (disj set key)] + (if ks + (recur ret (first ks) (next ks)) + ret)))) + +;redef into with batch support +(defn into + "Returns a new coll consisting of to-coll with all of the items of + from-coll conjoined." + [to from] + (if (instance? clojure.lang.IEditableCollection to) + (#(loop [ret (transient to) items (seq from)] + (if items + (recur (conj! ret (first items)) (next items)) + (persistent! ret)))) + (#(loop [ret to items (seq from)] + (if items + (recur (conj ret (first items)) (next items)) + ret))))) + diff --git a/src/clj/clojure/core_proxy.clj b/src/clj/clojure/core_proxy.clj index 93e3e0ba..c24b69d5 100644 --- a/src/clj/clojure/core_proxy.clj +++ b/src/clj/clojure/core_proxy.clj @@ -34,10 +34,13 @@ (defn proxy-name {:tag String} [#^Class super interfaces] - (apply str "clojure.proxy." - (.getName super) - (interleave (repeat "$") - (sort (map #(.getSimpleName #^Class %) interfaces))))) + (let [inames (into (sorted-set) (map #(.getName #^Class %) interfaces))] + (apply str (.replace (str *ns*) \- \_) ".proxy" + (interleave (repeat "$") + (concat + [(.getName super)] + (map #(subs % (inc (.lastIndexOf #^String % "."))) inames) + [(Integer/toHexString (hash inames))]))))) (defn- generate-proxy [#^Class super interfaces] (let [cv (new ClassWriter (. ClassWriter COMPUTE_MAXS)) @@ -255,7 +258,7 @@ pname (proxy-name super interfaces)] (or (RT/loadClassForName pname) (let [[cname bytecode] (generate-proxy super interfaces)] - (. (deref clojure.lang.Compiler/LOADER) (defineClass pname bytecode)))))) + (. #^DynamicClassLoader (deref clojure.lang.Compiler/LOADER) (defineClass pname bytecode)))))) (defn construct-proxy "Takes a proxy class and any arguments for its superclass ctor and diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj index 58fa0a3f..633c21e9 100644 --- a/src/clj/clojure/main.clj +++ b/src/clj/clojure/main.clj @@ -93,7 +93,7 @@ (defn- root-cause "Returns the initial cause of an exception or error by peeling off all of its wrappers" - [throwable] + [#^Throwable throwable] (loop [cause throwable] (if-let [cause (.getCause cause)] (recur cause) @@ -161,7 +161,7 @@ (let [{:keys [init need-prompt prompt flush read eval print caught] :or {init #() need-prompt (if (instance? LineNumberingPushbackReader *in*) - #(.atLineStart *in*) + #(.atLineStart #^LineNumberingPushbackReader *in*) #(identity true)) prompt repl-prompt flush flush @@ -203,7 +203,7 @@ (defn load-script "Loads Clojure source from a file or resource given its path. Paths beginning with @ or @/ are considered relative to classpath." - [path] + [#^String path] (if (.startsWith path "@") (RT/loadResourceScript (.substring path (if (.startsWith path "@/") 2 1))) diff --git a/src/clj/clojure/test.clj b/src/clj/clojure/test.clj index 7195034b..0d7c4600 100644 --- a/src/clj/clojure/test.clj +++ b/src/clj/clojure/test.clj @@ -505,7 +505,7 @@ Chas Emerick, Allen Rohner, and Stuart Halloway", "Returns a vector [filename line-number] for the nth call up the stack." [n] - (let [s (nth (.getStackTrace (new java.lang.Throwable)) n)] + (let [#^StackTraceElement s (nth (.getStackTrace (new java.lang.Throwable)) n)] [(.getFileName s) (.getLineNumber s)])) (defn testing-vars-str diff --git a/src/clj/clojure/test/tap.clj b/src/clj/clojure/test/tap.clj index 6f4b57a6..11550db0 100644 --- a/src/clj/clojure/test/tap.clj +++ b/src/clj/clojure/test/tap.clj @@ -51,7 +51,7 @@ "Prints a TAP diagnostic line. data is a (possibly multi-line) string." [data] - (doseq [line (.split data "\n")] + (doseq [line (.split #^String data "\n")] (println "#" line))) (defn print-tap-pass diff --git a/src/clj/clojure/xml.clj b/src/clj/clojure/xml.clj index 251f16c5..caac770a 100644 --- a/src/clj/clojure/xml.clj +++ b/src/clj/clojure/xml.clj @@ -26,7 +26,7 @@ (assoc e :content (conj (or (:content e) []) c))) push-chars (fn [] (when (and (= *state* :chars) - (some (complement #(. Character (isWhitespace %))) (str *sb*))) + (some (complement #(Character/isWhitespace (char %))) (str *sb*))) (set! *current* (push-content *current* (str *sb*)))))] (new clojure.lang.XMLHandler (proxy [ContentHandler] [] @@ -34,14 +34,14 @@ (let [attrs (fn [ret i] (if (neg? i) ret - (recur (assoc ret - (. clojure.lang.Keyword (intern (symbol (. atts (getQName i))))) - (. atts (getValue i))) + (recur (assoc ret + (clojure.lang.Keyword/intern (symbol (.getQName atts i))) + (.getValue atts (int i))) (dec i)))) - e (struct element + e (struct element (. clojure.lang.Keyword (intern (symbol q-name))) - (when (pos? (. atts (getLength))) - (attrs {} (dec (. atts (getLength))))))] + (when (pos? (.getLength atts)) + (attrs {} (dec (.getLength atts)))))] (push-chars) (set! *stack* (conj *stack* *current*)) (set! *current* e) @@ -53,11 +53,11 @@ (set! *stack* (pop *stack*)) (set! *state* :between) nil) - (characters [ch start length] + (characters [#^chars ch start length] (when-not (= *state* :chars) (set! *sb* (new StringBuilder))) (let [#^StringBuilder sb *sb*] - (. sb (append ch start length)) + (.append sb ch (int start) (int length)) (set! *state* :chars)) nil) (setDocumentLocator [locator]) diff --git a/src/clj/clojure/zip.clj b/src/clj/clojure/zip.clj index 81b09060..00cc3be5 100644 --- a/src/clj/clojure/zip.clj +++ b/src/clj/clojure/zip.clj @@ -31,12 +31,18 @@ (defn seq-zip "Returns a zipper for nested sequences, given a root sequence" [root] - (zipper seq? identity (fn [node children] children) root)) + (zipper seq? + identity + (fn [node children] (with-meta children (meta node))) + root)) (defn vector-zip "Returns a zipper for nested vectors, given a root vector" [root] - (zipper vector? seq (fn [node children] (apply vector children)) root)) + (zipper vector? + seq + (fn [node children] (with-meta (vec children) (meta node))) + root)) (defn xml-zip "Returns a zipper for xml elements (as from xml/parse), diff --git a/src/jvm/clojure/lang/ATransientMap.java b/src/jvm/clojure/lang/ATransientMap.java new file mode 100644 index 00000000..0f17ba6f --- /dev/null +++ b/src/jvm/clojure/lang/ATransientMap.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +package clojure.lang; + +import java.util.Map; + +import clojure.lang.PersistentHashMap.INode; + +abstract class ATransientMap extends AFn implements ITransientMap { + abstract void ensureEditable(); + abstract ITransientMap doAssoc(Object key, Object val); + abstract ITransientMap doWithout(Object key); + abstract Object doValAt(Object key, Object notFound); + abstract int doCount(); + abstract IPersistentMap doPersistent(); + + public ITransientMap conj(Object o) { + ensureEditable(); + if(o instanceof Map.Entry) + { + Map.Entry e = (Map.Entry) o; + + return assoc(e.getKey(), e.getValue()); + } + else if(o instanceof IPersistentVector) + { + IPersistentVector v = (IPersistentVector) o; + if(v.count() != 2) + throw new IllegalArgumentException("Vector arg to map conj must be a pair"); + return assoc(v.nth(0), v.nth(1)); + } + + ITransientMap ret = this; + for(ISeq es = RT.seq(o); es != null; es = es.next()) + { + Map.Entry e = (Map.Entry) es.first(); + ret = ret.assoc(e.getKey(), e.getValue()); + } + return ret; + } + + public final Object invoke(Object arg1) throws Exception{ + return valAt(arg1); + } + + public final Object invoke(Object arg1, Object notFound) throws Exception{ + return valAt(arg1, notFound); + } + + public final Object valAt(Object key) { + return valAt(key, null); + } + + public final ITransientMap assoc(Object key, Object val) { + ensureEditable(); + return doAssoc(key, val); + } + + public final ITransientMap without(Object key) { + ensureEditable(); + return doWithout(key); + } + + public final IPersistentMap persistent() { + ensureEditable(); + return doPersistent(); + } + + public final Object valAt(Object key, Object notFound) { + ensureEditable(); + return doValAt(key, notFound); + } + + public final int count() { + ensureEditable(); + return doCount(); + } +} diff --git a/src/jvm/clojure/lang/ATransientSet.java b/src/jvm/clojure/lang/ATransientSet.java new file mode 100644 index 00000000..6eec807a --- /dev/null +++ b/src/jvm/clojure/lang/ATransientSet.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Mar 3, 2008 */ + +package clojure.lang; + +public abstract class ATransientSet extends AFn implements ITransientSet{ + ITransientMap impl; + + ATransientSet(ITransientMap impl) { + this.impl = impl; + } + + public int count() { + return impl.count(); + } + + public ITransientSet conj(Object val) { + ITransientMap m = impl.assoc(val, val); + if (m != impl) this.impl = m; + return this; + } + + public boolean contains(Object key) { + return this != impl.valAt(key, this); + } + + public ITransientSet disjoin(Object key) throws Exception { + ITransientMap m = impl.without(key); + if (m != impl) this.impl = m; + return this; + } + + public Object get(Object key) { + return impl.valAt(key); + } + + public Object invoke(Object key, Object notFound) throws Exception { + return impl.valAt(key, notFound); + } + + public Object invoke(Object key) throws Exception { + return impl.valAt(key); + } + +} diff --git a/src/jvm/clojure/lang/ArraySeq.java b/src/jvm/clojure/lang/ArraySeq.java index 0373dd43..117dd4f5 100644 --- a/src/jvm/clojure/lang/ArraySeq.java +++ b/src/jvm/clojure/lang/ArraySeq.java @@ -42,6 +42,12 @@ static ISeq createFromObject(Object array){ return new ArraySeq_double(null, (double[]) array, 0); if(aclass == long[].class) return new ArraySeq_long(null, (long[]) array, 0); + if(aclass == byte[].class) + return new ArraySeq_byte(null, (byte[]) array, 0); + if(aclass == char[].class) + return new ArraySeq_char(null, (char[]) array, 0); + if(aclass == boolean[].class) + return new ArraySeq_boolean(null, (boolean[]) array, 0); return new ArraySeq(array, 0); } @@ -122,6 +128,39 @@ public Object reduce(IFn f, Object start) throws Exception{ return ret; } +public int indexOf(Object o) { + if (oa != null) { + for (int j = i; j < oa.length; j++) + if (Util.equals(o, oa[j])) return j - i; + } else { + int n = Array.getLength(array); + for (int j = i; j < n; j++) + if (Util.equals(o, Reflector.prepRet(Array.get(array, j)))) return j - i; + } + return -1; +} + +public int lastIndexOf(Object o) { + if (oa != null) { + if (o == null) { + for (int j = oa.length - 1 ; j >= i; j--) + if (oa[j] == null) return j - i; + } else { + for (int j = oa.length - 1 ; j >= i; j--) + if (o.equals(oa[j])) return j - i; + } + } else { + if (o == null) { + for (int j = Array.getLength(array) - 1 ; j >= i; j--) + if (Reflector.prepRet(Array.get(array, j)) == null) return j - i; + } else { + for (int j = Array.getLength(array) - 1 ; j >= i; j--) + if (o.equals(Reflector.prepRet(Array.get(array, j)))) return j - i; + } + } + return -1; +} + //////////////////////////////////// specialized primitive versions /////////////////////////////// static public class ArraySeq_int extends ASeq implements IndexedSeq, IReduce{ @@ -169,6 +208,34 @@ static public class ArraySeq_int extends ASeq implements IndexedSeq, IReduce{ ret = f.invoke(ret, array[x]); return ret; } + + public int indexOf(Object o) { + if (o instanceof Integer) { + int k = ((Integer) o).intValue(); + for (int j = i; j < array.length; j++) + if (k == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Integer) { + int k = ((Integer) o).intValue(); + for (int j = array.length - 1; j >= i; j--) + if (k == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } } @@ -217,6 +284,34 @@ static public class ArraySeq_float extends ASeq implements IndexedSeq, IReduce{ ret = f.invoke(ret, array[x]); return ret; } + + public int indexOf(Object o) { + if (o instanceof Float) { + float f = ((Float) o).floatValue(); + for (int j = i; j < array.length; j++) + if (f == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Float) { + float f = ((Float) o).floatValue(); + for (int j = array.length - 1; j >= i; j--) + if (f == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } } static public class ArraySeq_double extends ASeq implements IndexedSeq, IReduce{ @@ -264,6 +359,34 @@ static public class ArraySeq_double extends ASeq implements IndexedSeq, IReduce{ ret = f.invoke(ret, array[x]); return ret; } + + public int indexOf(Object o) { + if (o instanceof Double) { + double d = ((Double) o).doubleValue(); + for (int j = i; j < array.length; j++) + if (d == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Double) { + double d = ((Double) o).doubleValue(); + for (int j = array.length - 1; j >= i; j--) + if (d == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } } static public class ArraySeq_long extends ASeq implements IndexedSeq, IReduce{ @@ -311,6 +434,259 @@ static public class ArraySeq_long extends ASeq implements IndexedSeq, IReduce{ ret = f.invoke(ret, array[x]); return ret; } + + public int indexOf(Object o) { + if (o instanceof Long) { + long l = ((Long) o).longValue(); + for (int j = i; j < array.length; j++) + if (l == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Long) { + long l = ((Long) o).longValue(); + for (int j = array.length - 1; j >= i; j--) + if (l == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } +} + +static public class ArraySeq_byte extends ASeq implements IndexedSeq, IReduce{ + final byte[] array; + final int i; + + ArraySeq_byte(IPersistentMap meta, byte[] array, int i){ + super(meta); + this.array = array; + this.i = i; + } + + public Object first(){ + return array[i]; + } + + public ISeq next(){ + if(i + 1 < array.length) + return new ArraySeq_byte(meta(), array, i + 1); + return null; + } + + public int count(){ + return array.length - i; + } + + public int index(){ + return i; + } + + public ArraySeq_byte withMeta(IPersistentMap meta){ + return new ArraySeq_byte(meta, array, i); + } + + public Object reduce(IFn f) throws Exception{ + Object ret = array[i]; + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public Object reduce(IFn f, Object start) throws Exception{ + Object ret = f.invoke(start, array[i]); + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public int indexOf(Object o) { + if (o instanceof Byte) { + byte b = ((Byte) o).byteValue(); + for (int j = i; j < array.length; j++) + if (b == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Byte) { + byte b = ((Byte) o).byteValue(); + for (int j = array.length - 1; j >= i; j--) + if (b == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } +} + +static public class ArraySeq_char extends ASeq implements IndexedSeq, IReduce{ + final char[] array; + final int i; + + ArraySeq_char(IPersistentMap meta, char[] array, int i){ + super(meta); + this.array = array; + this.i = i; + } + + public Object first(){ + return array[i]; + } + + public ISeq next(){ + if(i + 1 < array.length) + return new ArraySeq_char(meta(), array, i + 1); + return null; + } + + public int count(){ + return array.length - i; + } + + public int index(){ + return i; + } + + public ArraySeq_char withMeta(IPersistentMap meta){ + return new ArraySeq_char(meta, array, i); + } + + public Object reduce(IFn f) throws Exception{ + Object ret = array[i]; + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public Object reduce(IFn f, Object start) throws Exception{ + Object ret = f.invoke(start, array[i]); + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public int indexOf(Object o) { + if (o instanceof Character) { + char c = ((Character) o).charValue(); + for (int j = i; j < array.length; j++) + if (c == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Character) { + char c = ((Character) o).charValue(); + for (int j = array.length - 1; j >= i; j--) + if (c == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } +} + +static public class ArraySeq_boolean extends ASeq implements IndexedSeq, IReduce{ + final boolean[] array; + final int i; + + ArraySeq_boolean(IPersistentMap meta, boolean[] array, int i){ + super(meta); + this.array = array; + this.i = i; + } + + public Object first(){ + return array[i]; + } + + public ISeq next(){ + if(i + 1 < array.length) + return new ArraySeq_boolean(meta(), array, i + 1); + return null; + } + + public int count(){ + return array.length - i; + } + + public int index(){ + return i; + } + + public ArraySeq_boolean withMeta(IPersistentMap meta){ + return new ArraySeq_boolean(meta, array, i); + } + + public Object reduce(IFn f) throws Exception{ + Object ret = array[i]; + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public Object reduce(IFn f, Object start) throws Exception{ + Object ret = f.invoke(start, array[i]); + for(int x = i + 1; x < array.length; x++) + ret = f.invoke(ret, array[x]); + return ret; + } + + public int indexOf(Object o) { + if (o instanceof Boolean) { + boolean b = ((Boolean) o).booleanValue(); + for (int j = i; j < array.length; j++) + if (b == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = i; j < array.length; j++) + if (o.equals(array[j])) return j - i; + return -1; + } + + public int lastIndexOf(Object o) { + if (o instanceof Boolean) { + boolean b = ((Boolean) o).booleanValue(); + for (int j = array.length - 1; j >= i; j--) + if (b == array[j]) return j - i; + } + if (o == null) { + return -1; + } + for (int j = array.length - 1; j >= i; j--) + if (o.equals(array[j])) return j - i; + return -1; + } } } diff --git a/src/jvm/clojure/lang/Associative.java b/src/jvm/clojure/lang/Associative.java index 891def5f..a2399946 100644 --- a/src/jvm/clojure/lang/Associative.java +++ b/src/jvm/clojure/lang/Associative.java @@ -9,14 +9,11 @@ package clojure.lang; * the terms of this license.
* You must not remove this notice, or any other, from this software.
*/
-public interface Associative extends IPersistentCollection{
+public interface Associative extends IPersistentCollection, ILookup{
boolean containsKey(Object key);
IMapEntry entryAt(Object key);
Associative assoc(Object key, Object val);
-Object valAt(Object key);
-
-Object valAt(Object key, Object notFound);
}
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java index c15d4b2f..c5c9f922 100644 --- a/src/jvm/clojure/lang/Compiler.java +++ b/src/jvm/clojure/lang/Compiler.java @@ -57,7 +57,7 @@ static final Symbol IMPORT = Symbol.create("clojure.core", "import*"); //static final Symbol INSTANCE = Symbol.create("instance?"); //static final Symbol THISFN = Symbol.create("thisfn"); -//static final Symbol CLASS = Symbol.create("class"); +static final Symbol CLASS = Symbol.create("Class"); static final Symbol NEW = Symbol.create("new"); //static final Symbol UNQUOTE = Symbol.create("unquote"); //static final Symbol UNQUOTE_SPLICING = Symbol.create("unquote-splicing"); @@ -179,6 +179,9 @@ static final public Var METHOD = Var.create(null); //null or not static final public Var IN_CATCH_FINALLY = Var.create(null); +//DynamicClassLoader +static final public Var LOADER = Var.create(); + //String static final public Var SOURCE = Var.intern(Namespace.findOrCreate(Symbol.create("clojure.core")), Symbol.create("*source-path*"), "NO_SOURCE_FILE"); @@ -207,8 +210,6 @@ static final public Var NEXT_LOCAL_NUM = Var.create(0); //Integer static final public Var RET_LOCAL_NUM = Var.create(); -//DynamicClassLoader -static final public Var LOADER = Var.create(); public enum C{ STATEMENT, //value ignored @@ -819,7 +820,9 @@ static public abstract class HostExpr implements Expr, MaybePrimitiveExpr{ Symbol sym = (Symbol) tag; if(sym.ns == null) //if ns-qualified can't be classname { - if(sym.name.equals("ints")) + if(sym.name.equals("objects")) + c = Object[].class; + else if(sym.name.equals("ints")) c = int[].class; else if(sym.name.equals("longs")) c = long[].class; @@ -2989,8 +2992,27 @@ static public class ObjExpr implements Expr{ } else if(value instanceof Class) { - gen.push(((Class) value).getName()); - gen.invokeStatic(Type.getType(Class.class), Method.getMethod("Class forName(String)")); + Class cc = (Class)value; + if(cc.isPrimitive()) + { + Type bt; + if ( cc == boolean.class ) bt = Type.getType(Boolean.class); + else if ( cc == byte.class ) bt = Type.getType(Byte.class); + else if ( cc == char.class ) bt = Type.getType(Character.class); + else if ( cc == double.class ) bt = Type.getType(Double.class); + else if ( cc == float.class ) bt = Type.getType(Float.class); + else if ( cc == int.class ) bt = Type.getType(Integer.class); + else if ( cc == long.class ) bt = Type.getType(Long.class); + else if ( cc == short.class ) bt = Type.getType(Short.class); + else throw new RuntimeException( + "Can't embed unknown primitive in code: " + value); + gen.getStatic( bt, "TYPE", Type.getType(Class.class) ); + } + else + { + gen.push(cc.getName()); + gen.invokeStatic(Type.getType(Class.class), Method.getMethod("Class forName(String)")); + } } else if(value instanceof Symbol) { @@ -4266,7 +4288,7 @@ public static Object macroexpand1(Object x) throws Exception{ Object target = RT.second(form); if(HostExpr.maybeClass(target, false) != null) { - target = RT.list(IDENTITY, target); + target = ((IObj)RT.list(IDENTITY, target)).withMeta(RT.map(RT.TAG_KEY,CLASS)); } return RT.listStar(DOT, target, meth, form.next().next()); } diff --git a/src/jvm/clojure/lang/IEditableCollection.java b/src/jvm/clojure/lang/IEditableCollection.java new file mode 100644 index 00000000..63ccb53a --- /dev/null +++ b/src/jvm/clojure/lang/IEditableCollection.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Jul 17, 2009 */ + +package clojure.lang; + +public interface IEditableCollection{ +ITransientCollection asTransient(); +} diff --git a/src/jvm/clojure/lang/ILookup.java b/src/jvm/clojure/lang/ILookup.java new file mode 100644 index 00000000..b124955d --- /dev/null +++ b/src/jvm/clojure/lang/ILookup.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Aug 2, 2009 */ + +package clojure.lang; + +public interface ILookup{ +Object valAt(Object key); + +Object valAt(Object key, Object notFound); +} diff --git a/src/jvm/clojure/lang/ITransientAssociative.java b/src/jvm/clojure/lang/ITransientAssociative.java new file mode 100644 index 00000000..a4d2655a --- /dev/null +++ b/src/jvm/clojure/lang/ITransientAssociative.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Jul 17, 2009 */ + +package clojure.lang; + +public interface ITransientAssociative extends ITransientCollection, ILookup{ + +ITransientAssociative assoc(Object key, Object val); +} diff --git a/src/jvm/clojure/lang/ITransientCollection.java b/src/jvm/clojure/lang/ITransientCollection.java new file mode 100644 index 00000000..0079d33b --- /dev/null +++ b/src/jvm/clojure/lang/ITransientCollection.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Jul 17, 2009 */ + +package clojure.lang; + +public interface ITransientCollection{ + +ITransientCollection conj(Object val); + +IPersistentCollection persistent(); +} diff --git a/src/jvm/clojure/lang/ITransientMap.java b/src/jvm/clojure/lang/ITransientMap.java new file mode 100644 index 00000000..6516b34c --- /dev/null +++ b/src/jvm/clojure/lang/ITransientMap.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Jul 17, 2009 */ + +package clojure.lang; + +public interface ITransientMap extends ITransientAssociative, Counted{ + +ITransientMap assoc(Object key, Object val); + +ITransientMap without(Object key); + +IPersistentMap persistent(); +} diff --git a/src/jvm/clojure/lang/ITransientSet.java b/src/jvm/clojure/lang/ITransientSet.java new file mode 100644 index 00000000..b7a369cb --- /dev/null +++ b/src/jvm/clojure/lang/ITransientSet.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Mar 3, 2008 */ + +package clojure.lang; + +public interface ITransientSet extends ITransientCollection, Counted{ + public ITransientSet disjoin(Object key) throws Exception; + public boolean contains(Object key); + public Object get(Object key); +} diff --git a/src/jvm/clojure/lang/ITransientVector.java b/src/jvm/clojure/lang/ITransientVector.java new file mode 100644 index 00000000..6311754c --- /dev/null +++ b/src/jvm/clojure/lang/ITransientVector.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +/* rich Jul 17, 2009 */ + +package clojure.lang; + +public interface ITransientVector extends ITransientAssociative, Indexed{ + +ITransientVector assocN(int i, Object val); + +ITransientVector pop(); +} diff --git a/src/jvm/clojure/lang/Keyword.java b/src/jvm/clojure/lang/Keyword.java index fcb814f6..890f14d7 100644 --- a/src/jvm/clojure/lang/Keyword.java +++ b/src/jvm/clojure/lang/Keyword.java @@ -12,10 +12,12 @@ package clojure.lang; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.util.concurrent.ConcurrentHashMap; -public class Keyword implements IFn, Comparable, Named{ +public class Keyword implements IFn, Comparable, Named, Serializable { private static ConcurrentHashMap<Symbol, Keyword> table = new ConcurrentHashMap(); public final Symbol sym; @@ -30,6 +32,10 @@ public static Keyword intern(String ns, String name){ return intern(Symbol.intern(ns, name)); } +public static Keyword intern(String nsname){ + return intern(Symbol.intern(nsname)); +} + private Keyword(Symbol sym){ this.sym = sym; } @@ -72,6 +78,10 @@ public String getName(){ return sym.getName(); } +private Object readResolve() throws ObjectStreamException{ + return intern(sym); +} + /** * Indexer implements IFn for attr access * diff --git a/src/jvm/clojure/lang/LazilyPersistentVector.java b/src/jvm/clojure/lang/LazilyPersistentVector.java index f3562cae..44985467 100644 --- a/src/jvm/clojure/lang/LazilyPersistentVector.java +++ b/src/jvm/clojure/lang/LazilyPersistentVector.java @@ -14,141 +14,21 @@ package clojure.lang; import java.util.Collection; -public class LazilyPersistentVector extends APersistentVector{ -final Object[] array; -PersistentVector v = null; +public class LazilyPersistentVector{ + static public IPersistentVector createOwning(Object... items){ if(items.length == 0) return PersistentVector.EMPTY; - return new LazilyPersistentVector(null, items, null); + else if(items.length <= 32) + return new PersistentVector(items.length, 5, PersistentVector.EMPTY_NODE,items); + return PersistentVector.create(items); } static public IPersistentVector create(Collection coll){ - return createOwning(coll.toArray()); -} - -LazilyPersistentVector(IPersistentMap meta, Object[] array, PersistentVector v){ - super(meta); - this.array = array; - this.v = v; -} - -public ISeq seq(){ - if(array.length == 0) - return null; - return new ChunkedSeq(array); -} - -static class ChunkedSeq extends ASeq implements IChunkedSeq, IndexedSeq{ - final Object[] array; - final int offset; - final int end; - static final int BLOCK = 32; - - ChunkedSeq(IPersistentMap meta, Object[] array, int offset, int end){ - super(meta); - this.array = array; - this.offset = offset; - this.end = end; - } - - ChunkedSeq(Object[] array){ - this(array, 0); - } - - ChunkedSeq(Object[] array, int offset){ - this(array,offset,Math.min(offset + BLOCK, array.length)); - } - - ChunkedSeq(Object[] array, int offset, int end){ - this.array = array; - this.offset = offset; - this.end = end; - } - - public Obj withMeta(IPersistentMap meta){ - if(meta != _meta) - return new ChunkedSeq(meta, array,offset,end); - return this; - } - - public Object first(){ - return array[offset]; - } - - public ISeq next(){ - if(offset + 1 < end) - return new ChunkedSeq(array,offset + 1,end); - return chunkedNext(); - } - - public IChunk chunkedFirst(){ - return new ArrayChunk(array, offset, end); - } - - public ISeq chunkedNext(){ - if(end < array.length) - return new ChunkedSeq(array,end); - return null; - } - - public ISeq chunkedMore(){ - ISeq s = chunkedNext(); - if(s == null) - return PersistentList.EMPTY; - return s; - } - - public int index(){ - return offset; - } - - public int count(){ - return array.length - offset; - } -} - -public Object[] toArray(){ - return array.clone(); -} - -public Object nth(int i){ - return array[i]; -} - -public IPersistentVector assocN(int i, Object val){ - return (IPersistentVector) v().assoc(i, val); -} - -public int count(){ - return array.length; -} - -public IPersistentVector cons(Object o){ - return v().cons(o); -} - -public IPersistentCollection empty(){ - return PersistentVector.EMPTY.withMeta(meta()); -} - -public IPersistentStack pop(){ - return v().pop(); -} - -private synchronized IPersistentVector v(){ - if(v == null) - { - v = PersistentVector.create(array); - } - return v; -} - -public Obj withMeta(IPersistentMap meta){ - if(meta != _meta) - return new LazilyPersistentVector(meta, array, v); - return this; + if(!(coll instanceof ISeq) && coll.size() <= 32) + return createOwning(coll.toArray()); + return PersistentVector.create(RT.seq(coll)); } } diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java index 5bf22e66..368bc1fa 100644 --- a/src/jvm/clojure/lang/LispReader.java +++ b/src/jvm/clojure/lang/LispReader.java @@ -704,7 +704,20 @@ public static class SyntaxQuoteReader extends AFn{ // Simply quote method names. } else - sym = Compiler.resolveSymbol(sym); + { + Object maybeClass = null; + if(sym.ns != null) + maybeClass = Compiler.currentNS().getMapping( + Symbol.intern(null, sym.ns)); + if(maybeClass instanceof Class) + { + // Classname/foo -> package.qualified.Classname/foo + sym = Symbol.intern( + ((Class)maybeClass).getName(), sym.name); + } + else + sym = Compiler.resolveSymbol(sym); + } ret = RT.list(Compiler.QUOTE, sym); } else if(isUnquote(form)) diff --git a/src/jvm/clojure/lang/LockingTransaction.java b/src/jvm/clojure/lang/LockingTransaction.java index 4976d169..ab69e223 100644 --- a/src/jvm/clojure/lang/LockingTransaction.java +++ b/src/jvm/clojure/lang/LockingTransaction.java @@ -16,6 +16,8 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.CountDownLatch; @SuppressWarnings({"SynchronizeOnNonFinalField"}) public class LockingTransaction{ @@ -43,11 +45,13 @@ static class AbortException extends Exception{ public static class Info{ final AtomicInteger status; final long startPoint; + final CountDownLatch latch; public Info(int status, long startPoint){ this.status = new AtomicInteger(status); this.startPoint = startPoint; + this.latch = new CountDownLatch(1); } public boolean running(){ @@ -83,7 +87,7 @@ void stop(int status){ synchronized(info) { info.status.set(status); - info.notifyAll(); + info.latch.countDown(); } info = null; vals.clear(); @@ -104,13 +108,32 @@ final HashMap<Ref, Object> vals = new HashMap<Ref, Object>(); final HashSet<Ref> sets = new HashSet<Ref>(); final TreeMap<Ref, ArrayList<CFn>> commutes = new TreeMap<Ref, ArrayList<CFn>>(); +final HashSet<Ref> ensures = new HashSet<Ref>(); //all hold readLock + + +void tryWriteLock(Ref ref){ + try + { + if(!ref.lock.writeLock().tryLock(LOCK_WAIT_MSECS, TimeUnit.MILLISECONDS)) + throw retryex; + } + catch(InterruptedException e) + { + throw retryex; + } +} //returns the most recent val Object lock(Ref ref){ - boolean unlocked = false; + //can't upgrade readLock, so release it + releaseIfEnsured(ref); + + boolean unlocked = true; try { - ref.lock.writeLock().lock(); + tryWriteLock(ref); + unlocked = false; + if(ref.tvals != null && ref.tvals.point > readPoint) throw retryex; Info refinfo = ref.tinfo; @@ -122,22 +145,7 @@ Object lock(Ref ref){ { ref.lock.writeLock().unlock(); unlocked = true; - //stop prior to blocking - stop(RETRY); - synchronized(refinfo) - { - if(refinfo.running()) - { - try - { - refinfo.wait(LOCK_WAIT_MSECS); - } - catch(InterruptedException e) - { - } - } - } - throw retryex; + return blockAndBail(refinfo); } } ref.tinfo = info; @@ -150,6 +158,28 @@ Object lock(Ref ref){ } } +private Object blockAndBail(Info refinfo){ +//stop prior to blocking + stop(RETRY); + try + { + refinfo.latch.await(LOCK_WAIT_MSECS, TimeUnit.MILLISECONDS); + } + catch(InterruptedException e) + { + //ignore + } + throw retryex; +} + +private void releaseIfEnsured(Ref ref){ + if(ensures.contains(ref)) + { + ensures.remove(ref); + ref.lock.readLock().unlock(); + } +} + void abort() throws AbortException{ stop(KILLED); throw new AbortException(); @@ -165,12 +195,9 @@ private boolean barge(Info refinfo){ // try to abort the other if(bargeTimeElapsed() && startPoint < refinfo.startPoint) { - synchronized(refinfo) - { - barged = refinfo.status.compareAndSet(RUNNING, KILLED); - if(barged) - refinfo.notifyAll(); - } + barged = refinfo.status.compareAndSet(RUNNING, KILLED); + if(barged) + refinfo.latch.countDown(); } return barged; } @@ -240,8 +267,16 @@ Object run(Callable fn) throws Exception{ for(Map.Entry<Ref, ArrayList<CFn>> e : commutes.entrySet()) { Ref ref = e.getKey(); - ref.lock.writeLock().lock(); + if(sets.contains(ref)) continue; + + boolean wasEnsured = ensures.contains(ref); + //can't upgrade readLock, so release it + releaseIfEnsured(ref); + tryWriteLock(ref); locked.add(ref); + if(wasEnsured && ref.tvals != null && ref.tvals.point > readPoint) + throw retryex; + Info refinfo = ref.tinfo; if(refinfo != null && refinfo != info && refinfo.running()) { @@ -249,8 +284,7 @@ Object run(Callable fn) throws Exception{ throw retryex; } Object val = ref.tvals == null ? null : ref.tvals.val; - if(!sets.contains(ref)) - vals.put(ref, val); + vals.put(ref, val); for(CFn f : e.getValue()) { vals.put(ref, f.fn.applyTo(RT.cons(vals.get(ref), f.args))); @@ -258,11 +292,8 @@ Object run(Callable fn) throws Exception{ } for(Ref ref : sets) { - if(!commutes.containsKey(ref)) - { - ref.lock.writeLock().lock(); - locked.add(ref); - } + tryWriteLock(ref); + locked.add(ref); } //validate and enqueue notifications @@ -319,6 +350,11 @@ Object run(Callable fn) throws Exception{ locked.get(k).lock.writeLock().unlock(); } locked.clear(); + for(Ref r : ensures) + { + r.lock.readLock().unlock(); + } + ensures.clear(); stop(done ? COMMITTED : RETRY); try { @@ -391,10 +427,33 @@ Object doSet(Ref ref, Object val){ return val; } -void doTouch(Ref ref){ +void doEnsure(Ref ref){ if(!info.running()) throw retryex; - lock(ref); + if(ensures.contains(ref)) + return; + ref.lock.readLock().lock(); + + //someone completed a write after our snapshot + if(ref.tvals != null && ref.tvals.point > readPoint) { + ref.lock.readLock().unlock(); + throw retryex; + } + + Info refinfo = ref.tinfo; + + //writer exists + if(refinfo != null && refinfo.running()) + { + ref.lock.readLock().unlock(); + + if(refinfo != info) //not us, ensure is doomed + { + blockAndBail(refinfo); + } + } + else + ensures.add(ref); } Object doCommute(Ref ref, IFn fn, ISeq args) throws Exception{ diff --git a/src/jvm/clojure/lang/Numbers.java b/src/jvm/clojure/lang/Numbers.java index 043f9ae2..8d5fd855 100644 --- a/src/jvm/clojure/lang/Numbers.java +++ b/src/jvm/clojure/lang/Numbers.java @@ -1436,7 +1436,7 @@ static BitOps bitOps(Object x){ else { ISeq s = RT.seq(sizeOrSeq); - int size = s.count(); + int size = RT.count(s); float[] ret = new float[size]; for(int i = 0; i < size && s != null; i++, s = s.next()) ret[i] = ((Number) s.first()).floatValue(); @@ -1467,7 +1467,7 @@ static public double[] double_array(Object sizeOrSeq){ else { ISeq s = RT.seq(sizeOrSeq); - int size = s.count(); + int size = RT.count(s); double[] ret = new double[size]; for(int i = 0; i < size && s != null; i++, s = s.next()) ret[i] = ((Number) s.first()).doubleValue(); @@ -1498,7 +1498,7 @@ static public int[] int_array(Object sizeOrSeq){ else { ISeq s = RT.seq(sizeOrSeq); - int size = s.count(); + int size = RT.count(s); int[] ret = new int[size]; for(int i = 0; i < size && s != null; i++, s = s.next()) ret[i] = ((Number) s.first()).intValue(); @@ -1529,7 +1529,7 @@ static public long[] long_array(Object sizeOrSeq){ else { ISeq s = RT.seq(sizeOrSeq); - int size = s.count(); + int size = RT.count(s); long[] ret = new long[size]; for(int i = 0; i < size && s != null; i++, s = s.next()) ret[i] = ((Number) s.first()).longValue(); diff --git a/src/jvm/clojure/lang/PersistentArrayMap.java b/src/jvm/clojure/lang/PersistentArrayMap.java index 22757bef..7b7eda58 100644 --- a/src/jvm/clojure/lang/PersistentArrayMap.java +++ b/src/jvm/clojure/lang/PersistentArrayMap.java @@ -1,263 +1,346 @@ -/**
- * Copyright (c) Rich Hickey. All rights reserved.
- * The use and distribution terms for this software are covered by the
- * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
- * which can be found in the file epl-v10.html at the root of this distribution.
- * By using this software in any fashion, you are agreeing to be bound by
- * the terms of this license.
- * You must not remove this notice, or any other, from this software.
- **/
-
-package clojure.lang;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Simple implementation of persistent map on an array
- * <p/>
- * Note that instances of this class are constant values
- * i.e. add/remove etc return new values
- * <p/>
- * Copies array on every change, so only appropriate for _very_small_ maps
- * <p/>
- * null keys and values are ok, but you won't be able to distinguish a null value via valAt - use contains/entryAt
- */
-
-public class PersistentArrayMap extends APersistentMap{
-
-final Object[] array;
-static final int HASHTABLE_THRESHOLD = 16;
-
-public static final PersistentArrayMap EMPTY = new PersistentArrayMap();
-
-static public IPersistentMap create(Map other){
- IPersistentMap ret = EMPTY;
- for(Object o : other.entrySet())
- {
- Map.Entry e = (Entry) o;
- ret = ret.assoc(e.getKey(), e.getValue());
- }
- return ret;
-}
-
-protected PersistentArrayMap(){
- this.array = new Object[]{};
-}
-
-public PersistentArrayMap withMeta(IPersistentMap meta){
- return new PersistentArrayMap(meta, array);
-}
-
-PersistentArrayMap create(Object... init){
- return new PersistentArrayMap(meta(), init);
-}
-
-IPersistentMap createHT(Object[] init){
- return PersistentHashMap.create(meta(), init);
-}
-
-/**
- * This ctor captures/aliases the passed array, so do not modify later
- *
- * @param init {key1,val1,key2,val2,...}
- */
-public PersistentArrayMap(Object[] init){
- this.array = init;
-}
-
-
-public PersistentArrayMap(IPersistentMap meta, Object[] init){
- super(meta);
- this.array = init;
-}
-
-public int count(){
- return array.length / 2;
-}
-
-public boolean containsKey(Object key){
- return indexOf(key) >= 0;
-}
-
-public IMapEntry entryAt(Object key){
- int i = indexOf(key);
- if(i >= 0)
- return new MapEntry(array[i],array[i+1]);
- return null;
-}
-
-public IPersistentMap assocEx(Object key, Object val) throws Exception{
- int i = indexOf(key);
- Object[] newArray;
- if(i >= 0)
- {
- throw new Exception("Key already present");
- }
- else //didn't have key, grow
- {
- if(array.length > HASHTABLE_THRESHOLD)
- return createHT(array).assocEx(key, val);
- newArray = new Object[array.length + 2];
- if(array.length > 0)
- System.arraycopy(array, 0, newArray, 2, array.length);
- newArray[0] = key;
- newArray[1] = val;
- }
- return create(newArray);
-}
-
-public IPersistentMap assoc(Object key, Object val){
- int i = indexOf(key);
- Object[] newArray;
- if(i >= 0) //already have key, same-sized replacement
- {
- if(array[i + 1] == val) //no change, no op
- return this;
- newArray = array.clone();
- newArray[i + 1] = val;
- }
- else //didn't have key, grow
- {
- if(array.length > HASHTABLE_THRESHOLD)
- return createHT(array).assoc(key, val);
- newArray = new Object[array.length + 2];
- if(array.length > 0)
- System.arraycopy(array, 0, newArray, 2, array.length);
- newArray[0] = key;
- newArray[1] = val;
- }
- return create(newArray);
-}
-
-public IPersistentMap without(Object key){
- int i = indexOf(key);
- if(i >= 0) //have key, will remove
- {
- int newlen = array.length - 2;
- if(newlen == 0)
- return empty();
- Object[] newArray = new Object[newlen];
- for(int s = 0, d = 0; s < array.length; s += 2)
- {
- if(!equalKey(array[s], key)) //skip removal key
- {
- newArray[d] = array[s];
- newArray[d + 1] = array[s + 1];
- d += 2;
- }
- }
- return create(newArray);
- }
- //don't have key, no op
- return this;
-}
-
-public IPersistentMap empty(){
- return (IPersistentMap) EMPTY.withMeta(meta());
-}
-
-final public Object valAt(Object key, Object notFound){
- int i = indexOf(key);
- if(i >= 0)
- return array[i + 1];
- return notFound;
-}
-
-public Object valAt(Object key){
- return valAt(key, null);
-}
-
-public int capacity(){
- return count();
-}
-
-private int indexOf(Object key){
- for(int i = 0; i < array.length; i += 2)
- {
- if(equalKey(array[i], key))
- return i;
- }
- return -1;
-}
-
-boolean equalKey(Object k1, Object k2){
- if(k1 == null)
- return k2 == null;
- return k1.equals(k2);
-}
-
-public Iterator iterator(){
- return new Iter(array);
-}
-
-public ISeq seq(){
- if(array.length > 0)
- return new Seq(array, 0);
- return null;
-}
-
-static class Seq extends ASeq implements Counted{
- final Object[] array;
- final int i;
-
- Seq(Object[] array, int i){
- this.array = array;
- this.i = i;
- }
-
- public Seq(IPersistentMap meta, Object[] array, int i){
- super(meta);
- this.array = array;
- this.i = i;
- }
-
- public Object first(){
- return new MapEntry(array[i],array[i+1]);
- }
-
- public ISeq next(){
- if(i + 2 < array.length)
- return new Seq(array, i + 2);
- return null;
- }
-
- public int count(){
- return (array.length - i) / 2;
- }
-
- public Obj withMeta(IPersistentMap meta){
- return new Seq(meta, array, i);
- }
-}
-
-static class Iter implements Iterator{
- Object[] array;
- int i;
-
- //for iterator
- Iter(Object[] array){
- this(array, -2);
- }
-
- //for entryAt
- Iter(Object[] array, int i){
- this.array = array;
- this.i = i;
- }
-
- public boolean hasNext(){
- return i < array.length - 2;
- }
-
- public Object next(){
- i += 2;
- return new MapEntry(array[i],array[i+1]);
- }
-
- public void remove(){
- throw new UnsupportedOperationException();
- }
-
-}
-}
+/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + **/ + +package clojure.lang; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; + +/** + * Simple implementation of persistent map on an array + * <p/> + * Note that instances of this class are constant values + * i.e. add/remove etc return new values + * <p/> + * Copies array on every change, so only appropriate for _very_small_ maps + * <p/> + * null keys and values are ok, but you won't be able to distinguish a null value via valAt - use contains/entryAt + */ + +public class PersistentArrayMap extends APersistentMap implements IEditableCollection { + +final Object[] array; +static final int HASHTABLE_THRESHOLD = 16; + +public static final PersistentArrayMap EMPTY = new PersistentArrayMap(); + +static public IPersistentMap create(Map other){ + ITransientMap ret = EMPTY.asTransient(); + for(Object o : other.entrySet()) + { + Map.Entry e = (Entry) o; + ret = ret.assoc(e.getKey(), e.getValue()); + } + return ret.persistent(); +} + +protected PersistentArrayMap(){ + this.array = new Object[]{}; +} + +public PersistentArrayMap withMeta(IPersistentMap meta){ + return new PersistentArrayMap(meta, array); +} + +PersistentArrayMap create(Object... init){ + return new PersistentArrayMap(meta(), init); +} + +IPersistentMap createHT(Object[] init){ + return PersistentHashMap.create(meta(), init); +} + +/** + * This ctor captures/aliases the passed array, so do not modify later + * + * @param init {key1,val1,key2,val2,...} + */ +public PersistentArrayMap(Object[] init){ + this.array = init; +} + + +public PersistentArrayMap(IPersistentMap meta, Object[] init){ + super(meta); + this.array = init; +} + +public int count(){ + return array.length / 2; +} + +public boolean containsKey(Object key){ + return indexOf(key) >= 0; +} + +public IMapEntry entryAt(Object key){ + int i = indexOf(key); + if(i >= 0) + return new MapEntry(array[i],array[i+1]); + return null; +} + +public IPersistentMap assocEx(Object key, Object val) throws Exception{ + int i = indexOf(key); + Object[] newArray; + if(i >= 0) + { + throw new Exception("Key already present"); + } + else //didn't have key, grow + { + if(array.length > HASHTABLE_THRESHOLD) + return createHT(array).assocEx(key, val); + newArray = new Object[array.length + 2]; + if(array.length > 0) + System.arraycopy(array, 0, newArray, 2, array.length); + newArray[0] = key; + newArray[1] = val; + } + return create(newArray); +} + +public IPersistentMap assoc(Object key, Object val){ + int i = indexOf(key); + Object[] newArray; + if(i >= 0) //already have key, same-sized replacement + { + if(array[i + 1] == val) //no change, no op + return this; + newArray = array.clone(); + newArray[i + 1] = val; + } + else //didn't have key, grow + { + if(array.length > HASHTABLE_THRESHOLD) + return createHT(array).assoc(key, val); + newArray = new Object[array.length + 2]; + if(array.length > 0) + System.arraycopy(array, 0, newArray, 2, array.length); + newArray[0] = key; + newArray[1] = val; + } + return create(newArray); +} + +public IPersistentMap without(Object key){ + int i = indexOf(key); + if(i >= 0) //have key, will remove + { + int newlen = array.length - 2; + if(newlen == 0) + return empty(); + Object[] newArray = new Object[newlen]; + for(int s = 0, d = 0; s < array.length; s += 2) + { + if(!equalKey(array[s], key)) //skip removal key + { + newArray[d] = array[s]; + newArray[d + 1] = array[s + 1]; + d += 2; + } + } + return create(newArray); + } + //don't have key, no op + return this; +} + +public IPersistentMap empty(){ + return (IPersistentMap) EMPTY.withMeta(meta()); +} + +final public Object valAt(Object key, Object notFound){ + int i = indexOf(key); + if(i >= 0) + return array[i + 1]; + return notFound; +} + +public Object valAt(Object key){ + return valAt(key, null); +} + +public int capacity(){ + return count(); +} + +private int indexOf(Object key){ + for(int i = 0; i < array.length; i += 2) + { + if(equalKey(array[i], key)) + return i; + } + return -1; +} + +static boolean equalKey(Object k1, Object k2){ + if(k1 == null) + return k2 == null; + return k1.equals(k2); +} + +public Iterator iterator(){ + return new Iter(array); +} + +public ISeq seq(){ + if(array.length > 0) + return new Seq(array, 0); + return null; +} + +static class Seq extends ASeq implements Counted{ + final Object[] array; + final int i; + + Seq(Object[] array, int i){ + this.array = array; + this.i = i; + } + + public Seq(IPersistentMap meta, Object[] array, int i){ + super(meta); + this.array = array; + this.i = i; + } + + public Object first(){ + return new MapEntry(array[i],array[i+1]); + } + + public ISeq next(){ + if(i + 2 < array.length) + return new Seq(array, i + 2); + return null; + } + + public int count(){ + return (array.length - i) / 2; + } + + public Obj withMeta(IPersistentMap meta){ + return new Seq(meta, array, i); + } +} + +static class Iter implements Iterator{ + Object[] array; + int i; + + //for iterator + Iter(Object[] array){ + this(array, -2); + } + + //for entryAt + Iter(Object[] array, int i){ + this.array = array; + this.i = i; + } + + public boolean hasNext(){ + return i < array.length - 2; + } + + public Object next(){ + i += 2; + return new MapEntry(array[i],array[i+1]); + } + + public void remove(){ + throw new UnsupportedOperationException(); + } + +} + +public ITransientMap asTransient(){ + return new TransientArrayMap(array); +} + +static final class TransientArrayMap extends ATransientMap { + int len; + final Object[] array; + final Thread owner; + + public TransientArrayMap(Object[] array){ + this.owner = Thread.currentThread(); + this.array = new Object[Math.max(HASHTABLE_THRESHOLD, array.length)]; + System.arraycopy(array, 0, this.array, 0, array.length); + this.len = array.length; + } + + private int indexOf(Object key){ + for(int i = 0; i < len; i += 2) + { + if(equalKey(array[i], key)) + return i; + } + return -1; + } + + ITransientMap doAssoc(Object key, Object val){ + int i = indexOf(key); + if(i >= 0) //already have key, + { + if(array[i + 1] != val) //no change, no op + array[i + 1] = val; + } + else //didn't have key, grow + { + if(len >= array.length) + return PersistentHashMap.create(array).asTransient().assoc(key, val); + array[len++] = key; + array[len++] = val; + } + return this; + } + + ITransientMap doWithout(Object key) { + int i = indexOf(key); + if(i >= 0) //have key, will remove + { + if (len >= 2) + { + array[i] = array[len - 2]; + array[i + 1] = array[len - 1]; + } + len -= 2; + } + return this; + } + + Object doValAt(Object key, Object notFound) { + int i = indexOf(key); + if (i >= 0) + return array[i + 1]; + return notFound; + } + + int doCount() { + return len / 2; + } + + IPersistentMap doPersistent(){ + Object[] a = new Object[len]; + System.arraycopy(array,0,a,0,len); + return new PersistentArrayMap(a); + } + + void ensureEditable(){ + if(owner == Thread.currentThread()) + return; + if(owner != null) + throw new IllegalAccessError("Mutable used by non-owner thread"); + throw new IllegalAccessError("Mutable used after immutable call"); + } +} +} diff --git a/src/jvm/clojure/lang/PersistentHashMap.java b/src/jvm/clojure/lang/PersistentHashMap.java index 0705ffd7..5f5f5404 100644 --- a/src/jvm/clojure/lang/PersistentHashMap.java +++ b/src/jvm/clojure/lang/PersistentHashMap.java @@ -1,43 +1,19 @@ /** - Copyright (c) 2007-2008, Rich Hickey - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Clojure nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. **/ -/* rich Jun 18, 2007 */ - package clojure.lang; -import java.util.*; -//this stuff is just for the test main() +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /* A persistent rendition of Phil Bagwell's Hash Array Mapped Trie @@ -49,37 +25,40 @@ import java.util.*; Any errors are my own */ -public class PersistentHashMap extends APersistentMap{ +public class PersistentHashMap extends APersistentMap implements IEditableCollection { final int count; final INode root; +final boolean hasNull; +final Object nullValue; -final public static PersistentHashMap EMPTY = new PersistentHashMap(0, new EmptyNode()); +final public static PersistentHashMap EMPTY = new PersistentHashMap(0, null, false, null); +final private static Object NOT_FOUND = new Object(); static public IPersistentMap create(Map other){ - IPersistentMap ret = EMPTY; + ITransientMap ret = EMPTY.asTransient(); for(Object o : other.entrySet()) { Map.Entry e = (Entry) o; ret = ret.assoc(e.getKey(), e.getValue()); } - return ret; + return ret.persistent(); } /* * @param init {key1,val1,key2,val2,...} */ public static PersistentHashMap create(Object... init){ - IPersistentMap ret = EMPTY; + ITransientMap ret = EMPTY.asTransient(); for(int i = 0; i < init.length; i += 2) { ret = ret.assoc(init[i], init[i + 1]); } - return (PersistentHashMap) ret; + return (PersistentHashMap) ret.persistent(); } public static PersistentHashMap create(List init){ - IPersistentMap ret = EMPTY; + ITransientMap ret = EMPTY.asTransient(); for(Iterator i = init.iterator(); i.hasNext();) { Object key = i.next(); @@ -88,65 +67,72 @@ public static PersistentHashMap create(List init){ Object val = i.next(); ret = ret.assoc(key, val); } - return (PersistentHashMap) ret; + return (PersistentHashMap) ret.persistent(); } static public PersistentHashMap create(ISeq items){ - IPersistentMap ret = EMPTY; + ITransientMap ret = EMPTY.asTransient(); for(; items != null; items = items.next().next()) { if(items.next() == null) throw new IllegalArgumentException(String.format("No value supplied for key: %s", items.first())); ret = ret.assoc(items.first(), RT.second(items)); } - return (PersistentHashMap) ret; + return (PersistentHashMap) ret.persistent(); } /* * @param init {key1,val1,key2,val2,...} */ public static PersistentHashMap create(IPersistentMap meta, Object... init){ - IPersistentMap ret = EMPTY.withMeta(meta); - for(int i = 0; i < init.length; i += 2) - { - ret = ret.assoc(init[i], init[i + 1]); - } - return (PersistentHashMap) ret; + return create(init).withMeta(meta); } -PersistentHashMap(int count, INode root){ +PersistentHashMap(int count, INode root, boolean hasNull, Object nullValue){ this.count = count; this.root = root; + this.hasNull = hasNull; + this.nullValue = nullValue; } - -public PersistentHashMap(IPersistentMap meta, int count, INode root){ +public PersistentHashMap(IPersistentMap meta, int count, INode root, boolean hasNull, Object nullValue){ super(meta); this.count = count; this.root = root; + this.hasNull = hasNull; + this.nullValue = nullValue; } public boolean containsKey(Object key){ - return entryAt(key) != null; + if(key == null) + return hasNull; + return (root != null) ? root.find(0, Util.hash(key), key, NOT_FOUND) != NOT_FOUND : false; } public IMapEntry entryAt(Object key){ - return root.find(Util.hash(key), key); + if(key == null) + return hasNull ? new MapEntry(null, nullValue) : null; + return (root != null) ? root.find(0, Util.hash(key), key) : null; } public IPersistentMap assoc(Object key, Object val){ + if(key == null) { + if(hasNull && val == nullValue) + return this; + return new PersistentHashMap(meta(), hasNull ? count : count + 1, root, true, val); + } Box addedLeaf = new Box(null); - INode newroot = root.assoc(0, Util.hash(key), key, val, addedLeaf); + INode newroot = (root == null ? BitmapIndexedNode.EMPTY : root) + .assoc(0, Util.hash(key), key, val, addedLeaf); if(newroot == root) return this; - return new PersistentHashMap(meta(), addedLeaf.val == null ? count : count + 1, newroot); + return new PersistentHashMap(meta(), addedLeaf.val == null ? count : count + 1, newroot, hasNull, nullValue); } public Object valAt(Object key, Object notFound){ - IMapEntry e = entryAt(key); - if(e != null) - return e.val(); - return notFound; + if(key == null) + return hasNull ? nullValue : notFound; + return root != null ? root.find(0, Util.hash(key), key, notFound) : notFound; } public Object valAt(Object key){ @@ -160,12 +146,14 @@ public IPersistentMap assocEx(Object key, Object val) throws Exception{ } public IPersistentMap without(Object key){ - INode newroot = root.without(Util.hash(key), key); + if(key == null) + return hasNull ? new PersistentHashMap(meta(), count - 1, root, false, null) : this; + if(root == null) + return this; + INode newroot = root.without(0, Util.hash(key), key); if(newroot == root) return this; - if(newroot == null) - return EMPTY.withMeta(meta()); - return new PersistentHashMap(meta(), count - 1, newroot); + return new PersistentHashMap(meta(), count - 1, newroot, hasNull, nullValue); } public Iterator iterator(){ @@ -177,7 +165,8 @@ public int count(){ } public ISeq seq(){ - return root.nodeSeq(); + ISeq s = root != null ? root.nodeSeq() : null; + return hasNull ? new Cons(new MapEntry(null, nullValue), s) : s; } public IPersistentCollection empty(){ @@ -190,469 +179,666 @@ static int mask(int hash, int shift){ } public PersistentHashMap withMeta(IPersistentMap meta){ - return new PersistentHashMap(meta, count, root); -} - -/* -final static int[] pathmasks = new int[32]; -static{ - pathmasks[0] = 0; - for(int i=1;i<32;i++) - pathmasks[i] = 0x80000000 >> (i - 1); + return new PersistentHashMap(meta, count, root, hasNull, nullValue); } -//final static int pathmask(int hash, int shift){ -// //return hash & (0x80000000 >> (shift - 1)); -// return hash & pathmasks[shift]; -// } -static boolean diffPath(int shift, int hash1, int hash2){ - return shift > 0 && ((hash1^hash2) & pathmasks[shift]) != 0; - //return shift > 0 && pathmask(hash1^hash2,shift) != 0; +public TransientHashMap asTransient() { + return new TransientHashMap(this); } -*/ -static interface INode{ - INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf); - INode without(int hash, Object key); +static final class TransientHashMap extends ATransientMap { + AtomicReference<Thread> edit; + INode root; + int count; + boolean hasNull; + Object nullValue; + final Box leafFlag = new Box(null); - LeafNode find(int hash, Object key); - ISeq nodeSeq(); - - int getHash(); -} - -/* -static interface ILeaf extends INode{ - int getHash(); -} -*/ - -final static class EmptyNode implements INode{ + TransientHashMap(PersistentHashMap m) { + this(new AtomicReference<Thread>(Thread.currentThread()), m.root, m.count, m.hasNull, m.nullValue); + } + + TransientHashMap(AtomicReference<Thread> edit, INode root, int count, boolean hasNull, Object nullValue) { + this.edit = edit; + this.root = root; + this.count = count; + this.hasNull = hasNull; + this.nullValue = nullValue; + } - public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){ - INode ret = new LeafNode(hash, key, val); - addedLeaf.val = ret; - return ret; + ITransientMap doAssoc(Object key, Object val) { + if (key == null) { + if (this.nullValue != val) + this.nullValue = val; + if (!hasNull) { + this.count++; + this.hasNull = true; + } + return this; + } +// Box leafFlag = new Box(null); + leafFlag.val = null; + INode n = (root == null ? BitmapIndexedNode.EMPTY : root) + .assoc(edit, 0, Util.hash(key), key, val, leafFlag); + if (n != this.root) + this.root = n; + if(leafFlag.val != null) this.count++; + return this; } - public INode without(int hash, Object key){ + ITransientMap doWithout(Object key) { + if (key == null) { + if (!hasNull) return this; + hasNull = false; + nullValue = null; + this.count--; + return this; + } + if (root == null) return this; +// Box leafFlag = new Box(null); + leafFlag.val = null; + INode n = root.without(edit, 0, Util.hash(key), key, leafFlag); + if (n != root) + this.root = n; + if(leafFlag.val != null) this.count--; return this; } - public LeafNode find(int hash, Object key){ - return null; + IPersistentMap doPersistent() { + edit.set(null); + return new PersistentHashMap(count, root, hasNull, nullValue); } - public ISeq nodeSeq(){ - return null; + Object doValAt(Object key, Object notFound) { + if (key == null) + if (hasNull) + return nullValue; + else + return notFound; + if (root == null) + return null; + return root.find(0, Util.hash(key), key, notFound); } - public int getHash(){ - return 0; + int doCount() { + return count; + } + + void ensureEditable(){ + Thread owner = edit.get(); + if(owner == Thread.currentThread()) + return; + if(owner != null) + throw new IllegalAccessError("Mutable used by non-owner thread"); + throw new IllegalAccessError("Mutable used after immutable call"); } } -final static class FullNode implements INode{ - final INode[] nodes; - final int shift; - final int _hash; +static interface INode{ + INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf); + INode without(int shift, int hash, Object key); - static int bitpos(int hash, int shift){ - return 1 << mask(hash, shift); - } + IMapEntry find(int shift, int hash, Object key); + + Object find(int shift, int hash, Object key, Object notFound); + + ISeq nodeSeq(); + + INode assoc(AtomicReference<Thread> edit, int shift, int hash, Object key, Object val, Box addedLeaf); - FullNode(INode[] nodes, int shift){ - this.nodes = nodes; - this.shift = shift; - this._hash = nodes[0].getHash(); + INode without(AtomicReference<Thread> edit, int shift, int hash, Object key, Box removedLeaf); +} + +final static class ArrayNode implements INode{ + int count; + final INode[] array; + final AtomicReference<Thread> edit; + + ArrayNode(AtomicReference<Thread> edit, int count, INode[] array){ + this.array = array; + this.edit = edit; + this.count = count; } - public INode assoc(int levelShift, int hash, Object key, Object val, Box addedLeaf){ -// if(levelShift < shift && diffPath(shift,hash,_hash)) -// return BitmapIndexedNode.create(levelShift, this, hash, key, val, addedLeaf); + public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){ int idx = mask(hash, shift); - - INode n = nodes[idx].assoc(shift + 5, hash, key, val, addedLeaf); - if(n == nodes[idx]) + INode node = array[idx]; + if(node == null) + return new ArrayNode(null, count + 1, cloneAndSet(array, idx, BitmapIndexedNode.EMPTY.assoc(shift + 5, hash, key, val, addedLeaf))); + INode n = node.assoc(shift + 5, hash, key, val, addedLeaf); + if(n == node) return this; - else - { - INode[] newnodes = nodes.clone(); - newnodes[idx] = n; - return new FullNode(newnodes, shift); - } + return new ArrayNode(null, count, cloneAndSet(array, idx, n)); } - public INode without(int hash, Object key){ -// if(diffPath(shift,hash,_hash)) -// return this; + public INode without(int shift, int hash, Object key){ int idx = mask(hash, shift); - INode n = nodes[idx].without(hash, key); - if(n != nodes[idx]) - { - if(n == null) - { - INode[] newnodes = new INode[nodes.length - 1]; - System.arraycopy(nodes, 0, newnodes, 0, idx); - System.arraycopy(nodes, idx + 1, newnodes, idx, nodes.length - (idx + 1)); - return new BitmapIndexedNode(~bitpos(hash, shift), newnodes, shift); - } - INode[] newnodes = nodes.clone(); - newnodes[idx] = n; - return new FullNode(newnodes, shift); - } - return this; + INode node = array[idx]; + if(node == null) + return this; + INode n = node.without(shift + 5, hash, key); + if(n == node) + return this; + if (n == null) { + if (count <= 8) // shrink + return pack(null, idx); + return new ArrayNode(null, count - 1, cloneAndSet(array, idx, n)); + } else + return new ArrayNode(null, count, cloneAndSet(array, idx, n)); } - public LeafNode find(int hash, Object key){ -// if(diffPath(shift,hash,_hash)) -// return null; - return nodes[mask(hash, shift)].find(hash, key); + public IMapEntry find(int shift, int hash, Object key){ + int idx = mask(hash, shift); + INode node = array[idx]; + if(node == null) + return null; + return node.find(shift + 5, hash, key); } + public Object find(int shift, int hash, Object key, Object notFound){ + int idx = mask(hash, shift); + INode node = array[idx]; + if(node == null) + return notFound; + return node.find(shift + 5, hash, key, notFound); + } + public ISeq nodeSeq(){ - return Seq.create(this, 0); + return Seq.create(array); } - public int getHash(){ - return _hash; + private ArrayNode ensureEditable(AtomicReference<Thread> edit){ + if(this.edit == edit) + return this; + return new ArrayNode(edit, count, this.array.clone()); + } + + private ArrayNode editAndSet(AtomicReference<Thread> edit, int i, INode n){ + ArrayNode editable = ensureEditable(edit); + editable.array[i] = n; + return editable; } - static class Seq extends ASeq{ - final ISeq s; - final int i; - final FullNode node; + private INode pack(AtomicReference<Thread> edit, int idx) { + Object[] newArray = new Object[2*(count - 1)]; + int j = 1; + int bitmap = 0; + for(int i = 0; i < idx; i++) + if (array[i] != null) { + newArray[j] = array[i]; + bitmap |= 1 << i; + j += 2; + } + for(int i = idx + 1; i < array.length; i++) + if (array[i] != null) { + newArray[j] = array[i]; + bitmap |= 1 << i; + j += 2; + } + return new BitmapIndexedNode(edit, bitmap, newArray); + } - Seq(ISeq s, int i, FullNode node){ - this.s = s; - this.i = i; - this.node = node; + public INode assoc(AtomicReference<Thread> edit, int shift, int hash, Object key, Object val, Box addedLeaf){ + int idx = mask(hash, shift); + INode node = array[idx]; + if(node == null) { + ArrayNode editable = editAndSet(edit, idx, BitmapIndexedNode.EMPTY.assoc(edit, shift + 5, hash, key, val, addedLeaf)); + editable.count++; + return editable; } + INode n = node.assoc(edit, shift + 5, hash, key, val, addedLeaf); + if(n == node) + return this; + return editAndSet(edit, idx, n); + } - Seq(IPersistentMap meta, ISeq s, int i, FullNode node){ + public INode without(AtomicReference<Thread> edit, int shift, int hash, Object key, Box removedLeaf){ + int idx = mask(hash, shift); + INode node = array[idx]; + if(node == null) + return this; + INode n = node.without(edit, shift + 5, hash, key, removedLeaf); + if(n == node) + return this; + if(n == null) { + if (count <= 8) // shrink + return pack(edit, idx); + ArrayNode editable = editAndSet(edit, idx, n); + editable.count--; + return editable; + } + return editAndSet(edit, idx, n); + } + + static class Seq extends ASeq { + final INode[] nodes; + final int i; + final ISeq s; + + static ISeq create(INode[] nodes) { + return create(null, nodes, 0, null); + } + + private static ISeq create(IPersistentMap meta, INode[] nodes, int i, ISeq s) { + if (s != null) + return new Seq(meta, nodes, i, s); + for(int j = i; j < nodes.length; j++) + if (nodes[j] != null) { + ISeq ns = nodes[j].nodeSeq(); + if (ns != null) + return new Seq(meta, nodes, j + 1, ns); + } + return null; + } + + private Seq(IPersistentMap meta, INode[] nodes, int i, ISeq s) { super(meta); - this.s = s; + this.nodes = nodes; this.i = i; - this.node = node; + this.s = s; } - static ISeq create(FullNode node, int i){ - if(i >= node.nodes.length) - return null; - return new Seq(node.nodes[i].nodeSeq(), i, node); + public Obj withMeta(IPersistentMap meta) { + return new Seq(meta, nodes, i, s); } - public Object first(){ + public Object first() { return s.first(); } - public ISeq next(){ - ISeq nexts = s.next(); - if(nexts != null) - return new Seq(nexts, i, node); - return create(node, i + 1); + public ISeq next() { + return create(null, nodes, i, s.next()); } - - public Seq withMeta(IPersistentMap meta){ - return new Seq(meta, s, i, node); - } - } - - + + } } final static class BitmapIndexedNode implements INode{ - final int bitmap; - final INode[] nodes; - final int shift; - final int _hash; - - static int bitpos(int hash, int shift){ - return 1 << mask(hash, shift); - } + static final BitmapIndexedNode EMPTY = new BitmapIndexedNode(null, 0, new Object[0]); + + int bitmap; + Object[] array; + final AtomicReference<Thread> edit; final int index(int bit){ return Integer.bitCount(bitmap & (bit - 1)); } - - BitmapIndexedNode(int bitmap, INode[] nodes, int shift){ + BitmapIndexedNode(AtomicReference<Thread> edit, int bitmap, Object[] array){ this.bitmap = bitmap; - this.nodes = nodes; - this.shift = shift; - this._hash = nodes[0].getHash(); - } - - static INode create(int bitmap, INode[] nodes, int shift){ - if(bitmap == -1) - return new FullNode(nodes, shift); - return new BitmapIndexedNode(bitmap, nodes, shift); - } - - static INode create(int shift, INode branch, int hash, Object key, Object val, Box addedLeaf){ -// int hx = branch.getHash()^hash; -// while(mask(hx,shift) == 0) -// shift += 5; -// if(mask(branch.getHash(),shift) == mask(hash,shift)) -// return create(shift+5,branch,hash,key,val,addedLeaf); - return (new BitmapIndexedNode(bitpos(branch.getHash(), shift), new INode[]{branch}, shift)) - .assoc(shift, hash, key, val, addedLeaf); + this.array = array; + this.edit = edit; } - public INode assoc(int levelShift, int hash, Object key, Object val, Box addedLeaf){ -// if(levelShift < shift && diffPath(shift,hash,_hash)) -// return create(levelShift, this, hash, key, val, addedLeaf); + public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){ int bit = bitpos(hash, shift); int idx = index(bit); - if((bitmap & bit) != 0) - { - INode n = nodes[idx].assoc(shift + 5, hash, key, val, addedLeaf); - if(n == nodes[idx]) - return this; - else - { - INode[] newnodes = nodes.clone(); - newnodes[idx] = n; - return new BitmapIndexedNode(bitmap, newnodes, shift); - } - } - else - { - INode[] newnodes = new INode[nodes.length + 1]; - System.arraycopy(nodes, 0, newnodes, 0, idx); - addedLeaf.val = newnodes[idx] = new LeafNode(hash, key, val); - System.arraycopy(nodes, idx, newnodes, idx + 1, nodes.length - idx); - return create(bitmap | bit, newnodes, shift); + if((bitmap & bit) != 0) { + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) { + INode n = ((INode) valOrNode).assoc(shift + 5, hash, key, val, addedLeaf); + if(n == valOrNode) + return this; + return new BitmapIndexedNode(null, bitmap, cloneAndSet(array, 2*idx+1, n)); + } + if(Util.equals(key, keyOrNull)) { + if(val == valOrNode) + return this; + return new BitmapIndexedNode(null, bitmap, cloneAndSet(array, 2*idx+1, val)); + } + addedLeaf.val = addedLeaf; + return new BitmapIndexedNode(null, bitmap, + cloneAndSet(array, + 2*idx, null, + 2*idx+1, createNode(shift + 5, keyOrNull, valOrNode, hash, key, val))); + } else { + int n = Integer.bitCount(bitmap); + if(n >= 16) { + INode[] nodes = new INode[32]; + int jdx = mask(hash, shift); + nodes[jdx] = EMPTY.assoc(shift + 5, hash, key, val, addedLeaf); + int j = 0; + for(int i = 0; i < 32; i++) + if(((bitmap >>> i) & 1) != 0) { + if (array[j] == null) + nodes[i] = (INode) array[j+1]; + else + nodes[i] = EMPTY.assoc(shift + 5, Util.hash(array[j]), array[j], array[j+1], addedLeaf); + j += 2; + } + return new ArrayNode(null, n + 1, nodes); + } else { + Object[] newArray = new Object[2*(n+1)]; + System.arraycopy(array, 0, newArray, 0, 2*idx); + newArray[2*idx] = key; + addedLeaf.val = addedLeaf; + newArray[2*idx+1] = val; + System.arraycopy(array, 2*idx, newArray, 2*(idx+1), 2*(n-idx)); + return new BitmapIndexedNode(null, bitmap | bit, newArray); } + } } - public INode without(int hash, Object key){ -// if(diffPath(shift,hash,_hash)) -// return this; + public INode without(int shift, int hash, Object key){ int bit = bitpos(hash, shift); - if((bitmap & bit) != 0) - { - int idx = index(bit); - INode n = nodes[idx].without(hash, key); - if(n != nodes[idx]) - { - if(n == null) - { - if(bitmap == bit) - return null; -// if(nodes.length == 2) -// return nodes[idx == 0?1:0]; - INode[] newnodes = new INode[nodes.length - 1]; - System.arraycopy(nodes, 0, newnodes, 0, idx); - System.arraycopy(nodes, idx + 1, newnodes, idx, nodes.length - (idx + 1)); - return new BitmapIndexedNode(bitmap & ~bit, newnodes, shift); - } - INode[] newnodes = nodes.clone(); - newnodes[idx] = n; - return new BitmapIndexedNode(bitmap, newnodes, shift); - } - } + if((bitmap & bit) == 0) + return this; + int idx = index(bit); + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) { + INode n = ((INode) valOrNode).without(shift + 5, hash, key); + if (n == valOrNode) + return this; + if (n != null) + return new BitmapIndexedNode(null, bitmap, cloneAndSet(array, 2*idx+1, n)); + if (bitmap == bit) + return null; + return new BitmapIndexedNode(null, bitmap ^ bit, removePair(array, idx)); + } + if(Util.equals(key, keyOrNull)) + // TODO: collapse + return new BitmapIndexedNode(null, bitmap ^ bit, removePair(array, idx)); return this; } - - public LeafNode find(int hash, Object key){ -// if(diffPath(shift,hash,_hash)) -// return null; + + public IMapEntry find(int shift, int hash, Object key){ int bit = bitpos(hash, shift); - if((bitmap & bit) != 0) - { - return nodes[index(bit)].find(hash, key); - } - else + if((bitmap & bit) == 0) return null; + int idx = index(bit); + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) + return ((INode) valOrNode).find(shift + 5, hash, key); + if(Util.equals(key, keyOrNull)) + return new MapEntry(keyOrNull, valOrNode); + return null; } - public int getHash(){ - return _hash; + public Object find(int shift, int hash, Object key, Object notFound){ + int bit = bitpos(hash, shift); + if((bitmap & bit) == 0) + return notFound; + int idx = index(bit); + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) + return ((INode) valOrNode).find(shift + 5, hash, key, notFound); + if(Util.equals(key, keyOrNull)) + return valOrNode; + return notFound; } public ISeq nodeSeq(){ - return Seq.create(this, 0); + return NodeSeq.create(array); } - static class Seq extends ASeq{ - final ISeq s; - final int i; - final BitmapIndexedNode node; + private BitmapIndexedNode ensureEditable(AtomicReference<Thread> edit){ + if(this.edit == edit) + return this; + int n = Integer.bitCount(bitmap); + Object[] newArray = new Object[n >= 0 ? 2*(n+1) : 4]; // make room for next assoc + System.arraycopy(array, 0, newArray, 0, 2*n); + return new BitmapIndexedNode(edit, bitmap, newArray); + } + + private BitmapIndexedNode editAndSet(AtomicReference<Thread> edit, int i, Object a) { + BitmapIndexedNode editable = ensureEditable(edit); + editable.array[i] = a; + return editable; + } + private BitmapIndexedNode editAndSet(AtomicReference<Thread> edit, int i, Object a, int j, Object b) { + BitmapIndexedNode editable = ensureEditable(edit); + editable.array[i] = a; + editable.array[j] = b; + return editable; + } - Seq(ISeq s, int i, BitmapIndexedNode node){ - this.s = s; - this.i = i; - this.node = node; - } + private BitmapIndexedNode editAndRemovePair(AtomicReference<Thread> edit, int bit, int i) { + if (bitmap == bit) + return null; + BitmapIndexedNode editable = ensureEditable(edit); + editable.bitmap ^= bit; + System.arraycopy(editable.array, 2*(i+1), editable.array, 2*i, editable.array.length - 2*(i+1)); + editable.array[editable.array.length - 2] = null; + editable.array[editable.array.length - 1] = null; + return editable; + } - Seq(IPersistentMap meta, ISeq s, int i, BitmapIndexedNode node){ - super(meta); - this.s = s; - this.i = i; - this.node = node; + public INode assoc(AtomicReference<Thread> edit, int shift, int hash, Object key, Object val, Box addedLeaf){ + int bit = bitpos(hash, shift); + int idx = index(bit); + if((bitmap & bit) != 0) { + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) { + INode n = ((INode) valOrNode).assoc(edit, shift + 5, hash, key, val, addedLeaf); + if(n == valOrNode) + return this; + return editAndSet(edit, 2*idx+1, n); + } + if(Util.equals(key, keyOrNull)) { + if(val == valOrNode) + return this; + return editAndSet(edit, 2*idx+1, val); + } + addedLeaf.val = addedLeaf; + return editAndSet(edit, 2*idx, null, 2*idx+1, + createNode(edit, shift + 5, keyOrNull, valOrNode, hash, key, val)); + } else { + int n = Integer.bitCount(bitmap); + if(n*2 < array.length) { + addedLeaf.val = addedLeaf; + BitmapIndexedNode editable = ensureEditable(edit); + System.arraycopy(editable.array, 2*idx, editable.array, 2*(idx+1), 2*(n-idx)); + editable.array[2*idx] = key; + editable.array[2*idx+1] = val; + editable.bitmap |= bit; + return editable; + } + if(n >= 16) { + INode[] nodes = new INode[32]; + int jdx = mask(hash, shift); + nodes[jdx] = EMPTY.assoc(edit, shift + 5, hash, key, val, addedLeaf); + int j = 0; + for(int i = 0; i < 32; i++) + if(((bitmap >>> i) & 1) != 0) { + if (array[j] == null) + nodes[i] = (INode) array[j+1]; + else + nodes[i] = EMPTY.assoc(edit, shift + 5, Util.hash(array[j]), array[j], array[j+1], addedLeaf); + j += 2; + } + return new ArrayNode(edit, n + 1, nodes); + } else { + Object[] newArray = new Object[2*(n+4)]; + System.arraycopy(array, 0, newArray, 0, 2*idx); + newArray[2*idx] = key; + addedLeaf.val = addedLeaf; + newArray[2*idx+1] = val; + System.arraycopy(array, 2*idx, newArray, 2*(idx+1), 2*(n-idx)); + BitmapIndexedNode editable = ensureEditable(edit); + editable.array = newArray; + editable.bitmap |= bit; + return editable; + } } + } - static ISeq create(BitmapIndexedNode node, int i){ - if(i >= node.nodes.length) + public INode without(AtomicReference<Thread> edit, int shift, int hash, Object key, Box removedLeaf){ + int bit = bitpos(hash, shift); + if((bitmap & bit) == 0) + return this; + int idx = index(bit); + Object keyOrNull = array[2*idx]; + Object valOrNode = array[2*idx+1]; + if(keyOrNull == null) { + INode n = ((INode) valOrNode).without(edit, shift + 5, hash, key, removedLeaf); + if (n == valOrNode) + return this; + if (n != null) + return editAndSet(edit, 2*idx+1, n); + if (bitmap == bit) return null; - return new Seq(node.nodes[i].nodeSeq(), i, node); - } - - public Object first(){ - return s.first(); + removedLeaf.val = removedLeaf; + return editAndRemovePair(edit, bit, idx); } - - public ISeq next(){ - ISeq nexts = s.next(); - if(nexts != null) - return new Seq(nexts, i, node); - return create(node, i + 1); - } - - public Seq withMeta(IPersistentMap meta){ - return new Seq(meta, s, i, node); + if(Util.equals(key, keyOrNull)) { + removedLeaf.val = removedLeaf; + // TODO: collapse + return editAndRemovePair(edit, bit, idx); } - } - - + return this; + } } -final static class LeafNode extends AMapEntry implements INode{ +final static class HashCollisionNode implements INode{ + final int hash; - final Object key; - final Object val; + int count; + Object[] array; + final AtomicReference<Thread> edit; - public LeafNode(int hash, Object key, Object val){ + HashCollisionNode(AtomicReference<Thread> edit, int hash, int count, Object... array){ + this.edit = edit; this.hash = hash; - this.key = key; - this.val = val; + this.count = count; + this.array = array; } public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){ - if(hash == this.hash) - { - if(Util.equals(key, this.key)) - { - if(val == this.val) + if(hash == this.hash) { + int idx = findIndex(key); + if(idx != -1) { + if(array[idx + 1] == val) return this; - //note - do not set addedLeaf, since we are replacing - return new LeafNode(hash, key, val); - } - //hash collision - same hash, different keys - LeafNode newLeaf = new LeafNode(hash, key, val); - addedLeaf.val = newLeaf; - return new HashCollisionNode(hash, this, newLeaf); + return new HashCollisionNode(null, hash, count, cloneAndSet(array, idx + 1, val)); } - return BitmapIndexedNode.create(shift, this, hash, key, val, addedLeaf); + Object[] newArray = new Object[array.length + 2]; + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = key; + newArray[array.length + 1] = val; + addedLeaf.val = addedLeaf; + return new HashCollisionNode(edit, hash, count + 1, newArray); + } + // nest it in a bitmap node + return new BitmapIndexedNode(null, bitpos(this.hash, shift), new Object[] {null, this}) + .assoc(shift, hash, key, val, addedLeaf); } - public INode without(int hash, Object key){ - if(hash == this.hash && Util.equals(key, this.key)) + public INode without(int shift, int hash, Object key){ + int idx = findIndex(key); + if(idx == -1) + return this; + if(count == 1) return null; - return this; + return new HashCollisionNode(null, hash, count - 1, removePair(array, idx/2)); } - public LeafNode find(int hash, Object key){ - if(hash == this.hash && Util.equals(key, this.key)) - return this; + public IMapEntry find(int shift, int hash, Object key){ + int idx = findIndex(key); + if(idx < 0) + return null; + if(Util.equals(key, array[idx])) + return new MapEntry(array[idx], array[idx+1]); return null; } - public ISeq nodeSeq(){ - return RT.cons(this, null); + public Object find(int shift, int hash, Object key, Object notFound){ + int idx = findIndex(key); + if(idx < 0) + return notFound; + if(Util.equals(key, array[idx])) + return array[idx+1]; + return notFound; } - public int getHash(){ - return hash; + public ISeq nodeSeq(){ + return NodeSeq.create(array); } - public Object key(){ - return this.key; + public int findIndex(Object key){ + for(int i = 0; i < 2*count; i+=2) + { + if(Util.equals(key, array[i])) + return i; + } + return -1; } - public Object val(){ - return this.val; + private HashCollisionNode ensureEditable(AtomicReference<Thread> edit){ + if(this.edit == edit) + return this; + return new HashCollisionNode(edit, hash, count, array); } - public Object getKey(){ - return this.key; + private HashCollisionNode ensureEditable(AtomicReference<Thread> edit, int count, Object[] array){ + if(this.edit == edit) { + this.array = array; + this.count = count; + return this; + } + return new HashCollisionNode(edit, hash, count, array); } - public Object getValue(){ - return this.val; + private HashCollisionNode editAndSet(AtomicReference<Thread> edit, int i, Object a) { + HashCollisionNode editable = ensureEditable(edit); + editable.array[i] = a; + return editable; } -} - -final static class HashCollisionNode implements INode{ - final int hash; - final LeafNode[] leaves; - - public HashCollisionNode(int hash, LeafNode... leaves){ - this.hash = hash; - this.leaves = leaves; + private HashCollisionNode editAndSet(AtomicReference<Thread> edit, int i, Object a, int j, Object b) { + HashCollisionNode editable = ensureEditable(edit); + editable.array[i] = a; + editable.array[j] = b; + return editable; } - public INode assoc(int shift, int hash, Object key, Object val, Box addedLeaf){ - if(hash == this.hash) - { - int idx = findIndex(hash, key); - if(idx != -1) - { - if(leaves[idx].val == val) + + public INode assoc(AtomicReference<Thread> edit, int shift, int hash, Object key, Object val, Box addedLeaf){ + if(hash == this.hash) { + int idx = findIndex(key); + if(idx != -1) { + if(array[idx + 1] == val) return this; - LeafNode[] newLeaves = leaves.clone(); - //note - do not set addedLeaf, since we are replacing - newLeaves[idx] = new LeafNode(hash, key, val); - return new HashCollisionNode(hash, newLeaves); - } - LeafNode[] newLeaves = new LeafNode[leaves.length + 1]; - System.arraycopy(leaves, 0, newLeaves, 0, leaves.length); - addedLeaf.val = newLeaves[leaves.length] = new LeafNode(hash, key, val); - return new HashCollisionNode(hash, newLeaves); + return editAndSet(edit, idx+1, val); } - return BitmapIndexedNode.create(shift, this, hash, key, val, addedLeaf); - } + if (array.length > 2*count) { + addedLeaf.val = addedLeaf; + HashCollisionNode editable = editAndSet(edit, 2*count, key, 2*count+1, val); + editable.count++; + return editable; + } + Object[] newArray = new Object[array.length + 2]; + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = key; + newArray[array.length + 1] = val; + addedLeaf.val = addedLeaf; + return ensureEditable(edit, count + 1, newArray); + } + // nest it in a bitmap node + return new BitmapIndexedNode(edit, bitpos(this.hash, shift), new Object[] {null, this, null, null}) + .assoc(edit, shift, hash, key, val, addedLeaf); + } - public INode without(int hash, Object key){ - int idx = findIndex(hash, key); + public INode without(AtomicReference<Thread> edit, int shift, int hash, Object key, Box removedLeaf){ + int idx = findIndex(key); if(idx == -1) return this; - if(leaves.length == 2) - return idx == 0 ? leaves[1] : leaves[0]; - LeafNode[] newLeaves = new LeafNode[leaves.length - 1]; - System.arraycopy(leaves, 0, newLeaves, 0, idx); - System.arraycopy(leaves, idx + 1, newLeaves, idx, leaves.length - (idx + 1)); - return new HashCollisionNode(hash, newLeaves); - } - - public LeafNode find(int hash, Object key){ - int idx = findIndex(hash, key); - if(idx != -1) - return leaves[idx]; - return null; - } - - public ISeq nodeSeq(){ - return ArraySeq.create((Object[]) leaves); - } - - int findIndex(int hash, Object key){ - for(int i = 0; i < leaves.length; i++) - { - if(leaves[i].find(hash, key) != null) - return i; - } - return -1; - } - - public int getHash(){ - return hash; + if(count == 1) + return null; + HashCollisionNode editable = ensureEditable(edit); + editable.array[idx] = editable.array[2*count-2]; + editable.array[idx+1] = editable.array[2*count-1]; + editable.array[2*count-2] = editable.array[2*count-1] = null; + editable.count--; + return editable; } } @@ -742,5 +928,109 @@ public static void main(String[] args){ } */ + +private static INode[] cloneAndSet(INode[] array, int i, INode a) { + INode[] clone = array.clone(); + clone[i] = a; + return clone; +} + +private static Object[] cloneAndSet(Object[] array, int i, Object a) { + Object[] clone = array.clone(); + clone[i] = a; + return clone; +} + +private static Object[] cloneAndSet(Object[] array, int i, Object a, int j, Object b) { + Object[] clone = array.clone(); + clone[i] = a; + clone[j] = b; + return clone; +} + +private static Object[] removePair(Object[] array, int i) { + Object[] newArray = new Object[array.length - 2]; + System.arraycopy(array, 0, newArray, 0, 2*i); + System.arraycopy(array, 2*(i+1), newArray, 2*i, newArray.length - 2*i); + return newArray; +} + +private static INode createNode(int shift, Object key1, Object val1, int key2hash, Object key2, Object val2) { + int key1hash = Util.hash(key1); + if(key1hash == key2hash) + return new HashCollisionNode(null, key1hash, 2, new Object[] {key1, val1, key2, val2}); + Box _ = new Box(null); + AtomicReference<Thread> edit = new AtomicReference<Thread>(); + return BitmapIndexedNode.EMPTY + .assoc(edit, shift, key1hash, key1, val1, _) + .assoc(edit, shift, key2hash, key2, val2, _); +} + +private static INode createNode(AtomicReference<Thread> edit, int shift, Object key1, Object val1, int key2hash, Object key2, Object val2) { + int key1hash = Util.hash(key1); + if(key1hash == key2hash) + return new HashCollisionNode(null, key1hash, 2, new Object[] {key1, val1, key2, val2}); + Box _ = new Box(null); + return BitmapIndexedNode.EMPTY + .assoc(edit, shift, key1hash, key1, val1, _) + .assoc(edit, shift, key2hash, key2, val2, _); +} + +private static int bitpos(int hash, int shift){ + return 1 << mask(hash, shift); +} + +static final class NodeSeq extends ASeq { + final Object[] array; + final int i; + final ISeq s; + + NodeSeq(Object[] array, int i) { + this(null, array, i, null); + } + + static ISeq create(Object[] array) { + return create(array, 0, null); + } + + private static ISeq create(Object[] array, int i, ISeq s) { + if(s != null) + return new NodeSeq(null, array, i, s); + for(int j = i; j < array.length; j+=2) { + if(array[j] != null) + return new NodeSeq(null, array, j, null); + INode node = (INode) array[j+1]; + if (node != null) { + ISeq nodeSeq = node.nodeSeq(); + if(nodeSeq != null) + return new NodeSeq(null, array, j + 2, nodeSeq); + } + } + return null; + } + + NodeSeq(IPersistentMap meta, Object[] array, int i, ISeq s) { + super(meta); + this.array = array; + this.i = i; + this.s = s; + } + + public Obj withMeta(IPersistentMap meta) { + return new NodeSeq(meta, array, i, s); + } + + public Object first() { + if(s != null) + return s.first(); + return new MapEntry(array[i], array[i+1]); + } + + public ISeq next() { + if(s != null) + return create(array, i, s.next()); + return create(array, i + 2, null); + } } +}
\ No newline at end of file diff --git a/src/jvm/clojure/lang/PersistentHashSet.java b/src/jvm/clojure/lang/PersistentHashSet.java index 35c6fa95..73a5a61f 100644 --- a/src/jvm/clojure/lang/PersistentHashSet.java +++ b/src/jvm/clojure/lang/PersistentHashSet.java @@ -14,7 +14,7 @@ package clojure.lang; import java.util.List; -public class PersistentHashSet extends APersistentSet{ +public class PersistentHashSet extends APersistentSet implements IEditableCollection { static public final PersistentHashSet EMPTY = new PersistentHashSet(null, PersistentHashMap.EMPTY); @@ -69,4 +69,18 @@ public PersistentHashSet withMeta(IPersistentMap meta){ return new PersistentHashSet(meta, impl); } +public ITransientCollection asTransient() { + return new TransientHashSet(((PersistentHashMap) impl).asTransient()); +} + +static final class TransientHashSet extends ATransientSet { + TransientHashSet(ITransientMap impl) { + super(impl); + } + + public IPersistentCollection persistent() { + return new PersistentHashSet(null, impl.persistent()); + } +} + } diff --git a/src/jvm/clojure/lang/PersistentVector.java b/src/jvm/clojure/lang/PersistentVector.java index 5c662556..2d55e2e8 100644 --- a/src/jvm/clojure/lang/PersistentVector.java +++ b/src/jvm/clojure/lang/PersistentVector.java @@ -1,35 +1,11 @@ /** - Copyright (c) 2007-2008, Rich Hickey - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * Neither the name of Clojure nor the names of its contributors - may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. **/ /* rich Jul 5, 2007 */ @@ -37,38 +13,57 @@ package clojure.lang; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class PersistentVector extends APersistentVector implements IEditableCollection{ + +static class Node{ + final AtomicReference<Thread> edit; + final Object[] array; + + Node(AtomicReference<Thread> edit, Object[] array){ + this.edit = edit; + this.array = array; + } + + Node(AtomicReference<Thread> edit){ + this.edit = edit; + this.array = new Object[32]; + } +} + +final static AtomicReference<Thread> NOEDIT = new AtomicReference<Thread>(null); +final static Node EMPTY_NODE = new Node(NOEDIT, new Object[32]); -public class PersistentVector extends APersistentVector{ final int cnt; final int shift; -final Object[] root; +final Node root; final Object[] tail; -public final static PersistentVector EMPTY = new PersistentVector(0, 5, RT.EMPTY_ARRAY, RT.EMPTY_ARRAY); - +public final static PersistentVector EMPTY = new PersistentVector(0, 5, EMPTY_NODE, new Object[]{}); static public PersistentVector create(ISeq items){ - PersistentVector ret = EMPTY; + TransientVector ret = EMPTY.asTransient(); for(; items != null; items = items.next()) - ret = ret.cons(items.first()); - return ret; + ret = ret.conj(items.first()); + return ret.persistent(); } static public PersistentVector create(List items){ - PersistentVector ret = EMPTY; + TransientVector ret = EMPTY.asTransient(); for(Object item : items) - ret = ret.cons(item); - return ret; + ret = ret.conj(item); + return ret.persistent(); } static public PersistentVector create(Object... items){ - PersistentVector ret = EMPTY; + TransientVector ret = EMPTY.asTransient(); for(Object item : items) - ret = ret.cons(item); - return ret; + ret = ret.conj(item); + return ret.persistent(); } -PersistentVector(int cnt, int shift, Object[] root, Object[] tail){ +PersistentVector(int cnt, int shift, Node root, Object[] tail){ super(null); this.cnt = cnt; this.shift = shift; @@ -77,7 +72,7 @@ PersistentVector(int cnt, int shift, Object[] root, Object[] tail){ } -PersistentVector(IPersistentMap meta, int cnt, int shift, Object[] root, Object[] tail){ +PersistentVector(IPersistentMap meta, int cnt, int shift, Node root, Object[] tail){ super(meta); this.cnt = cnt; this.shift = shift; @@ -85,25 +80,31 @@ PersistentVector(IPersistentMap meta, int cnt, int shift, Object[] root, Object[ this.tail = tail; } +public TransientVector asTransient(){ + return new TransientVector(this); +} + final int tailoff(){ - return cnt - tail.length; + if(cnt < 32) + return 0; + return ((cnt - 1) >>> 5) << 5; } -public Object[] nodeFor(int i){ +public Object[] arrayFor(int i){ if(i >= 0 && i < cnt) { if(i >= tailoff()) return tail; - Object[] arr = root; + Node node = root; for(int level = shift; level > 0; level -= 5) - arr = (Object[]) arr[(i >>> level) & 0x01f]; - return arr; + node = (Node) node.array[(i >>> level) & 0x01f]; + return node.array; } throw new IndexOutOfBoundsException(); } public Object nth(int i){ - Object[] node = nodeFor(i); + Object[] node = arrayFor(i); return node[i & 0x01f]; } @@ -126,16 +127,16 @@ public PersistentVector assocN(int i, Object val){ throw new IndexOutOfBoundsException(); } -private static Object[] doAssoc(int level, Object[] arr, int i, Object val){ - Object[] ret = arr.clone(); +private static Node doAssoc(int level, Node node, int i, Object val){ + Node ret = new Node(node.edit,node.array.clone()); if(level == 0) { - ret[i & 0x01f] = val; + ret.array[i & 0x01f] = val; } else { int subidx = (i >>> level) & 0x01f; - ret[subidx] = doAssoc(level - 5, (Object[]) arr[subidx], i, val); + ret.array[subidx] = doAssoc(level - 5, (Node) node.array[subidx], i, val); } return ret; } @@ -150,24 +151,64 @@ public PersistentVector withMeta(IPersistentMap meta){ public PersistentVector cons(Object val){ - if(tail.length < 32) + int i = cnt; + //room in tail? +// if(tail.length < 32) + if(cnt - tailoff() < 32) { Object[] newTail = new Object[tail.length + 1]; System.arraycopy(tail, 0, newTail, 0, tail.length); newTail[tail.length] = val; return new PersistentVector(meta(), cnt + 1, shift, root, newTail); } - Box expansion = new Box(null); - Object[] newroot = pushTail(shift - 5, root, tail, expansion); + //full tail, push into tree + Node newroot; + Node tailnode = new Node(root.edit,tail); int newshift = shift; - if(expansion.val != null) + //overflow root? + if((cnt >>> 5) > (1 << shift)) { - newroot = new Object[]{newroot, expansion.val}; + newroot = new Node(root.edit); + newroot.array[0] = root; + newroot.array[1] = newPath(root.edit,shift, tailnode); newshift += 5; } + else + newroot = pushTail(shift, root, tailnode); return new PersistentVector(meta(), cnt + 1, newshift, newroot, new Object[]{val}); } +private Node pushTail(int level, Node parent, Node tailnode){ + //if parent is leaf, insert node, + // else does it map to an existing child? -> nodeToInsert = pushNode one more level + // else alloc new path + //return nodeToInsert placed in copy of parent + int subidx = ((cnt - 1) >>> level) & 0x01f; + Node ret = new Node(parent.edit, parent.array.clone()); + Node nodeToInsert; + if(level == 5) + { + nodeToInsert = tailnode; + } + else + { + Node child = (Node) parent.array[subidx]; + nodeToInsert = (child != null)? + pushTail(level-5,child, tailnode) + :newPath(root.edit,level-5, tailnode); + } + ret.array[subidx] = nodeToInsert; + return ret; +} + +private static Node newPath(AtomicReference<Thread> edit,int level, Node node){ + if(level == 0) + return node; + Node ret = new Node(edit); + ret.array[0] = newPath(edit, level - 5, node); + return ret; +} + public IChunkedSeq chunkedSeq(){ if(count() == 0) return null; @@ -189,7 +230,7 @@ static public final class ChunkedSeq extends ASeq implements IChunkedSeq{ this.vec = vec; this.i = i; this.offset = offset; - this.node = vec.nodeFor(i); + this.node = vec.arrayFor(i); } ChunkedSeq(IPersistentMap meta, PersistentVector vec, Object[] node, int i, int offset){ @@ -245,84 +286,361 @@ public IPersistentCollection empty(){ return EMPTY.withMeta(meta()); } -private Object[] pushTail(int level, Object[] arr, Object[] tailNode, Box expansion){ - Object newchild; - if(level == 0) - { - newchild = tailNode; - } - else - { - newchild = pushTail(level - 5, (Object[]) arr[arr.length - 1], tailNode, expansion); - if(expansion.val == null) - { - Object[] ret = arr.clone(); - ret[arr.length - 1] = newchild; - return ret; - } - else - newchild = expansion.val; - } - //expansion - if(arr.length == 32) - { - expansion.val = new Object[]{newchild}; - return arr; - } - Object[] ret = new Object[arr.length + 1]; - System.arraycopy(arr, 0, ret, 0, arr.length); - ret[arr.length] = newchild; - expansion.val = null; - return ret; -} +//private Node pushTail(int level, Node node, Object[] tailNode, Box expansion){ +// Object newchild; +// if(level == 0) +// { +// newchild = tailNode; +// } +// else +// { +// newchild = pushTail(level - 5, (Object[]) arr[arr.length - 1], tailNode, expansion); +// if(expansion.val == null) +// { +// Object[] ret = arr.clone(); +// ret[arr.length - 1] = newchild; +// return ret; +// } +// else +// newchild = expansion.val; +// } +// //expansion +// if(arr.length == 32) +// { +// expansion.val = new Object[]{newchild}; +// return arr; +// } +// Object[] ret = new Object[arr.length + 1]; +// System.arraycopy(arr, 0, ret, 0, arr.length); +// ret[arr.length] = newchild; +// expansion.val = null; +// return ret; +//} public PersistentVector pop(){ if(cnt == 0) throw new IllegalStateException("Can't pop empty vector"); if(cnt == 1) return EMPTY.withMeta(meta()); - if(tail.length > 1) + //if(tail.length > 1) + if(cnt-tailoff() > 1) { Object[] newTail = new Object[tail.length - 1]; System.arraycopy(tail, 0, newTail, 0, newTail.length); return new PersistentVector(meta(), cnt - 1, shift, root, newTail); } - Box ptail = new Box(null); - Object[] newroot = popTail(shift - 5, root, ptail); + Object[] newtail = arrayFor(cnt - 2); + + Node newroot = popTail(shift, root); int newshift = shift; if(newroot == null) { - newroot = RT.EMPTY_ARRAY; + newroot = EMPTY_NODE; } - if(shift > 5 && newroot.length == 1) + if(shift > 5 && newroot.array[1] == null) { - newroot = (Object[]) newroot[0]; + newroot = (Node) newroot.array[0]; newshift -= 5; } - return new PersistentVector(meta(), cnt - 1, newshift, newroot, (Object[]) ptail.val); + return new PersistentVector(meta(), cnt - 1, newshift, newroot, newtail); } -private Object[] popTail(int shift, Object[] arr, Box ptail){ - if(shift > 0) +private Node popTail(int level, Node node){ + int subidx = ((cnt-2) >>> level) & 0x01f; + if(level > 5) { - Object[] newchild = popTail(shift - 5, (Object[]) arr[arr.length - 1], ptail); - if(newchild != null) + Node newchild = popTail(level - 5, (Node) node.array[subidx]); + if(newchild == null && subidx == 0) + return null; + else { - Object[] ret = arr.clone(); - ret[arr.length - 1] = newchild; + Node ret = new Node(root.edit, node.array.clone()); + ret.array[subidx] = newchild; return ret; } } - if(shift == 0) - ptail.val = arr[arr.length - 1]; - //contraction - if(arr.length == 1) + else if(subidx == 0) return null; - Object[] ret = new Object[arr.length - 1]; - System.arraycopy(arr, 0, ret, 0, ret.length); - return ret; + else + { + Node ret = new Node(root.edit, node.array.clone()); + ret.array[subidx] = null; + return ret; + } } +static final class TransientVector extends AFn implements ITransientVector, Counted{ + int cnt; + int shift; + Node root; + Object[] tail; + + TransientVector(int cnt, int shift, Node root, Object[] tail){ + this.cnt = cnt; + this.shift = shift; + this.root = root; + this.tail = tail; + } + + TransientVector(PersistentVector v){ + this(v.cnt, v.shift, editableRoot(v.root), editableTail(v.tail)); + } + + public int count(){ + ensureEditable(); + return cnt; + } + + Node ensureEditable(Node node){ + if(node.edit == root.edit) + return node; + return new Node(root.edit, node.array.clone()); + } + + void ensureEditable(){ + Thread owner = root.edit.get(); + if(owner == Thread.currentThread()) + return; + if(owner != null) + throw new IllegalAccessError("Mutable used by non-owner thread"); + throw new IllegalAccessError("Mutable used after immutable call"); + +// root = editableRoot(root); +// tail = editableTail(tail); + } + + static Node editableRoot(Node node){ + return new Node(new AtomicReference<Thread>(Thread.currentThread()), node.array.clone()); + } + + public PersistentVector persistent(){ + ensureEditable(); +// Thread owner = root.edit.get(); +// if(owner != null && owner != Thread.currentThread()) +// { +// throw new IllegalAccessError("Mutation release by non-owner thread"); +// } + root.edit.set(null); + Object[] trimmedTail = new Object[cnt-tailoff()]; + System.arraycopy(tail,0,trimmedTail,0,trimmedTail.length); + return new PersistentVector(cnt, shift, root, trimmedTail); + } + + static Object[] editableTail(Object[] tl){ + Object[] ret = new Object[32]; + System.arraycopy(tl,0,ret,0,tl.length); + return ret; + } + + public TransientVector conj(Object val){ + ensureEditable(); + int i = cnt; + //room in tail? + if(i - tailoff() < 32) + { + tail[i & 0x01f] = val; + ++cnt; + return this; + } + //full tail, push into tree + Node newroot; + Node tailnode = new Node(root.edit, tail); + tail = new Object[32]; + tail[0] = val; + int newshift = shift; + //overflow root? + if((cnt >>> 5) > (1 << shift)) + { + newroot = new Node(root.edit); + newroot.array[0] = root; + newroot.array[1] = newPath(root.edit,shift, tailnode); + newshift += 5; + } + else + newroot = pushTail(shift, root, tailnode); + root = newroot; + shift = newshift; + ++cnt; + return this; + } + + private Node pushTail(int level, Node parent, Node tailnode){ + //if parent is leaf, insert node, + // else does it map to an existing child? -> nodeToInsert = pushNode one more level + // else alloc new path + //return nodeToInsert placed in parent + parent = ensureEditable(parent); + int subidx = ((cnt - 1) >>> level) & 0x01f; + Node ret = parent; + Node nodeToInsert; + if(level == 5) + { + nodeToInsert = tailnode; + } + else + { + Node child = (Node) parent.array[subidx]; + nodeToInsert = (child != null) ? + pushTail(level - 5, child, tailnode) + : newPath(root.edit, level - 5, tailnode); + } + ret.array[subidx] = nodeToInsert; + return ret; + } + + final private int tailoff(){ + if(cnt < 32) + return 0; + return ((cnt-1) >>> 5) << 5; + } + + private Object[] arrayFor(int i){ + if(i >= 0 && i < cnt) + { + if(i >= tailoff()) + return tail; + Node node = root; + for(int level = shift; level > 0; level -= 5) + node = (Node) node.array[(i >>> level) & 0x01f]; + return node.array; + } + throw new IndexOutOfBoundsException(); + } + + public Object valAt(Object key){ + //note - relies on ensureEditable in 2-arg valAt + return valAt(key, null); + } + + public Object valAt(Object key, Object notFound){ + ensureEditable(); + if(Util.isInteger(key)) + { + int i = ((Number) key).intValue(); + if(i >= 0 && i < cnt) + return nth(i); + } + return notFound; + } + + public Object invoke(Object arg1) throws Exception{ + //note - relies on ensureEditable in nth + if(Util.isInteger(arg1)) + return nth(((Number) arg1).intValue()); + throw new IllegalArgumentException("Key must be integer"); + } + + public Object nth(int i){ + ensureEditable(); + Object[] node = arrayFor(i); + return node[i & 0x01f]; + } + + public TransientVector assocN(int i, Object val){ + ensureEditable(); + if(i >= 0 && i < cnt) + { + if(i >= tailoff()) + { + tail[i & 0x01f] = val; + return this; + } + + root = doAssoc(shift, root, i, val); + return this; + } + if(i == cnt) + return conj(val); + throw new IndexOutOfBoundsException(); + } + + public TransientVector assoc(Object key, Object val){ + //note - relies on ensureEditable in assocN + if(Util.isInteger(key)) + { + int i = ((Number) key).intValue(); + return assocN(i, val); + } + throw new IllegalArgumentException("Key must be integer"); + } + + private Node doAssoc(int level, Node node, int i, Object val){ + node = ensureEditable(node); + Node ret = node; + if(level == 0) + { + ret.array[i & 0x01f] = val; + } + else + { + int subidx = (i >>> level) & 0x01f; + ret.array[subidx] = doAssoc(level - 5, (Node) node.array[subidx], i, val); + } + return ret; + } + + public TransientVector pop(){ + ensureEditable(); + if(cnt == 0) + throw new IllegalStateException("Can't pop empty vector"); + if(cnt == 1) + { + cnt = 0; + return this; + } + int i = cnt - 1; + //pop in tail? + if((i & 0x01f) > 0) + { + --cnt; + return this; + } + + Object[] newtail = arrayFor(cnt - 2); + + Node newroot = popTail(shift, root); + int newshift = shift; + if(newroot == null) + { + newroot = EMPTY_NODE; + } + if(shift > 5 && newroot.array[1] == null) + { + newroot = (Node) newroot.array[0]; + newshift -= 5; + } + root = newroot; + shift = newshift; + --cnt; + tail = newtail; + return this; + } + + private Node popTail(int level, Node node){ + node = ensureEditable(node); + int subidx = ((cnt - 2) >>> level) & 0x01f; + if(level > 5) + { + Node newchild = popTail(level - 5, (Node) node.array[subidx]); + if(newchild == null && subidx == 0) + return null; + else + { + Node ret = node; + ret.array[subidx] = newchild; + return ret; + } + } + else if(subidx == 0) + return null; + else + { + Node ret = node; + ret.array[subidx] = null; + return ret; + } + } +} /* static public void main(String[] args){ if(args.length != 3) @@ -333,23 +651,27 @@ static public void main(String[] args){ int size = Integer.parseInt(args[0]); int writes = Integer.parseInt(args[1]); int reads = Integer.parseInt(args[2]); - Vector v = new Vector(size); - v.setSize(size); +// Vector v = new Vector(size); + ArrayList v = new ArrayList(size); +// v.setSize(size); //PersistentArray p = new PersistentArray(size); PersistentVector p = PersistentVector.EMPTY; +// MutableVector mp = p.mutable(); for(int i = 0; i < size; i++) { - v.set(i, i); + v.add(i); +// v.set(i, i); //p = p.set(i, 0); p = p.cons(i); +// mp = mp.conj(i); } Random rand; rand = new Random(42); long tv = 0; - System.out.println("Vector"); + System.out.println("ArrayList"); long startTime = System.nanoTime(); for(int i = 0; i < writes; i++) { @@ -369,23 +691,32 @@ static public void main(String[] args){ // PersistentVector oldp = p; //Random rand2 = new Random(42); + MutableVector mp = p.mutable(); for(int i = 0; i < writes; i++) { - p = p.assocN(rand.nextInt(size), i); +// p = p.assocN(rand.nextInt(size), i); + mp = mp.assocN(rand.nextInt(size), i); +// mp = mp.assoc(rand.nextInt(size), i); //dummy set to force perverse branching //oldp = oldp.assocN(rand2.nextInt(size), i); } for(int i = 0; i < reads; i++) { - tp += (Integer) p.nth(rand.nextInt(size)); +// tp += (Integer) p.nth(rand.nextInt(size)); + tp += (Integer) mp.nth(rand.nextInt(size)); } +// p = mp.immutable(); + //mp.cons(42); estimatedTime = System.nanoTime() - startTime; System.out.println("time: " + estimatedTime / 1000000); for(int i = 0; i < size / 2; i++) { - p = p.pop(); + mp = mp.pop(); +// p = p.pop(); v.remove(v.size() - 1); } + p = (PersistentVector) mp.immutable(); + //mp.pop(); //should fail for(int i = 0; i < size / 2; i++) { tp += (Integer) p.nth(i); @@ -394,5 +725,5 @@ static public void main(String[] args){ System.out.println("Done: " + tv + ", " + tp); } - */ +// */ } diff --git a/src/jvm/clojure/lang/RT.java b/src/jvm/clojure/lang/RT.java index 263ec551..277e62bc 100644 --- a/src/jvm/clojure/lang/RT.java +++ b/src/jvm/clojure/lang/RT.java @@ -476,7 +476,7 @@ static ISeq seqFrom(Object coll){ else { Class c = coll.getClass(); Class sc = c.getSuperclass(); - throw new IllegalArgumentException("Don't know how to create ISeq from: " + c.getSimpleName()); + throw new IllegalArgumentException("Don't know how to create ISeq from: " + c.getName()); } } @@ -615,8 +615,8 @@ static public Object pop(Object x){ static public Object get(Object coll, Object key){ if(coll == null) return null; - else if(coll instanceof Associative) - return ((Associative) coll).valAt(key); + else if(coll instanceof ILookup) + return ((ILookup) coll).valAt(key); else if(coll instanceof Map) { Map m = (Map) coll; return m.get(key); @@ -638,8 +638,8 @@ static public Object get(Object coll, Object key){ static public Object get(Object coll, Object key, Object notFound){ if(coll == null) return notFound; - else if(coll instanceof Associative) - return ((Associative) coll).valAt(key, notFound); + else if(coll instanceof ILookup) + return ((ILookup) coll).valAt(key, notFound); else if(coll instanceof Map) { Map m = (Map) coll; if(m.containsKey(key)) @@ -756,16 +756,17 @@ static public Object nth(Object coll, int n){ } static public Object nth(Object coll, int n, Object notFound){ - if(coll == null) - return notFound; - else if(n < 0) - return notFound; - else if(coll instanceof IPersistentVector) { - IPersistentVector v = (IPersistentVector) coll; - if(n < v.count()) + if(coll instanceof Indexed) { + Indexed v = (Indexed) coll; + if(n >= 0 && n < v.count()) return v.nth(n); return notFound; } + else if(coll == null) + return notFound; + else if(n < 0) + return notFound; + else if(coll instanceof String) { String s = (String) coll; if(n < s.length()) diff --git a/src/jvm/clojure/lang/Ref.java b/src/jvm/clojure/lang/Ref.java index fef7c439..a4626acb 100644 --- a/src/jvm/clojure/lang/Ref.java +++ b/src/jvm/clojure/lang/Ref.java @@ -175,7 +175,7 @@ public Object alter(IFn fn, ISeq args) throws Exception{ } public void touch(){ - LockingTransaction.getEx().doTouch(this); + LockingTransaction.getEx().doEnsure(this); } //*/ diff --git a/src/jvm/clojure/lang/StringSeq.java b/src/jvm/clojure/lang/StringSeq.java index b5622903..5cf54644 100644 --- a/src/jvm/clojure/lang/StringSeq.java +++ b/src/jvm/clojure/lang/StringSeq.java @@ -47,4 +47,8 @@ public ISeq next(){ public int index(){ return i; } + +public int count(){ + return s.length() - i; +} } diff --git a/src/jvm/clojure/lang/Symbol.java b/src/jvm/clojure/lang/Symbol.java index dac4a480..001043d9 100644 --- a/src/jvm/clojure/lang/Symbol.java +++ b/src/jvm/clojure/lang/Symbol.java @@ -42,7 +42,7 @@ static public Symbol intern(String ns, String name){ static public Symbol intern(String nsname){ int i = nsname.lastIndexOf('/'); - if(i == -1) + if(i == -1 || nsname.equals("/")) return new Symbol(null, nsname.intern()); else return new Symbol(nsname.substring(0, i).intern(), nsname.substring(i + 1).intern()); diff --git a/test/clojure/test_clojure.clj b/test/clojure/test_clojure.clj index 8b1e16ae..ac264e0f 100644 --- a/test/clojure/test_clojure.clj +++ b/test/clojure/test_clojure.clj @@ -15,7 +15,7 @@ ;; Created 22 October 2008 (ns clojure.test-clojure - (:use [clojure.test :only (run-tests)]) + (:require [clojure.test :as t]) (:gen-class)) (def test-names @@ -59,7 +59,23 @@ [] (println "Loading tests...") (apply require :reload-all test-namespaces) - (apply run-tests test-namespaces)) + (apply t/run-tests test-namespaces)) + +(defn run-ant + "Runs all defined tests, prints report to *err*, throw if failures. This works well for running in an ant java task." + [] + (let [rpt t/report] + (binding [;; binding to *err* because, in ant, when the test target + ;; runs after compile-clojure, *out* doesn't print anything + *out* *err* + t/*test-out* *err* + t/report (fn report [m] + (if (= :summary (:type m)) + (do (rpt m) + (if (or (pos? (:fail m)) (pos? (:error m))) + (throw (new Exception (str (:fail m) " failures, " (:error m) " errors."))))) + (rpt m)))] + (run)))) (defn -main "Run all defined tests from the command line" diff --git a/test/clojure/test_clojure/compilation.clj b/test/clojure/test_clojure/compilation.clj index c633db6d..fba781ca 100644 --- a/test/clojure/test_clojure/compilation.clj +++ b/test/clojure/test_clojure/compilation.clj @@ -36,4 +36,8 @@ (:macro m) true (:name m) 'when ))) - +(deftest test-embedded-constants + (testing "Embedded constants" + (are [t] (eval `(= t ~t/TYPE))) + Boolean Byte Character Double Float Integer Long Short)) + diff --git a/test/clojure/test_clojure/control.clj b/test/clojure/test_clojure/control.clj index 3a223d58..75f50a6b 100644 --- a/test/clojure/test_clojure/control.clj +++ b/test/clojure/test_clojure/control.clj @@ -6,7 +6,7 @@ ; the terms of this license. ; You must not remove this notice, or any other, from this software. -; Author: Frantisek Sodomka +; Author: Frantisek Sodomka, Mike Hinchey ;; ;; Test "flow control" constructs. @@ -60,15 +60,101 @@ (maintains-identity (fn [_] (do _))) ) -; loop/recur -; throw, try +;; loop/recur +(deftest test-loop + (are [x y] (= x y) + 1 (loop [] + 1) + 3 (loop [a 1] + (if (< a 3) + (recur (inc a)) + a)) + [2 4 6] (loop [a [] + b [1 2 3]] + (if (seq b) + (recur (conj a (* 2 (first b))) + (next b)) + a)) + [6 4 2] (loop [a () + b [1 2 3]] + (if (seq b) + (recur (conj a (* 2 (first b))) + (next b)) + a)) + ) + ) + + +;; throw, try + +; if: see logic.clj + +(deftest test-when + (are [x y] (= x y) + 1 (when true 1) + nil (when true) + nil (when false) + nil (when false (exception)) + )) + +(deftest test-when-not + (are [x y] (= x y) + 1 (when-not false 1) + nil (when-not true) + nil (when-not false) + nil (when-not true (exception)) + )) + +(deftest test-if-not + (are [x y] (= x y) + 1 (if-not false 1) + 1 (if-not false 1 (exception)) + nil (if-not true 1) + 2 (if-not true 1 2) + nil (if-not true (exception)) + 1 (if-not true (exception) 1) + )) -; [if (logic.clj)], if-not, if-let -; when, when-not, when-let, when-first +(deftest test-when-let + (are [x y] (= x y) + 1 (when-let [a 1] + a) + 2 (when-let [[a b] '(1 2)] + b) + nil (when-let [a false] + (exception)) + )) + +(deftest test-if-let + (are [x y] (= x y) + 1 (if-let [a 1] + a) + 2 (if-let [[a b] '(1 2)] + b) + nil (if-let [a false] + (exception)) + 1 (if-let [a false] + a 1) + 1 (if-let [[a b] nil] + b 1) + 1 (if-let [a false] + (exception) + 1) + )) + +(deftest test-when-first + (are [x y] (= x y) + 1 (when-first [a [1 2]] + a) + 2 (when-first [[a b] '((1 2) 3)] + b) + nil (when-first [a nil] + (exception)) + )) (deftest test-cond - (are [x y] (= x y) + (are [x y] (= x y) (cond) nil (cond nil true) nil @@ -107,11 +193,78 @@ (maintains-identity (fn [_] (cond true _))) ) -; condp +(deftest test-condp + (are [x] (= :pass x) + (condp = 1 + 1 :pass + 2 :fail) + (condp = 1 + 2 :fail + 1 :pass) + (condp = 1 + 2 :fail + :pass) + (condp = 1 + :pass) + (condp = 1 + 2 :fail + ;; doc of condp says result-expr is returned + ;; shouldn't it say similar to cond: "evaluates and returns + ;; the value of the corresponding expr and doesn't evaluate any of the + ;; other tests or exprs." + (identity :pass)) + (condp + 1 + 1 :>> #(if (= % 2) :pass :fail)) + (condp + 1 + 1 :>> #(if (= % 3) :fail :pass)) + ) + (is (thrown? IllegalArgumentException + (condp = 1) + )) + (is (thrown? IllegalArgumentException + (condp = 1 + 2 :fail) + )) + ) + ; [for, doseq (for.clj)] -; dotimes, while +(deftest test-dotimes + ;; dotimes always returns nil + (is (= nil (dotimes [n 1] n))) + ;; test using an atom since dotimes is for modifying + ;; test executes n times + (is (= 3 + (let [a (atom 0)] + (dotimes [n 3] + (swap! a inc)) + @a) + )) + ;; test all values of n + (is (= [0 1 2] + (let [a (atom [])] + (dotimes [n 3] + (swap! a conj n)) + @a))) + (is (= [] + (let [a (atom [])] + (dotimes [n 0] + (swap! a conj n)) + @a))) + ) + +(deftest test-while + (is (= nil (while nil (throw (Exception. "never"))))) + (is (= [0 nil] + ;; a will dec to 0 + ;; while always returns nil + (let [a (atom 3) + w (while (pos? @a) + (swap! a dec))] + [@a w]))) + (is (thrown? Exception (while true (throw (Exception. "expected to throw"))))) + ) ; locking, monitor-enter, monitor-exit diff --git a/test/clojure/test_clojure/for.clj b/test/clojure/test_clojure/for.clj index d8ebed26..6d9dc59e 100644 --- a/test/clojure/test_clojure/for.clj +++ b/test/clojure/test_clojure/for.clj @@ -121,3 +121,8 @@ '([0 1 1] [1 0 1] [1 2 3] [2 1 3]))) (is (= (for [x (range 6) :let [y (rem x 2)] :when (even? y) z [8 9]] [x z]) '([0 8] [0 9] [2 8] [2 9] [4 8] [4 9])))) + +; :while must skip all subsequent chunks as well as the remainder of +; the current chunk: +(deftest-both Chunked-While + (is (= (for [x (range 100) :while (even? x)] x) '(0)))) diff --git a/test/clojure/test_clojure/java_interop.clj b/test/clojure/test_clojure/java_interop.clj index 699ba361..81c7df0e 100644 --- a/test/clojure/test_clojure/java_interop.clj +++ b/test/clojure/test_clojure/java_interop.clj @@ -146,13 +146,13 @@ ; given size (and empty) (are [x] (and (= (alength (~type-array x)) x) - (= (vec (~type-array x)) (repeat x 0))) + (= (vec (~type-array x)) (repeat x 0))) 0 1 5 ) ; copy of a sequence (are [x] (and (= (alength (~type-array x)) (count x)) (= (vec (~type-array x)) x)) -;; [] ;; ERROR + [] [1] [1 -2 3 0 5] ) diff --git a/test/clojure/test_clojure/numbers.clj b/test/clojure/test_clojure/numbers.clj index 9f3cfdb2..98157eb5 100644 --- a/test/clojure/test_clojure/numbers.clj +++ b/test/clojure/test_clojure/numbers.clj @@ -24,7 +24,7 @@ (deftest Coerced-Byte (let [v (byte 3)] - (are [x] + (are [x] (true? x) (instance? Byte v) (number? v) (integer? v) @@ -32,7 +32,7 @@ (deftest Coerced-Short (let [v (short 3)] - (are [x] + (are [x] (true? x) (instance? Short v) (number? v) (integer? v) @@ -40,7 +40,7 @@ (deftest Coerced-Integer (let [v (int 3)] - (are [x] + (are [x] (true? x) (instance? Integer v) (number? v) (integer? v) @@ -48,7 +48,7 @@ (deftest Coerced-Long (let [v (long 3)] - (are [x] + (are [x] (true? x) (instance? Long v) (number? v) (integer? v) @@ -56,7 +56,7 @@ (deftest Coerced-BigInteger (let [v (bigint 3)] - (are [x] + (are [x] (true? x) (instance? BigInteger v) (number? v) (integer? v) @@ -64,21 +64,21 @@ (deftest Coerced-Float (let [v (float 3)] - (are [x] + (are [x] (true? x) (instance? Float v) (number? v) (float? v)))) (deftest Coerced-Double (let [v (double 3)] - (are [x] + (are [x] (true? x) (instance? Double v) (number? v) (float? v)))) (deftest Coerced-BigDecimal (let [v (bigdec 3)] - (are [x] + (are [x] (true? x) (instance? BigDecimal v) (number? v) (decimal? v) @@ -370,7 +370,7 @@ ;; even? odd? (deftest test-even? - (are [x] + (are [x] (true? x) (even? -4) (not (even? -3)) (even? 0) @@ -380,7 +380,7 @@ (is (thrown? ArithmeticException (even? (double 10))))) (deftest test-odd? - (are [x] + (are [x] (true? x) (not (odd? -4)) (odd? -3) (not (odd? 0)) @@ -389,3 +389,32 @@ (is (thrown? ArithmeticException (odd? 1/2))) (is (thrown? ArithmeticException (odd? (double 10))))) +(defn- expt + "clojure.contrib.math/expt is a better and much faster impl, but this works. +Math/pow overflows to Infinity." + [x n] (apply * (replicate n x))) + +(deftest test-bit-shift-left + (are [x y] (= x y) + 2r10 (bit-shift-left 2r1 1) + 2r100 (bit-shift-left 2r1 2) + 2r1000 (bit-shift-left 2r1 3) + 2r00101110 (bit-shift-left 2r00010111 1) + 2r00101110 (apply bit-shift-left [2r00010111 1]) + 2r01 (bit-shift-left 2r10 -1) + (expt 2 32) (bit-shift-left 1 32) + (expt 2 10000) (bit-shift-left 1 10000) + )) + +(deftest test-bit-shift-right + (are [x y] (= x y) + 2r0 (bit-shift-right 2r1 1) + 2r010 (bit-shift-right 2r100 1) + 2r001 (bit-shift-right 2r100 2) + 2r000 (bit-shift-right 2r100 3) + 2r0001011 (bit-shift-right 2r00010111 1) + 2r0001011 (apply bit-shift-right [2r00010111 1]) + 2r100 (bit-shift-right 2r10 -1) + 1 (bit-shift-right (expt 2 32) 32) + 1 (bit-shift-right (expt 2 10000) 10000) + )) diff --git a/test/clojure/test_clojure/predicates.clj b/test/clojure/test_clojure/predicates.clj index 8e68c757..2923ef3d 100644 --- a/test/clojure/test_clojure/predicates.clj +++ b/test/clojure/test_clojure/predicates.clj @@ -137,6 +137,6 @@ ;; http://groups.google.com/group/clojure/browse_thread/thread/537761a06edb4b06/bfd4f0705b746a38 ;; (deftest test-string?-more - (are (not (string? _)) + (are [x] (not (string? x)) (new java.lang.StringBuilder "abc") (new java.lang.StringBuffer "xyz"))) diff --git a/test/clojure/test_clojure/reader.clj b/test/clojure/test_clojure/reader.clj index b04543a9..223e9d75 100644 --- a/test/clojure/test_clojure/reader.clj +++ b/test/clojure/test_clojure/reader.clj @@ -222,7 +222,18 @@ ;; Keywords -(deftest t-Keywords) +(deftest t-Keywords + (is (= :abc (keyword "abc"))) + (is (= :abc (keyword 'abc))) + (is (= :*+!-_? (keyword "*+!-_?"))) + (is (= :abc:def:ghi (keyword "abc:def:ghi"))) + (is (= :abc/def (keyword "abc" "def"))) + (is (= :abc/def (keyword 'abc/def))) + (is (= :abc.def/ghi (keyword "abc.def" "ghi"))) + (is (= :abc/def.ghi (keyword "abc" "def.ghi"))) + (is (= :abc:def/ghi:jkl.mno (keyword "abc:def" "ghi:jkl.mno"))) + (is (instance? clojure.lang.Keyword :alphabet)) + ) ;; Lists @@ -286,7 +297,7 @@ ;; Unquote-splicing (~@) (deftest t-Syntax-quote - (are (= _1 _2) + (are [x y] (= x y) `() () ; was NPE before SVN r1337 )) diff --git a/test/clojure/test_clojure/sequences.clj b/test/clojure/test_clojure/sequences.clj index c1e19342..d735507f 100644 --- a/test/clojure/test_clojure/sequences.clj +++ b/test/clojure/test_clojure/sequences.clj @@ -474,12 +474,12 @@ (is (thrown? IndexOutOfBoundsException (nth [] 0))) (is (thrown? IndexOutOfBoundsException (nth [1 2 3] 5))) (is (thrown? IndexOutOfBoundsException (nth [] -1))) - (is (thrown? ArrayIndexOutOfBoundsException (nth [1 2 3] -1))) ; ??? + (is (thrown? IndexOutOfBoundsException (nth [1 2 3] -1))) ; ??? - (is (thrown? ArrayIndexOutOfBoundsException (nth (into-array []) 0))) - (is (thrown? ArrayIndexOutOfBoundsException (nth (into-array [1 2 3]) 5))) - (is (thrown? ArrayIndexOutOfBoundsException (nth (into-array []) -1))) - (is (thrown? ArrayIndexOutOfBoundsException (nth (into-array [1 2 3]) -1))) + (is (thrown? IndexOutOfBoundsException (nth (into-array []) 0))) + (is (thrown? IndexOutOfBoundsException (nth (into-array [1 2 3]) 5))) + (is (thrown? IndexOutOfBoundsException (nth (into-array []) -1))) + (is (thrown? IndexOutOfBoundsException (nth (into-array [1 2 3]) -1))) (is (thrown? StringIndexOutOfBoundsException (nth "" 0))) (is (thrown? StringIndexOutOfBoundsException (nth "abc" 5))) @@ -488,8 +488,8 @@ (is (thrown? IndexOutOfBoundsException (nth (java.util.ArrayList. []) 0))) (is (thrown? IndexOutOfBoundsException (nth (java.util.ArrayList. [1 2 3]) 5))) - (is (thrown? ArrayIndexOutOfBoundsException (nth (java.util.ArrayList. []) -1))) ; ??? - (is (thrown? ArrayIndexOutOfBoundsException (nth (java.util.ArrayList. [1 2 3]) -1))) ; ??? + (is (thrown? IndexOutOfBoundsException (nth (java.util.ArrayList. []) -1))) ; ??? + (is (thrown? IndexOutOfBoundsException (nth (java.util.ArrayList. [1 2 3]) -1))) ; ??? (are [x y] (= x y) (nth '(1) 0) 1 @@ -980,5 +980,29 @@ true (not-any? #{:a} [:b :b]) )) -; TODO: some - +(deftest test-some + ;; always nil for nil or empty coll/seq + (are [x] (= (some pos? x) nil) + nil + () [] {} #{} + (lazy-seq []) + (into-array [])) + + (are [x y] (= x y) + nil (some nil nil) + + true (some pos? [1]) + true (some pos? [1 2]) + + nil (some pos? [-1]) + nil (some pos? [-1 -2]) + true (some pos? [-1 2]) + true (some pos? [1 -2]) + + :a (some #{:a} [:a :a]) + :a (some #{:a} [:b :a]) + nil (some #{:a} [:b :b]) + + :a (some #{:a} '(:a :b)) + :a (some #{:a} #{:a :b}) + )) diff --git a/test/clojure/test_clojure/test.clj b/test/clojure/test_clojure/test.clj index 38cf802f..aa1fea2d 100644 --- a/test/clojure/test_clojure/test.clj +++ b/test/clojure/test_clojure/test.clj @@ -6,7 +6,7 @@ ; the terms of this license. ; You must not remove this notice, or any other, from this software. -;;; test_contrib/test_is.clj: unit tests for test_is.clj +;;; test_clojure/test.clj: unit tests for test.clj ;; by Stuart Sierra ;; January 16, 2009 @@ -83,7 +83,7 @@ (is (does-not-exist) "Should error")) -;; Here, we create an alternate version of test-is/report, that +;; Here, we create an alternate version of test/report, that ;; compares the event with the message, then calls the original ;; 'report' with modified arguments. @@ -105,7 +105,7 @@ (original-report {:type :fail, :message (str msg " but got " event) :expected expected, :actual actual})))) -;; test-ns-hook will be used by test-is/test-ns to run tests in this +;; test-ns-hook will be used by test/test-ns to run tests in this ;; namespace. (defn test-ns-hook [] (binding [original-report report diff --git a/test/clojure/test_clojure/test_fixtures.clj b/test/clojure/test_clojure/test_fixtures.clj index 707dcc38..9a85b58b 100644 --- a/test/clojure/test_clojure/test_fixtures.clj +++ b/test/clojure/test_clojure/test_fixtures.clj @@ -6,7 +6,7 @@ ; the terms of this license. ; You must not remove this notice, or any other, from this software. ; -;;; test_is_fixtures.clj: unit tests for fixtures in test_is.clj +;;; test_fixtures.clj: unit tests for fixtures in test.clj ;; by Stuart Sierra ;; March 28, 2009 diff --git a/test/clojure/test_clojure/vars.clj b/test/clojure/test_clojure/vars.clj index cbdc72d9..e83c88c2 100644 --- a/test/clojure/test_clojure/vars.clj +++ b/test/clojure/test_clojure/vars.clj @@ -6,7 +6,7 @@ ; the terms of this license. ; You must not remove this notice, or any other, from this software. -; Author: Frantisek Sodomka +; Author: Frantisek Sodomka, Stephen C. Gilardi (ns clojure.test-clojure.vars @@ -21,7 +21,7 @@ (def a) (deftest test-binding - (are [_1 _2] (= _1 _2) + (are [x y] (= x y) (eval `(binding [a 4] a)) 4 ; regression in Clojure SVN r1370 )) |