summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Hickey <richhickey@gmail.com>2009-10-22 18:33:32 -0400
committerRich Hickey <richhickey@gmail.com>2009-10-22 18:33:32 -0400
commitc0218cfc80ba9ae4c1808e8f5644e14c464a5268 (patch)
treec12187f5abc452c9aa64338cbb31663f8bea12d5
parent3d69750b26d41d72920264ce8d338c20be7383a9 (diff)
parentd910b3d997e1c40528aab2212fe356a8598bb738 (diff)
Merge branch 'master' into new
-rw-r--r--build.xml8
-rw-r--r--src/clj/clojure/core.clj478
-rw-r--r--src/clj/clojure/core_proxy.clj13
-rw-r--r--src/clj/clojure/main.clj6
-rw-r--r--src/clj/clojure/test.clj2
-rw-r--r--src/clj/clojure/test/tap.clj2
-rw-r--r--src/clj/clojure/xml.clj18
-rw-r--r--src/clj/clojure/zip.clj10
-rw-r--r--src/jvm/clojure/lang/ATransientMap.java86
-rw-r--r--src/jvm/clojure/lang/ATransientSet.java54
-rw-r--r--src/jvm/clojure/lang/ArraySeq.java376
-rw-r--r--src/jvm/clojure/lang/Associative.java5
-rw-r--r--src/jvm/clojure/lang/Compiler.java36
-rw-r--r--src/jvm/clojure/lang/IEditableCollection.java17
-rw-r--r--src/jvm/clojure/lang/ILookup.java19
-rw-r--r--src/jvm/clojure/lang/ITransientAssociative.java18
-rw-r--r--src/jvm/clojure/lang/ITransientCollection.java20
-rw-r--r--src/jvm/clojure/lang/ITransientMap.java22
-rw-r--r--src/jvm/clojure/lang/ITransientSet.java19
-rw-r--r--src/jvm/clojure/lang/ITransientVector.java20
-rw-r--r--src/jvm/clojure/lang/Keyword.java12
-rw-r--r--src/jvm/clojure/lang/LazilyPersistentVector.java136
-rw-r--r--src/jvm/clojure/lang/LispReader.java15
-rw-r--r--src/jvm/clojure/lang/LockingTransaction.java129
-rw-r--r--src/jvm/clojure/lang/Numbers.java8
-rw-r--r--src/jvm/clojure/lang/PersistentArrayMap.java609
-rw-r--r--src/jvm/clojure/lang/PersistentHashMap.java1100
-rw-r--r--src/jvm/clojure/lang/PersistentHashSet.java16
-rw-r--r--src/jvm/clojure/lang/PersistentVector.java571
-rw-r--r--src/jvm/clojure/lang/RT.java25
-rw-r--r--src/jvm/clojure/lang/Ref.java2
-rw-r--r--src/jvm/clojure/lang/StringSeq.java4
-rw-r--r--src/jvm/clojure/lang/Symbol.java2
-rw-r--r--test/clojure/test_clojure.clj20
-rw-r--r--test/clojure/test_clojure/compilation.clj6
-rw-r--r--test/clojure/test_clojure/control.clj169
-rw-r--r--test/clojure/test_clojure/for.clj5
-rw-r--r--test/clojure/test_clojure/java_interop.clj4
-rw-r--r--test/clojure/test_clojure/numbers.clj49
-rw-r--r--test/clojure/test_clojure/predicates.clj2
-rw-r--r--test/clojure/test_clojure/reader.clj15
-rw-r--r--test/clojure/test_clojure/sequences.clj42
-rw-r--r--test/clojure/test_clojure/test.clj6
-rw-r--r--test/clojure/test_clojure/test_fixtures.clj2
-rw-r--r--test/clojure/test_clojure/vars.clj4
45 files changed, 3028 insertions, 1154 deletions
diff --git a/build.xml b/build.xml
index b37adefc..482e9724 100644
--- a/build.xml
+++ b/build.xml
@@ -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
))