From 271be827b0af67a072333d74702445a80e96a8d2 Mon Sep 17 00:00:00 2001
From: Roman Scherer <roman.scherer@burningswell.com>
Date: Thu, 19 Jul 2012 16:07:47 +0200
Subject: [PATCH] Implemented conditional compilation.

---
 src/clj/clojure/core.clj             |  6 +++++
 src/jvm/clojure/lang/LispReader.java | 50 +++++++++++++++++++++++++++++++++++-
 test/clojure/test_clojure/reader.clj | 41 +++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+), 1 deletion(-)

diff --git a/src/clj/clojure/core.clj b/src/clj/clojure/core.clj
index c4710c6..ccb0ca1 100644
--- a/src/clj/clojure/core.clj
+++ b/src/clj/clojure/core.clj
@@ -6717,3 +6717,9 @@
  (catch Throwable t
    (.printStackTrace t)
    (throw t)))
+
+(def
+ ^{:dynamic true
+   :doc "Set of platform specific features."
+   :added "1.5"}
+ *features* #{:clojure})
diff --git a/src/jvm/clojure/lang/LispReader.java b/src/jvm/clojure/lang/LispReader.java
index 4b987dc..ca0e395 100644
--- a/src/jvm/clojure/lang/LispReader.java
+++ b/src/jvm/clojure/lang/LispReader.java
@@ -38,6 +38,10 @@ import java.util.regex.Pattern;
 
 public class LispReader{
 
+static final Symbol AND = Symbol.intern("and");
+static final Symbol NOT = Symbol.intern("not");
+static final Symbol OR = Symbol.intern("or");
+
 static final Symbol QUOTE = Symbol.intern("quote");
 static final Symbol THE_VAR = Symbol.intern("var");
 //static Symbol SYNTAX_QUOTE = Symbol.intern(null, "syntax-quote");
@@ -109,6 +113,8 @@ static IFn ctorReader = new CtorReader();
 	dispatchMacros['!'] = new CommentReader();
 	dispatchMacros['<'] = new UnreadableReader();
 	dispatchMacros['_'] = new DiscardReader();
+	dispatchMacros['+'] = new FeatureReader();
+	dispatchMacros['-'] = new FeatureReader();
 	}
 
 static boolean isWhitespace(int ch){
@@ -1222,6 +1228,49 @@ public static class CtorReader extends AFn{
 	}
 }
 
+public static class FeatureReader extends AFn {
+
+    public static boolean hasFeature(Object expr) {        
+        if (expr instanceof Symbol) {
+            IPersistentSet available = (IPersistentSet) RT.var("clojure.core", "*features*").deref();
+            return available.contains(Keyword.intern((Symbol) expr));
+        } else if (expr instanceof IPersistentList) {
+            ISeq sequence = ((IPersistentList) expr).seq();
+            Symbol op = (Symbol) sequence.first();
+            if (op.equals(AND)) {
+                if (sequence.next() == null) return true;
+                for (Object feature: RT.seqToArray(sequence.more())) {
+                    if (!hasFeature(feature)) return false;
+                }
+                return true;
+            } else if (op.equals(NOT)) {
+                return !hasFeature(sequence.more().first());
+            } else if (op.equals(OR)) {
+                if (sequence.next() == null) return false;
+                for (Object feature: RT.seqToArray(sequence.more())) {
+                    if (hasFeature(feature)) return true;
+                }
+                return false;
+            }
+            throw Util.runtimeException("Invalid feature expression operator: " + op);
+        } else {
+            throw Util.runtimeException("Invalid feature expression: " + expr);
+        }
+    }
+
+    public Object invoke(Object reader, Object mode) {
+        PushbackReader r = (PushbackReader) reader;
+        Object test = read(r, true, null, true);
+        Object expr = read(r, true, null, true);
+        boolean wants = (((Integer) mode).intValue() == 43);
+        if (hasFeature(test) == wants) {
+            return expr;
+        } else {            
+            return r; // no-op, return the reader
+        }
+    }
+}
+
 /*
 public static void main(String[] args) throws Exception{
 	//RT.init();
@@ -1255,4 +1304,3 @@ public static void main(String[] args){
  */
 
 }
-
diff --git a/test/clojure/test_clojure/reader.clj b/test/clojure/test_clojure/reader.clj
index d6178dc..2741b26 100644
--- a/test/clojure/test_clojure/reader.clj
+++ b/test/clojure/test_clojure/reader.clj
@@ -501,3 +501,44 @@
   (is (= 4 (.version #uuid "550e8400-e29b-41d4-a716-446655440000")))
   (is (= (print-str #uuid "550e8400-e29b-41d4-a716-446655440000")
          "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))
+
+(deftest test-features
+  (testing "#+"
+    (testing "with symbol"
+      (is (= ["x"] [#+clojure "x"]))
+      (is (= [] [#+clojurescript "x"])))
+    (testing "with and"
+      (is (= ["x"] [#+(and) "x"]))
+      (is (= ["x"] [#+(and clojure) "x"]))
+      (is (= ["x"] [#+(and clojure clojure) "x"]))
+      (is (= [] [#+(and clojure clojurescript) "x"])))
+    (testing "with or"
+      (is (= [] [#+(or) "x"]))
+      (is (= ["x"] [#+(or clojure) "x"]))
+      (is (= ["x"] [#+(or clojure clojurescript) "x"]))
+      (is (= [] [#+(or clojurescript) "x"])))
+    (testing "with not"
+      (is (= ["x"] [#+(not clojurescript) "x"])))
+    (testing "with nested expressions"
+      (is (= [] [#+(not (and clojure (or clojurescript (not clojurescript)))) "x"]))
+      (is (= ["x"] [#+(and clojure (or clojurescript (not clojurescript))) "x"]))
+      (is (= ["x"] [#+(or clojurescript (or #+clojure clojure)) "x"]))))
+  (testing "#-"
+    (testing "with symbol"
+      (is (= [] [#-clojure "x"]))
+      (is (= ["x"] [#-clojurescript "x"])))
+    (testing "with not"
+      (is (= ["x"] [#-(not clojure) "x"])))
+    (testing "with and"
+      (is (= [] [#-(and) "x"]))
+      (is (= [] [#-(and clojure clojure) "x"]))
+      (is (= ["x"] [#-(and clojure clojurescript) "x"])))
+    (testing "with or"
+      (is (= ["x"] [#-(or) "x"]))
+      (is (= [] [#-(or clojure) "x"]))
+      (is (= [] [#-(or clojure clojurescript) "x"]))
+      (is (= ["x"] [#-(or clojurescript) "x"])))
+    (testing "with nested expressions"
+      (is (= ["x"] [#-(not (and clojure (or clojurescript (not clojurescript)))) "x"]))
+      (is (= [] [#-(and clojure (or clojurescript (not clojurescript))) "x"]))
+      (is (= ["x"] [#-(or clojurescript (or #-clojure clojure)) "x"])))))
-- 
1.7.11.2

