summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/clj/clojure/core.clj226
-rw-r--r--src/jvm/clojure/lang/Compiler.java309
-rw-r--r--test/clojure/test_clojure/control.clj86
-rw-r--r--test/clojure/test_clojure/rt.clj27
-rw-r--r--test/clojure/test_helper.clj35
5 files changed, 546 insertions, 137 deletions
diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
index c928e4f7..6f41594e 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -5714,25 +5714,154 @@
(map #(cons `fn %) fnspecs)))
~@body))
+(defn fnil
+ "Takes a function f, and returns a function that calls f, replacing
+ a nil first argument to f with the supplied value x. Higher arity
+ versions can replace arguments in the second and third
+ positions (y, z). Note that the function f can take any number of
+ arguments, not just the one(s) being nil-patched."
+ {:added "1.2"
+ :static true}
+ ([f x]
+ (fn
+ ([a] (f (if (nil? a) x a)))
+ ([a b] (f (if (nil? a) x a) b))
+ ([a b c] (f (if (nil? a) x a) b c))
+ ([a b c & ds] (apply f (if (nil? a) x a) b c ds))))
+ ([f x y]
+ (fn
+ ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
+ ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c))
+ ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds))))
+ ([f x y z]
+ (fn
+ ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
+ ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
+ ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))
+
;;;;;;; case ;;;;;;;;;;;;;
(defn- shift-mask [shift mask x]
(-> x (bit-shift-right shift) (bit-and mask)))
+(def ^:private max-mask-bits 13)
+(def ^:private max-switch-table-size (bit-shift-left 1 max-mask-bits))
+
+(defn- maybe-min-hash
+ "takes a collection of hashes and returns [shift mask] or nil if none found"
+ [hashes]
+ (first
+ (filter (fn [[s m]]
+ (apply distinct? (map #(shift-mask s m %) hashes)))
+ (for [mask (map #(dec (bit-shift-left 1 %)) (range 1 (inc max-mask-bits)))
+ shift (range 0 31)]
+ [shift mask]))))
+
(defn- min-hash
"takes a collection of keys and returns [shift mask]"
[keys]
(let [hashes (map hash keys)
- cnt (count keys)]
- (when-not (apply distinct? hashes)
- (throw (IllegalArgumentException. "Hashes must be distinct")))
- (or (first
- (filter (fn [[s m]]
- (apply distinct? (map #(shift-mask s m %) hashes)))
- (for [mask (map #(dec (bit-shift-left 1 %)) (range 1 14))
- shift (range 0 31)]
- [shift mask])))
- (throw (IllegalArgumentException. "No distinct mapping found")))))
+ _ (when-not (apply distinct? hashes)
+ (throw (IllegalArgumentException. "Hashes must be distinct")))
+ sm (maybe-min-hash hashes)]
+ (or sm (throw (IllegalArgumentException. "No distinct mapping found")))))
+
+(defn- case-map
+ "Transforms a sequence of test constants and a corresponding sequence of then
+ expressions into a sorted map to be consumed by case*. The form of the map
+ entries are {(case-f test) [(test-f test) then]}."
+ [case-f test-f tests thens]
+ (into1 (sorted-map)
+ (zipmap (map case-f tests)
+ (map vector
+ (map test-f tests)
+ thens))))
+
+(defn- fits-table?
+ "Returns true if the collection of ints can fit within the
+ max-table-switch-size, false otherwise."
+ [ints]
+ (< (- (apply max (seq ints)) (apply min (seq ints))) max-switch-table-size))
+
+(defn- prep-ints
+ "Takes a sequence of int-sized test constants and a corresponding sequence of
+ then expressions. Returns a tuple of [shift mask case-map switch-type] where
+ case-map is a map of int case values to [test then] tuples, and switch-type
+ is either :sparse or :compact."
+ [tests thens]
+ (if (fits-table? tests)
+ ; compact case ints, no shift-mask
+ [0 0 (case-map int int tests thens) :compact]
+ (let [[shift mask] (or (maybe-min-hash (map int tests)) [0 0])]
+ (if (zero? mask)
+ ; sparse case ints, no shift-mask
+ [0 0 (case-map int int tests thens) :sparse]
+ ; compact case ints, with shift-mask
+ [shift mask (case-map #(shift-mask shift mask (int %)) int tests thens) :compact]))))
+
+(defn- merge-hash-collisions
+ "Takes a case expression, default expression, and a sequence of test constants
+ and a corresponding sequence of then expressions. Returns a tuple of
+ [tests thens skip-check-set] where no tests have the same hash. Each set of
+ input test constants with the same hash is replaced with a single test
+ constant (the case int), and their respective thens are combined into:
+ (condp = expr
+ test-1 then-1
+ ...
+ test-n then-n
+ default).
+ The skip-check is a set of case ints for which post-switch equivalence
+ checking must not be done (the cases holding the above condp thens)."
+ [expr-sym default tests thens]
+ (let [buckets (loop [m {} ks tests vs thens]
+ (if (and ks vs)
+ (recur
+ (update-in m [(hash (first ks))] (fnil conj []) [(first ks) (first vs)])
+ (next ks) (next vs))
+ m))
+ assoc-multi (fn [m h bucket]
+ (let [testexprs (apply concat bucket)
+ expr `(condp = ~expr-sym ~@testexprs ~default)]
+ (assoc m h expr)))
+ hmap (reduce1
+ (fn [m [h bucket]]
+ (if (== 1 (count bucket))
+ (assoc m (ffirst bucket) (second (first bucket)))
+ (assoc-multi m h bucket)))
+ {} buckets)
+ skip-check (->> buckets
+ (filter #(< 1 (count (second %))))
+ (map first)
+ (into1 #{}))]
+ [(keys hmap) (vals hmap) skip-check]))
+
+(defn- prep-hashes
+ "Takes a sequence of test constants and a corresponding sequence of then
+ expressions. Returns a tuple of [shift mask case-map switch-type skip-check]
+ where case-map is a map of int case values to [test then] tuples, switch-type
+ is either :sparse or :compact, and skip-check is a set of case ints for which
+ post-switch equivalence checking must not be done (occurs with hash
+ collisions)."
+ [expr-sym default tests thens]
+ (let [hashes (into1 #{} (map hash tests))]
+ (if (== (count tests) (count hashes))
+ (if (fits-table? hashes)
+ ; compact case ints, no shift-mask
+ [0 0 (case-map hash identity tests thens) :compact]
+ (let [[shift mask] (or (maybe-min-hash hashes) [0 0])]
+ (if (zero? mask)
+ ; sparse case ints, no shift-mask
+ [0 0 (case-map hash identity tests thens) :sparse]
+ ; compact case ints, with shift-mask
+ [shift mask (case-map #(shift-mask shift mask (hash %)) identity tests thens) :compact])))
+ ; resolve hash collisions and try again
+ (let [[tests thens skip-check] (merge-hash-collisions expr-sym default tests thens)
+ [shift mask case-map switch-type] (prep-hashes expr-sym default tests thens)
+ skip-check (if (zero? mask)
+ skip-check
+ (into1 #{} (map #(shift-mask shift mask %) skip-check)))]
+ [shift mask case-map switch-type skip-check]))))
+
(defmacro case
"Takes an expression, and a set of clauses.
@@ -5763,24 +5892,40 @@
(let [ge (with-meta (gensym) {:tag Object})
default (if (odd? (count clauses))
(last clauses)
- `(throw (IllegalArgumentException. (str "No matching clause: " ~ge))))
- cases (partition 2 clauses)
- case-map (reduce1 (fn [m [test expr]]
- (if (seq? test)
- (into1 m (zipmap test (repeat expr)))
- (assoc m test expr)))
- {} cases)
- [shift mask] (if (seq case-map) (min-hash (keys case-map)) [0 0])
-
- hmap (reduce1 (fn [m [test expr :as te]]
- (assoc m (shift-mask shift mask (hash test)) te))
- (sorted-map) case-map)]
- `(let [~ge ~e]
- ~(condp = (count clauses)
- 0 default
- 1 default
- `(case* ~ge ~shift ~mask ~(key (first hmap)) ~(key (last hmap)) ~default ~hmap
- ~(every? keyword? (keys case-map)))))))
+ `(throw (IllegalArgumentException. (str "No matching clause: " ~ge))))]
+ (if (> 2 (count clauses))
+ `(let [~ge ~e] ~default)
+ (let [pairs (partition 2 clauses)
+ assoc-test (fn assoc-test [m test expr]
+ (if (contains? m test)
+ (throw (IllegalArgumentException. (str "Duplicate case test constant: " test)))
+ (assoc m test expr)))
+ pairs (reduce1
+ (fn [m [test expr]]
+ (if (seq? test)
+ (reduce1 #(assoc-test %1 %2 expr) m test)
+ (assoc-test m test expr)))
+ {} pairs)
+ tests (keys pairs)
+ thens (vals pairs)
+ mode (cond
+ (every? #(and (integer? %) (<= Integer/MIN_VALUE % Integer/MAX_VALUE)) tests)
+ :ints
+ (every? keyword? tests)
+ :identity
+ :else :hashes)]
+ (condp = mode
+ :ints
+ (let [[shift mask imap switch-type] (prep-ints tests thens)]
+ `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :int)))
+ :hashes
+ (let [[shift mask imap switch-type skip-check] (prep-hashes ge default tests thens)]
+ `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :hash-equiv ~skip-check)))
+ :identity
+ (let [[shift mask imap switch-type skip-check] (prep-hashes ge default tests thens)]
+ `(let [~ge ~e] (case* ~ge ~shift ~mask ~default ~imap ~switch-type :hash-identity ~skip-check))))))))
+
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; helper files ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(alter-meta! (find-ns 'clojure.core) assoc :doc "Fundamental library of the Clojure language")
@@ -6194,31 +6339,6 @@
(cons x (keepi (inc idx) (rest s)))))))))]
(keepi 0 coll))))
-(defn fnil
- "Takes a function f, and returns a function that calls f, replacing
- a nil first argument to f with the supplied value x. Higher arity
- versions can replace arguments in the second and third
- positions (y, z). Note that the function f can take any number of
- arguments, not just the one(s) being nil-patched."
- {:added "1.2"
- :static true}
- ([f x]
- (fn
- ([a] (f (if (nil? a) x a)))
- ([a b] (f (if (nil? a) x a) b))
- ([a b c] (f (if (nil? a) x a) b c))
- ([a b c & ds] (apply f (if (nil? a) x a) b c ds))))
- ([f x y]
- (fn
- ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
- ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c))
- ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds))))
- ([f x y z]
- (fn
- ([a b] (f (if (nil? a) x a) (if (nil? b) y b)))
- ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c)))
- ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds)))))
-
(defn every-pred
"Takes a set of predicates and returns a function f that returns true if all of its
composing predicates return a logical true value against all of its arguments, else it returns
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index d037186e..89d5df4e 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -1212,6 +1212,31 @@ static Class maybePrimitiveType(Expr e){
return null;
}
+static Class maybeJavaClass(Collection<Expr> exprs){
+ Class match = null;
+ try
+ {
+ for (Expr e : exprs)
+ {
+ if (e instanceof ThrowExpr)
+ continue;
+ if (!e.hasJavaClass())
+ return null;
+ Class c = e.getJavaClass();
+ if (match == null)
+ match = c;
+ else if (match != c)
+ return null;
+ }
+ }
+ catch(Exception e)
+ {
+ return null;
+ }
+ return match;
+}
+
+
static abstract class MethodExpr extends HostExpr{
static void emitArgsAsArray(IPersistentVector args, ObjExpr objx, GeneratorAdapter gen){
gen.push(args.count());
@@ -7700,23 +7725,32 @@ static public class MethodParamExpr implements Expr, MaybePrimitiveExpr{
}
}
-public static class CaseExpr extends UntypedExpr{
+public static class CaseExpr implements Expr, MaybePrimitiveExpr{
public final LocalBindingExpr expr;
public final int shift, mask, low, high;
public final Expr defaultExpr;
- public final HashMap<Integer,Expr> tests;
+ public final SortedMap<Integer,Expr> tests;
public final HashMap<Integer,Expr> thens;
- public final boolean allKeywords;
-
+ public final Keyword switchType;
+ public final Keyword testType;
+ public final Set<Integer> skipCheck;
+ public final Class returnType;
public final int line;
+ final static Type NUMBER_TYPE = Type.getType(Number.class);
+ final static Method intValueMethod = Method.getMethod("int intValue()");
+
final static Method hashMethod = Method.getMethod("int hash(Object)");
final static Method hashCodeMethod = Method.getMethod("int hashCode()");
- final static Method equalsMethod = Method.getMethod("boolean equals(Object, Object)");
-
-
+ final static Method equivMethod = Method.getMethod("boolean equiv(Object, Object)");
+ final static Keyword compactKey = Keyword.intern(null, "compact");
+ final static Keyword sparseKey = Keyword.intern(null, "sparse");
+ final static Keyword hashIdentityKey = Keyword.intern(null, "hash-identity");
+ final static Keyword hashEquivKey = Keyword.intern(null, "hash-equiv");
+ final static Keyword intKey = Keyword.intern(null, "int");
+ //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
public CaseExpr(int line, LocalBindingExpr expr, int shift, int mask, int low, int high, Expr defaultExpr,
- HashMap<Integer,Expr> tests,HashMap<Integer,Expr> thens, boolean allKeywords){
+ SortedMap<Integer,Expr> tests,HashMap<Integer,Expr> thens, Keyword switchType, Keyword testType, Set<Integer> skipCheck){
this.expr = expr;
this.shift = shift;
this.mask = mask;
@@ -7726,66 +7760,217 @@ public static class CaseExpr extends UntypedExpr{
this.tests = tests;
this.thens = thens;
this.line = line;
- this.allKeywords = allKeywords;
+ if (switchType != compactKey && switchType != sparseKey)
+ throw new IllegalArgumentException("Unexpected switch type: "+switchType);
+ this.switchType = switchType;
+ if (testType != intKey && testType != hashEquivKey && testType != hashIdentityKey)
+ throw new IllegalArgumentException("Unexpected test type: "+switchType);
+ this.testType = testType;
+ this.skipCheck = skipCheck;
+ Collection<Expr> returns = new ArrayList(thens.values());
+ returns.add(defaultExpr);
+ this.returnType = maybeJavaClass(returns);
+ if(RT.count(skipCheck) > 0 && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Performance warning, %s:%d - hash collision of some case test constants; if selected, those entries will be tested sequentially.\n",
+ SOURCE_PATH.deref(), line);
+ }
+ }
+
+ public boolean hasJavaClass(){
+ return returnType != null;
+ }
+
+ public boolean canEmitPrimitive(){
+ return Util.isPrimitive(returnType);
+ }
+
+ public Class getJavaClass(){
+ return returnType;
}
public Object eval() {
throw new UnsupportedOperationException("Can't eval case");
}
- public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
+ public void emit(C context, ObjExpr objx, GeneratorAdapter gen){
+ doEmit(context, objx, gen, false);
+ }
+
+ public void emitUnboxed(C context, ObjExpr objx, GeneratorAdapter gen){
+ doEmit(context, objx, gen, true);
+ }
+
+ public void doEmit(C context, ObjExpr objx, GeneratorAdapter gen, boolean emitUnboxed){
Label defaultLabel = gen.newLabel();
Label endLabel = gen.newLabel();
- HashMap<Integer,Label> labels = new HashMap();
+ SortedMap<Integer,Label> labels = new TreeMap();
for(Integer i : tests.keySet())
{
labels.put(i, gen.newLabel());
}
- Label[] la = new Label[(high-low)+1];
+ gen.visitLineNumber(line, gen.mark());
- for(int i=low;i<=high;i++)
- {
- la[i-low] = labels.containsKey(i) ? labels.get(i) : defaultLabel;
- }
+ Class primExprClass = maybePrimitiveType(expr);
+ Type primExprType = primExprClass == null ? null : Type.getType(primExprClass);
- gen.visitLineNumber(line, gen.mark());
- expr.emit(C.EXPRESSION, objx, gen);
- gen.invokeStatic(UTIL_TYPE,hashMethod);
- gen.push(shift);
- gen.visitInsn(ISHR);
- gen.push(mask);
- gen.visitInsn(IAND);
- gen.visitTableSwitchInsn(low, high, defaultLabel, la);
+ if (testType == intKey)
+ emitExprForInts(objx, gen, primExprType, defaultLabel);
+ else
+ emitExprForHashes(objx, gen);
+
+ if (switchType == sparseKey)
+ {
+ Label[] la = new Label[labels.size()];
+ la = labels.values().toArray(la);
+ int[] ints = Numbers.int_array(tests.keySet());
+ gen.visitLookupSwitchInsn(defaultLabel, ints, la);
+ }
+ else
+ {
+ Label[] la = new Label[(high-low)+1];
+ for(int i=low;i<=high;i++)
+ {
+ la[i-low] = labels.containsKey(i) ? labels.get(i) : defaultLabel;
+ }
+ gen.visitTableSwitchInsn(low, high, defaultLabel, la);
+ }
for(Integer i : labels.keySet())
{
gen.mark(labels.get(i));
- expr.emit(C.EXPRESSION, objx, gen);
- tests.get(i).emit(C.EXPRESSION, objx, gen);
- if(allKeywords)
- {
- gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
- }
+ if (testType == intKey)
+ emitThenForInts(objx, gen, primExprType, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
+ else if (RT.contains(skipCheck, i) == RT.T)
+ emitExpr(objx, gen, thens.get(i), emitUnboxed);
else
- {
- gen.invokeStatic(UTIL_TYPE, equalsMethod);
- gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
- }
- thens.get(i).emit(C.EXPRESSION,objx,gen);
+ emitThenForHashes(objx, gen, tests.get(i), thens.get(i), defaultLabel, emitUnboxed);
gen.goTo(endLabel);
}
gen.mark(defaultLabel);
- defaultExpr.emit(C.EXPRESSION, objx, gen);
+ emitExpr(objx, gen, defaultExpr, emitUnboxed);
gen.mark(endLabel);
if(context == C.STATEMENT)
gen.pop();
}
+ private boolean isShiftMasked(){
+ return mask != 0;
+ }
+
+ private void emitShiftMask(GeneratorAdapter gen){
+ if (isShiftMasked())
+ {
+ gen.push(shift);
+ gen.visitInsn(ISHR);
+ gen.push(mask);
+ gen.visitInsn(IAND);
+ }
+ }
+
+ private void emitExprForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Label defaultLabel){
+ if (exprType == null)
+ {
+ if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Performance warning, %s:%d - case has int tests, but tested expression is not primitive.\n",
+ SOURCE_PATH.deref(), line);
+ }
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.instanceOf(NUMBER_TYPE);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.checkCast(NUMBER_TYPE);
+ gen.invokeVirtual(NUMBER_TYPE, intValueMethod);
+ emitShiftMask(gen);
+ }
+ else if (exprType == Type.LONG_TYPE
+ || exprType == Type.INT_TYPE
+ || exprType == Type.SHORT_TYPE
+ || exprType == Type.BYTE_TYPE)
+ {
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.cast(exprType, Type.INT_TYPE);
+ emitShiftMask(gen);
+ }
+ else
+ {
+ gen.goTo(defaultLabel);
+ }
+ }
+
+ private void emitThenForInts(ObjExpr objx, GeneratorAdapter gen, Type exprType, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
+ if (exprType == null)
+ {
+ expr.emit(C.EXPRESSION, objx, gen);
+ test.emit(C.EXPRESSION, objx, gen);
+ gen.invokeStatic(UTIL_TYPE, equivMethod);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else if (exprType == Type.LONG_TYPE)
+ {
+ ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else if (exprType == Type.INT_TYPE
+ || exprType == Type.SHORT_TYPE
+ || exprType == Type.BYTE_TYPE)
+ {
+ if (isShiftMasked())
+ {
+ ((NumberExpr)test).emitUnboxed(C.EXPRESSION, objx, gen);
+ expr.emitUnboxed(C.EXPRESSION, objx, gen);
+ gen.cast(exprType, Type.LONG_TYPE);
+ gen.ifCmp(Type.LONG_TYPE, GeneratorAdapter.NE, defaultLabel);
+ }
+ // else direct match
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+ else
+ {
+ gen.goTo(defaultLabel);
+ }
+ }
+
+ private void emitExprForHashes(ObjExpr objx, GeneratorAdapter gen){
+ expr.emit(C.EXPRESSION, objx, gen);
+ gen.invokeStatic(UTIL_TYPE,hashMethod);
+ emitShiftMask(gen);
+ }
+
+ private void emitThenForHashes(ObjExpr objx, GeneratorAdapter gen, Expr test, Expr then, Label defaultLabel, boolean emitUnboxed){
+ expr.emit(C.EXPRESSION, objx, gen);
+ test.emit(C.EXPRESSION, objx, gen);
+ if(testType == hashIdentityKey)
+ {
+ gen.visitJumpInsn(IF_ACMPNE, defaultLabel);
+ }
+ else
+ {
+ gen.invokeStatic(UTIL_TYPE, equivMethod);
+ gen.ifZCmp(GeneratorAdapter.EQ, defaultLabel);
+ }
+ emitExpr(objx, gen, then, emitUnboxed);
+ }
+
+ private static void emitExpr(ObjExpr objx, GeneratorAdapter gen, Expr expr, boolean emitUnboxed){
+ if (emitUnboxed && expr instanceof MaybePrimitiveExpr)
+ ((MaybePrimitiveExpr)expr).emitUnboxed(C.EXPRESSION,objx,gen);
+ else
+ expr.emit(C.EXPRESSION,objx,gen);
+ }
+
+
static class Parser implements IParser{
- //(case* expr shift mask low high default map<minhash, [test then]> identity?)
+ //(case* expr shift mask default map<minhash, [test then]> table-type test-type skip-check?)
//prepared by case macro and presumed correct
//case macro binds actual expr in let so expr is always a local,
//no need to worry about multiple evaluation
@@ -7794,25 +7979,43 @@ public static class CaseExpr extends UntypedExpr{
if(context == C.EVAL)
return analyze(context, RT.list(RT.list(FN, PersistentVector.EMPTY, form)));
PersistentVector args = PersistentVector.create(form.next());
- HashMap<Integer,Expr> tests = new HashMap();
- HashMap<Integer,Expr> thens = new HashMap();
- LocalBindingExpr testexpr = (LocalBindingExpr) analyze(C.EXPRESSION, args.nth(0));
+ Object exprForm = args.nth(0);
+ int shift = ((Number)args.nth(1)).intValue();
+ int mask = ((Number)args.nth(2)).intValue();
+ Object defaultForm = args.nth(3);
+ Map caseMap = (Map)args.nth(4);
+ Keyword switchType = ((Keyword)args.nth(5));
+ Keyword testType = ((Keyword)args.nth(6));
+ Set skipCheck = RT.count(args) < 8 ? null : (Set)args.nth(7);
+
+ ISeq keys = RT.keys(caseMap);
+ int low = ((Number)RT.first(keys)).intValue();
+ int high = ((Number)RT.nth(keys, RT.count(keys)-1)).intValue();
+
+ LocalBindingExpr testexpr = (LocalBindingExpr) analyze(C.EXPRESSION, exprForm);
testexpr.shouldClear = false;
-
+
+ SortedMap<Integer,Expr> tests = new TreeMap();
+ HashMap<Integer,Expr> thens = new HashMap();
+
PathNode branch = new PathNode(PATHTYPE.BRANCH, (PathNode) CLEAR_PATH.get());
- for(Object o : ((Map)args.nth(6)).entrySet())
+
+ for(Object o : caseMap.entrySet())
{
Map.Entry e = (Map.Entry) o;
Integer minhash = ((Number)e.getKey()).intValue();
- MapEntry me = (MapEntry) e.getValue();
- Expr testExpr = new ConstantExpr(me.getKey());
- tests.put(minhash, testExpr);
+ Object pair = e.getValue(); // [test-val then-expr]
+ Expr testExpr = testType == intKey
+ ? NumberExpr.parse(((Number)RT.first(pair)).intValue())
+ : new ConstantExpr(RT.first(pair));
+ tests.put(minhash, testExpr);
+
Expr thenExpr;
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
- thenExpr = analyze(context, me.getValue());
+ thenExpr = analyze(context, RT.second(pair));
}
finally{
Var.popThreadBindings();
@@ -7824,21 +8027,15 @@ public static class CaseExpr extends UntypedExpr{
try {
Var.pushThreadBindings(
RT.map(CLEAR_PATH, new PathNode(PATHTYPE.PATH,branch)));
- defaultExpr = analyze(context, args.nth(5));
+ defaultExpr = analyze(context, args.nth(3));
}
finally{
Var.popThreadBindings();
}
- return new CaseExpr(((Number)LINE.deref()).intValue(),
- testexpr,
- ((Number)args.nth(1)).intValue(),
- ((Number)args.nth(2)).intValue(),
- ((Number)args.nth(3)).intValue(),
- ((Number)args.nth(4)).intValue(),
- defaultExpr,
- tests,thens,args.nth(7) != RT.F);
-
+ int line = ((Number)LINE.deref()).intValue();
+ return new CaseExpr(line, testexpr, shift, mask, low, high,
+ defaultExpr, tests, thens, switchType, testType, skipCheck);
}
}
}
diff --git a/test/clojure/test_clojure/control.clj b/test/clojure/test_clojure/control.clj
index 49cfe95b..3a7d4c3a 100644
--- a/test/clojure/test_clojure/control.clj
+++ b/test/clojure/test_clojure/control.clj
@@ -14,7 +14,7 @@
(ns clojure.test-clojure.control
(:use clojure.test
- [clojure.test-helper :only (exception)]))
+ clojure.test-helper))
;; *** Helper functions ***
@@ -323,6 +323,90 @@
:map (sorted-map :a 1 :b 2)
:set #{3 2 1}
:set (sorted-set 2 1 3))))
+ (testing "test number equivalence"
+ (is (= :1 (case 1N 1 :1 :else))))
+ (testing "test warn when boxing/hashing expr for all-ints case"
+ (should-print-err-message
+ #"Performance warning, .*:\d+ - case has int tests, but tested expression is not primitive..*\r?\n"
+ (let [x (Object.)] (case x 1 1 2))))
+ (testing "test correct behavior on sparse ints"
+ (are [result input] (= result (case input
+ 2r1000000000000000000000000000000 :big
+ 1 :small
+ :else))
+ :small 1
+ :big 1073741824
+ :else 2)
+ (are [result input] (= result (case input
+ 1 :small
+ 2r1000000000000000000000000000000 :big
+ :else))
+ :small 1
+ :big 1073741824
+ :else 2))
+ (testing "test emits return types"
+ (should-not-reflect (Long. (case 1 1 1))) ; new Long(long)
+ (should-not-reflect (Long. (case 1 1 "1")))) ; new Long(String)
+ (testing "non-equivalence of chars and nums"
+ (are [result input] (= result (case input 97 :97 :else))
+ :else \a
+ :else (char \a)
+ :97 (int \a))
+ (are [result input] (= result (case input \a :a :else))
+ :else 97
+ :else 97N
+ :a (char 97)))
+ (testing "test error on duplicate test constants"
+ (is (thrown-with-msg?
+ IllegalArgumentException
+ #"Duplicate case test constant: 1"
+ (eval `(case 0 1 :x 1 :y)))))
+ (testing "test correct behaviour on Number truncation"
+ (let [^Object x (Long. 8589934591) ; force bindings to not be emitted as a primitive long
+ ^Object y (Long. -1)]
+ (is (= :diff (case x -1 :oops :diff)))
+ (is (= :same (case y -1 :same :oops)))))
+ (testing "test correct behavior on hash collision"
+ (is (== (hash 1) (hash 9223372039002259457N)))
+ (are [result input] (= result (case input
+ 1 :long
+ 9223372039002259457N :big
+ :else))
+ :long 1
+ :big 9223372039002259457N
+ :else 4294967296
+ :else 2)
+ (are [result input] (= result (case input
+ 9223372039002259457N :big
+ 1 :long
+ :else))
+ :long 1
+ :big 9223372039002259457N
+ :else 4294967296
+ :else 2)
+ (are [result input] (= result (case input
+ 0 :zero
+ -1 :neg1
+ 2 :two
+ :oops :OOPS))
+ :zero 0
+ :neg1 -1
+ :two 2
+ :OOPS :oops)
+ (are [result input] (= result (case input
+ 1204766517646190306 :a
+ 1 :b
+ -2 :c
+ :d))
+ :a 1204766517646190306
+ :b 1
+ :c -2
+ :d 4294967296
+ :d 3))
+ (testing "test warn for hash collision"
+ (should-print-err-message
+ #"Performance warning, .*:\d+ - hash collision of some case test constants; if selected, those entries will be tested sequentially..*\r?\n"
+ (case 1 1 :long 9223372039002259457N :big 2)))
(testing "test constants are *not* evaluated"
(let [test-fn
;; never write code like this...
diff --git a/test/clojure/test_clojure/rt.clj b/test/clojure/test_clojure/rt.clj
index 231e12c9..a70df2db 100644
--- a/test/clojure/test_clojure/rt.clj
+++ b/test/clojure/test_clojure/rt.clj
@@ -11,33 +11,6 @@
(ns clojure.test-clojure.rt
(:use clojure.test clojure.test-helper))
-(defmacro with-err-print-writer
- "Evaluate with err pointing to a temporary PrintWriter, and
- return err contents as a string."
- [& body]
- `(let [s# (java.io.StringWriter.)
- p# (java.io.PrintWriter. s#)]
- (binding [*err* p#]
- ~@body
- (str s#))))
-
-(defmacro with-err-string-writer
- "Evaluate with err pointing to a temporary StringWriter, and
- return err contents as a string."
- [& body]
- `(let [s# (java.io.StringWriter.)]
- (binding [*err* s#]
- ~@body
- (str s#))))
-
-(defmacro should-print-err-message
- "Turn on all warning flags, and test that error message prints
- correctly for all semi-reasonable bindings of *err*."
- [msg-re form]
- `(binding [*warn-on-reflection* true]
- (is (re-matches ~msg-re (with-err-string-writer (eval-in-temp-ns ~form))))
- (is (re-matches ~msg-re (with-err-print-writer (eval-in-temp-ns ~form))))))
-
(defn bare-rt-print
"Return string RT would print prior to print-initialize"
[x]
diff --git a/test/clojure/test_helper.clj b/test/clojure/test_helper.clj
index b5d11f43..2e8ba94a 100644
--- a/test/clojure/test_helper.clj
+++ b/test/clojure/test_helper.clj
@@ -95,3 +95,38 @@
reach certain point."
[]
(throw (new Exception "Exception which should never occur")))
+
+(defmacro with-err-print-writer
+ "Evaluate with err pointing to a temporary PrintWriter, and
+ return err contents as a string."
+ [& body]
+ `(let [s# (java.io.StringWriter.)
+ p# (java.io.PrintWriter. s#)]
+ (binding [*err* p#]
+ ~@body
+ (str s#))))
+
+(defmacro with-err-string-writer
+ "Evaluate with err pointing to a temporary StringWriter, and
+ return err contents as a string."
+ [& body]
+ `(let [s# (java.io.StringWriter.)]
+ (binding [*err* s#]
+ ~@body
+ (str s#))))
+
+(defmacro should-print-err-message
+ "Turn on all warning flags, and test that error message prints
+ correctly for all semi-reasonable bindings of *err*."
+ [msg-re form]
+ `(binding [*warn-on-reflection* true]
+ (is (re-matches ~msg-re (with-err-string-writer (eval-in-temp-ns ~form))))
+ (is (re-matches ~msg-re (with-err-print-writer (eval-in-temp-ns ~form))))))
+
+(defmacro should-not-reflect
+ "Turn on all warning flags, and test that reflection does not occur
+ (as identified by messages to *err*)."
+ [form]
+ `(binding [*warn-on-reflection* true]
+ (is (nil? (re-find #"^Reflection warning" (with-err-string-writer (eval-in-temp-ns ~form)))))
+ (is (nil? (re-find #"^Reflection warning" (with-err-print-writer (eval-in-temp-ns ~form)))))))