diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index a0f0625..9273b14 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -681,8 +681,7 @@ #+cljs (defn handle-params [precompiled params-maker possible-params] (let [params (fast-object-array (count params-maker))] - ;;TODO: is there a faster way to do this in cljs? - (doseq [i (range (count params-maker))] + (dotimes [i (count params-maker)] (aset params i ((get possible-params (get params-maker i))))) (bind-params* precompiled params 0) )) @@ -729,7 +728,7 @@ )) (instance? SpecialFormUse p) - (if (-> p :code first (= 'fn*)) + (if (->> p :code first (contains? #{'fn* 'fn})) (do (swap! params-atom conj (:code p)) pred* diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index 8b9e222..5723764 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -333,7 +333,9 @@ (i/fn-invocation? path) (let [[op & params] path] - (if (special-symbol? op) + ;; need special case for 'fn since macroexpand does NOT + ;; expand fn when run on cljs code, but it's also not considered a special symbol + (if (or (= 'fn op) (special-symbol? op)) `(com.rpl.specter.impl/->SpecialFormUse ~path (quote ~path)) `(com.rpl.specter.impl/->FnInvocation ~(ic-prepare-path locals-set op) @@ -351,7 +353,7 @@ (fn [e] (cond (or (set? e) (map? e) ; in case inline maps are ever extended - (and (i/fn-invocation? e) (= 'fn* (first e)))) + (and (i/fn-invocation? e) (contains? #{'fn* 'fn} (first e)))) [e] (i/fn-invocation? e) @@ -399,7 +401,6 @@ ~(mapv (fn [p] `(fn [] ~p)) possible-params) )) ] - ;; in order to pass the used locals to the clj handle-params macro `(let [info# (i/get-path-cache ~cache-id) ^com.rpl.specter.impl.CachedPathInfo info# diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index e97d5ce..87efc43 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -3,6 +3,7 @@ [cljs.test :refer [is deftest]] [cljs.test.check.cljs-test :refer [defspec]] [com.rpl.specter.cljs-test-helpers :refer [for-all+]] + [com.rpl.specter.test-helpers :refer [ic-test]] [com.rpl.specter.macros :refer [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! @@ -10,7 +11,7 @@ (:use #+clj [clojure.test :only [deftest is]] #+clj [clojure.test.check.clojure-test :only [defspec]] - #+clj [com.rpl.specter.test-helpers :only [for-all+]] + #+clj [com.rpl.specter.test-helpers :only [for-all+ ic-test]] #+clj [com.rpl.specter.macros :only [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! @@ -858,3 +859,67 @@ (= q1 q2) (= (type q1) (type q2)))))) +(def ^:dynamic *APATH* s/keypath) + +(deftest inline-caching-test + (ic-test + true + [k] + [s/ALL (s/must k)] + inc + [{:a 1} {:b 2 :c 3} {:a 7 :d -1}] + [[:a] [:b] [:c] [:d] [:e]]) + (ic-test + true + [] + [s/ALL #{4 5 11} #(> % 2) (fn [e] (< e 7))] + inc + (range 20) + []) + (ic-test + false + [v] + (if v :a :b) + inc + {:a 1 :b 2} + [[true] [false]]) + (ic-test + false + [k] + (*APATH* k) + str + {:a 1 :b 2} + [[:a] [:b] [:c]] + ) + (binding [*APATH* s/must] + (ic-test + false + [k] + (*APATH* k) + inc + {:a 1 :b 2} + [[:a] [:b] [:c]] + )) + (ic-test + true + [k k2] + [s/ALL (s/selected? (s/must k) #(> % 2)) (s/must k2)] + dec + [{:a 1 :b 2} {:a 10 :b 6} {:c 7 :b 8} {:c 1 :d 9} {:c 3 :d -1}] + [[:a :b] [:b :a] [:c :d] [:b :c]] + ) + ;; TODO: test exception thrown when can't cache and must-cache-paths! + ) + +;;TODO: +;; make a macro to help with testing inline caching +;; - put into test-helpers +;; - do must-cache-paths! +;; - take in params vec, path expression, data, transform fn, and sequence of param vecs +;; - verify that functional version gets same result as inline +;; caching version run on the data multiple times (make sure same callsite) +;; - have another test that turns must-cache-paths on and verifies certain paths +;; don't cache (but still run with must-cache-paths off) + + + diff --git a/test/com/rpl/specter/test_helpers.clj b/test/com/rpl/specter/test_helpers.clj index e256026..6d7a344 100644 --- a/test/com/rpl/specter/test_helpers.clj +++ b/test/com/rpl/specter/test_helpers.clj @@ -1,10 +1,15 @@ (ns com.rpl.specter.test-helpers (:require [clojure.test.check [generators :as gen] - [properties :as prop]])) + [properties :as prop]] + [clojure.test] + [cljs.test]) + (:use [com.rpl.specter.macros :only [select transform]] + [com.rpl.specter :only [select* transform* must-cache-paths!]])) ;; it seems like gen/bind and gen/return are a monad (hence the names) +;; this is only for clj (cljs version in different file) (defmacro for-all+ [bindings & body] (let [parts (partition 2 bindings) vars (vec (map first parts)) @@ -15,3 +20,22 @@ (reverse parts))] `(prop/for-all [~vars ~genned] ~@body ))) + + +(defmacro ic-test [must-cache? params-decl apath transform-fn data params] + (let [platform (if (contains? &env :locals) :cljs :clj) + is-sym (if (= platform :clj) 'clojure.test/is 'cljs.test/is)] + `(let [icfnsel# (fn [~@params-decl] (select ~apath ~data)) + icfntran# (fn [~@params-decl] (transform ~apath ~transform-fn ~data)) + regfnsel# (fn [~@params-decl] (select* ~apath ~data)) + regfntran# (fn [~@params-decl] (transform* ~apath ~transform-fn ~data)) + params# (if (empty? ~params) [[]] ~params) + ] + (must-cache-paths! ~must-cache?) + (dotimes [_# 3] + (doseq [ps# params#] + (~is-sym (= (apply icfnsel# ps#) (apply regfnsel# ps#))) + (~is-sym (= (apply icfntran# ps#) (apply regfntran# ps#))) + )) + (must-cache-paths! false) + )))