diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index d5458bf..dbda859 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -2,13 +2,13 @@ #?(:cljs (:require-macros [com.rpl.specter.macros :refer - [fixed-pathed-collector - fixed-pathed-nav - defcollector + [late-bound-richnav + late-bound-nav + late-bound-collector defnav defpathedfn richnav - defnavconstructor]] + defrichnav]] [com.rpl.specter.util-macros :refer [doseqres]])) @@ -21,7 +21,7 @@ defnav defpathedfn richnav - defnavconstructor]]) + defrichnav]]) #?(:clj [com.rpl.specter.util-macros :only [doseqres]])) (:require [com.rpl.specter.impl :as i] @@ -38,18 +38,6 @@ [& apath] (i/comp-paths* (vec apath))) -(def ^{:doc "Mandate that operations that do inline path factoring and compilation - (select/transform/setval/replace-in/path/etc.) must succeed in - factoring the path into static and dynamic portions. If not, an - error will be thrown and the reasons for not being able to factor - will be printed. Defaults to false, and `(must-cache-paths! false)` - can be used to turn this feature off. - - Reasons why it may not be able to factor a path include using - a local symbol, special form, or regular function invocation - where a navigator is expected."} - must-cache-paths! i/must-cache-paths!) - ;; Selection functions (def ^{:doc "Version of select that takes in a path precompiled with comp-paths"} @@ -162,24 +150,6 @@ [path transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}] (compiled-replace-in (i/comp-paths* path) transform-fn structure :merge-fn merge-fn)) -;; Helpers for defining selectors and collectors with late-bound params - -(def ^{:doc "Takes a compiled path that needs late-bound params and supplies it with - an array of params and a position in the array from which to begin reading - params. The return value is an executable selector."} - bind-params* i/bind-params*) - -(defn params-reset [params-path] - ;; TODO: error if not paramsneededpath - (let [nav (i/params-needed-nav params-path) - needed (i/num-needed-params params-path)] - (richnav 0 - (select* [this params params-idx vals structure next-fn] - (i/exec-rich-select* nav params (- params-idx needed) vals structure next-fn)) - (transform* [this params params-idx vals structure next-fn] - (i/exec-rich-transform* nav params (- params-idx needed) vals structure next-fn))))) - - ;; Built-in pathing and context operations (defnav @@ -209,19 +179,18 @@ function works just like it does in `transform`, with collected values given as the first arguments"} terminal - (richnav 1 - (select* [this params params-idx vals structure next-fn] + (richnav [afn] + (select* [this vals structure next-fn] (i/throw-illegal "'terminal' should only be used in multi-transform")) (transform* [this params params-idx vals structure next-fn] - (n/terminal* params params-idx vals structure)))) + (n/terminal* afn vals structure)))) -(defnavconstructor terminal-val +(defn terminal-val "Like `terminal` but specifies a val to set at the location regardless of the collected values or the value at the location." - [p terminal] [v] - (p (i/fast-constantly v))) + (terminal (i/fast-constantly v))) (defnav ^{:doc "Navigate to every element of the collection. For maps navigates to @@ -241,7 +210,6 @@ (select* [this structure next-fn] (doseqres NONE [v (vals structure)] (next-fn v))) - (transform* [this structure next-fn] (n/map-vals-transform structure next-fn))) @@ -292,7 +260,6 @@ (select* [this structure next-fn] (doseqres NONE [[s e] (n/matching-ranges structure pred)] (n/srange-select structure s e next-fn))) - (transform* [this structure next-fn] (reduce (fn [structure [s e]] @@ -381,7 +348,7 @@ children in the same order when executed on \"select\" and then \"transform\"." [& path] - (fixed-pathed-nav [late path] + (late-bound-nav [late path] (select* [this structure next-fn] (next-fn (compiled-select late structure))) (transform* [this structure next-fn] @@ -454,15 +421,66 @@ (defpathedfn selected? "Filters the current value based on whether a path finds anything. e.g. (selected? :vals ALL even?) keeps the current element only if an - even number exists for the :vals key. - - The input path may be parameterized, in which case the result of selected? - will be parameterized in the order of which the parameterized navigators - were declared." + even number exists for the :vals key." [& path] + ;;TODO: how to handle this if the path is being auto-compiled by this point? + ;; same for if-path... + ;; make selected? and if-path macros? + ;; expand to: (if-let [(afn (extract-basic-filter-fn path)) (pred afn) (let [p ...] (nav [] ...))] + ;; there needs to be a "compile-time" component here.... which is a macro + ;; but it's not "compile-time", it's the first run-through by specter + ;; maybe if pathed-fn returns a function when run in first run-through, then it's given + ;; a compiled/parameterized version of the path + ;; pathedfn basically substitutes for either a navigator or a parameterized navigator (parameterized + ;; with a compiled path) + ;; TODO: no, the function vs. nav thing doesn't work because a path may or may not + ;; be needed by the resulting navigator... (and there could be multiple path arguments, some + ;; of which may not be needed) + ;; maybe there could be a special marker for inline caching to invoke different codepaths + ;; if a path is a series of functions or not... + ;; but would much rather have it be internalizable in the operation + ;; maybe still have fixed-pathed-nav and inline caching turns path into something that + ;; has delayed evaluation + ;; what about non-path params? they should be dynamic every single time... + ;; maybe defnav indicates what's a path and what's not... + ;; - no, still need intermediate logic to determine what the nav will be... + ;; it needs to happen outside the function... + ;; or "transformed" needs to work differently... and return a function that takes in the param + ;; could tag with metadata about how to statically analyze that argument + ;; and then it takes in the actual paths as input + ;; - this would compose with other pathedfns, like how filterer works + ;; - but static analysis needs to ALSO switch the implementation depending on what it finds + ;; - also needs to work if just call it like a regular function... + ;; - maybe fixed-pathed-nav recognizes metadata on the path to see if it's inside inline + ;; caching or not... and then decides whether to compile or not + ;; - maybe I need a dynamic nav that looks at uncompiled paths and returns function to invoke + ;; with the same arguments... uncompiledpath has parts of it escaped + ;; - if path and all arguments are static then it will be invoked and cached normally... + ;; - no, but still doesn't handle the switching to pred case and making a custom navigator + ;; - unless rely on the fact it will be invoked statically when everything is constant... + ;; - but filterer still doesn't seem to work so well... + ;; - the dynamic nav is told when a ^:notpath argument is dynamic or not + ;; - how does this compose for filterer? + ;; - not quite right... since extraction only happens at "compile time" and then the + ;; pred navigator used like it's static + ;; - maybe instead of "fixed-pathed-nav" have "late-nav" that can also take non-path args that were + ;; marked as ^:notpath + ;; - having fixed-pathed-navs doesn't work because of non-path arguments + ;; - returning functions doesn't work because may want to call down to other higher-order navs... + ;; - maybe DO have a paramsneeded type that's a function with the uncompiled paths + dynamic local + ;; information on it (which is what fixed-pathed-nav can do...) + ;; - or can just say "with-args" and then metadata tells specter which are paths and which aren't + ;; - (late-bound-nav [late path c an-arg]) + ;; - if they aren't bound yet, then that returns a function... otherwise it returns a proper navigator + ;; - how does this compose with filterer? + ;; (subselect ALL (selected? path)) + ;; - selected? returns a function that takes in params (and is annotated with WHAT params)... + ;; - subselect does the same late bound stuff with its path and it sees what IT is composed of + ;; - at end have an AST indicating what the final top-level paths / sub-paths are + (if-let [afn (n/extract-basic-filter-fn path)] afn - (fixed-pathed-nav [late path] + (late-bound-nav [late path] (select* [this structure next-fn] (i/filter-select #(n/selected?* late %) @@ -477,7 +495,7 @@ (defpathedfn not-selected? [& path] (if-let [afn (n/extract-basic-filter-fn path)] (fn [s] (not (afn s))) - (fixed-pathed-nav [late path] + (late-bound-nav [late path] (select* [this structure next-fn] (i/filter-select #(n/not-selected?* late %) @@ -508,22 +526,17 @@ will be parameterized in the order of which the parameterized navigators were declared." [path ^:notpath update-fn] - (fixed-pathed-nav [late path] + (late-bound-nav [late path] (select* [this structure next-fn] (next-fn (compiled-transform late update-fn structure))) (transform* [this structure next-fn] (next-fn (compiled-transform late update-fn structure))))) -(defnav +(def ^{:doc "Keeps the element only if it matches the supplied predicate. This is the late-bound parameterized version of using a function directly in a path."} pred - [afn] - (select* [this structure next-fn] - (if (afn structure) (next-fn structure) NONE)) - (transform* [this structure next-fn] - (if (afn structure) (next-fn structure) structure))) - + i/pred*) (extend-type nil ImplicitNav @@ -533,7 +546,6 @@ ImplicitNav (implicit-nav [this] (keypath this))) - (extend-type #?(:clj clojure.lang.AFn :cljs function) ImplicitNav (implicit-nav [this] (pred this))) @@ -584,7 +596,7 @@ current value to the collected vals."} collect [& path] - (fixed-pathed-collector [late path] + (late-bound-collector [late path] (collect-val [this structure] (compiled-select late structure)))) @@ -594,7 +606,7 @@ current value to the collected vals."} collect-one [& path] - (fixed-pathed-collector [late path] + (late-bound-collector [late path] (collect-val [this structure] (compiled-select-one late structure)))) @@ -612,76 +624,58 @@ (collect-val [this structure] val)) -(def +(defrichnav ^{:doc "Drops all collected values for subsequent navigation."} - DISPENSE n/DISPENSE*) - + DISPENSE + [] + (select* [this vals structure next-fn] + (next-fn [] structure)) + (transform* [this vals structure next-fn] + (next-fn [] structure))) (defpathedfn if-path "Like cond-path, but with if semantics." ([cond-p then-path] (if-path cond-p then-path STOP)) ([cond-p then-path else-path] - (let [then-comp (i/comp-paths-internalized then-path) - else-comp (i/comp-paths-internalized else-path) - then-needed (i/num-needed-params then-comp) - else-needed (i/num-needed-params else-comp) - then-nav (i/extract-rich-nav then-comp) - else-nav (i/extract-rich-nav else-comp)] - (if-let [afn (n/extract-basic-filter-fn cond-p)] - (richnav (+ then-needed else-needed) - (select* [this params params-idx vals structure next-fn] - (n/if-select - params - params-idx - vals - structure - next-fn - afn - then-nav - then-needed - else-nav)) - - (transform* [this params params-idx vals structure next-fn] - (n/if-transform - params - params-idx - vals - structure - next-fn - afn - then-nav - then-needed - else-nav)))) - - (let [cond-comp (i/comp-paths-internalized cond-p) - cond-needed (i/num-needed-params cond-comp)] - (richnav (+ then-needed else-needed cond-needed) - (select* [this params params-idx vals structure next-fn] - (let [late-cond (i/parameterize-path cond-comp params params-idx)] - (n/if-select - params - (+ params-idx cond-needed) - vals - structure - next-fn - #(n/selected?* late-cond %) - then-nav - then-needed - else-nav))) - - (transform* [this params params-idx vals structure next-fn] - (let [late-cond (i/parameterize-path cond-comp params params-idx)] - (n/if-transform - params - (+ params-idx cond-needed) - vals - structure - next-fn - #(n/selected?* late-cond %) - then-nav - then-needed - else-nav)))))))) + (if-let [afn (n/extract-basic-filter-fn cond-p)] + (late-bound-nav [late-then then-path + late-else else-path] + (select* [this vals structure next-fn] + (n/if-select + vals + structure + next-fn + afn + late-then + late-else)) + (transform* [this vals structure next-fn] + (n/if-transform + vals + structure + next-fn + afn + late-then + late-else))) + (late-bound-nav [late-cond cond-p + late-then then-path + late-else else-path] + (select* [this vals structure next-fn] + (n/if-select + vals + structure + next-fn + #(n/selected?* late-cond %) + late-then + late-else)) + (transform* [this vals structure next-fn] + (n/if-transform + vals + structure + next-fn + #(n/selected?* late-cond %) + late-then + late-else)))))) (defpathedfn cond-path @@ -707,26 +701,19 @@ "A path that branches on multiple paths. For updates, applies updates to the paths in order." ([] STAY) - ([path] (i/comp-paths* path)) + ([path] path) ([path1 path2] - (let [comp1 (i/comp-paths-internalized path1) - comp2 (i/comp-paths-internalized path2) - comp1-needed (i/num-needed-params comp1) - nav1 (i/extract-rich-nav comp1) - nav2 (i/extract-rich-nav comp2)] - - (richnav (+ comp1-needed (i/num-needed-params comp2)) - (select* [this params params-idx vals structure next-fn] - (let [res1 (i/exec-rich-select* nav1 params params-idx vals structure next-fn) - res2 (i/exec-rich-select* nav2 params (+ params-idx comp1-needed) vals structure next-fn)] - (if (identical? NONE res2) - res1 - res2))) - - (transform* [this params params-idx vals structure next-fn] - (let [s1 (i/exec-rich-transform* nav1 params params-idx vals structure next-fn)] - (i/exec-rich-transform* nav2 params (+ params-idx comp1-needed) vals s1 next-fn)))))) - + (late-bound-nav [late1 path1 + late2 path2] + (select* [this vals structure next-fn] + (let [res1 (i/exec-select* nav1 vals structure next-fn) + res2 (i/exec-select* nav2 vals structure next-fn)] + (if (identical? NONE res2) + res1 + res2))) + (transform* [this vals structure next-fn] + (let [s1 (i/exec-transform* nav1 vals structure next-fn)] + (i/exec-transform* nav2 vals s1 next-fn))))) ([path1 path2 & paths] (reduce multi-path (multi-path path1 path2) paths))) diff --git a/src/clj/com/rpl/specter/defhelpers.clj b/src/clj/com/rpl/specter/defhelpers.clj deleted file mode 100644 index 173692e..0000000 --- a/src/clj/com/rpl/specter/defhelpers.clj +++ /dev/null @@ -1,19 +0,0 @@ -(ns com.rpl.specter.defhelpers) - -(defn gensyms [amt] - (vec (repeatedly amt gensym))) - -(defmacro define-ParamsNeededPath [clj? fn-type invoke-name var-arity-impl] - (let [a (with-meta (gensym "array") {:tag 'objects}) - impls (for [i (range 21) - :let [args (vec (gensyms i)) - setters (for [j (range i)] `(aset ~a ~j ~(get args j)))]] - `(~invoke-name [this# ~@args] - (let [~a (~(if clj? 'com.rpl.specter.impl/fast-object-array 'object-array) ~i)] - ~@setters - (com.rpl.specter.impl/bind-params* this# ~a 0))))] - - `(defrecord ~'ParamsNeededPath [~'rich-nav ~'num-needed-params] - ~fn-type - ~@impls - ~var-arity-impl))) diff --git a/src/clj/com/rpl/specter/defnavhelpers.cljc b/src/clj/com/rpl/specter/defnavhelpers.cljc deleted file mode 100644 index 5b16560..0000000 --- a/src/clj/com/rpl/specter/defnavhelpers.cljc +++ /dev/null @@ -1,13 +0,0 @@ -(ns com.rpl.specter.defnavhelpers - (:require [com.rpl.specter.impl :as i])) - -(defn param-delta [i] - (fn [^objects params params-idx] - (aget params (+ params-idx i)))) - - -(defn bound-params [path start-delta] - (fn [^objects params params-idx] - (if (i/params-needed-path? path) - (i/bind-params* path params (+ params-idx start-delta)) - path))) diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 3c99f55..d3292d4 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -1,15 +1,13 @@ (ns com.rpl.specter.impl #?(:cljs (:require-macros - [com.rpl.specter.defhelpers :refer [define-ParamsNeededPath]] [com.rpl.specter.util-macros :refer [doseqres]])) (:use [com.rpl.specter.protocols :only - [select* transform* collect-val Navigator]] + [select* transform* collect-val Rich Navigator]] #?(:clj [com.rpl.specter.util-macros :only [doseqres]])) (:require [com.rpl.specter.protocols :as p] [clojure.string :as s] - #?(:clj [com.rpl.specter.defhelpers :as dh]) #?(:clj [riddley.walk :as riddley])) #?(:clj (:import [com.rpl.specter Util MutableCell]))) @@ -30,9 +28,6 @@ (defn smart-str [& elems] (apply str (map smart-str* elems))) -(defn object-aget [^objects a i] - (aget a i)) - (defn fast-constantly [v] (fn ([] v) ([a1] v) @@ -101,39 +96,6 @@ (dotimes [_ iters] (afn)))) -(deftype ExecutorFunctions [traverse-executor transform-executor]) - -(deftype ParameterizedRichNav [rich-nav params params-idx]) - -(defprotocol RichNavigator - (rich-select* [this params params-idx vals structure next-fn]) - (rich-transform* [this params params-idx vals structure next-fn])) - - -#?( - :clj - (defmacro exec-rich-select* [this & args] - (let [hinted (with-meta this {:tag 'com.rpl.specter.impl.RichNavigator})] - `(.rich-select* ~hinted ~@args))) - - - :cljs - (defn exec-rich-select* [this params params-idx vals structure next-fn] - (rich-select* ^not-native this params params-idx vals structure next-fn))) - - -#?( - :clj - (defmacro exec-rich-transform* [this & args] - (let [hinted (with-meta this {:tag 'com.rpl.specter.impl.RichNavigator})] - `(.rich-transform* ~hinted ~@args))) - - - :cljs - (defn exec-rich-transform* [this params params-idx vals structure next-fn] - (rich-transform* ^not-native this params params-idx vals structure next-fn))) - - #?( :clj (defmacro exec-select* [this & args] @@ -142,8 +104,8 @@ :cljs - (defn exec-select* [this structure next-fn] - (p/select* ^not-native this structure next-fn))) + (defn exec-select* [this vals structure next-fn] + (p/select* ^not-native this vals structure next-fn))) #?( @@ -154,123 +116,17 @@ :cljs - (defn exec-transform* [this structure next-fn] - (p/transform* ^not-native this structure next-fn))) - - -(def RichPathExecutor - (->ExecutorFunctions - (fn [^ParameterizedRichNav richnavp result-fn structure] - (exec-rich-select* (.-rich-nav richnavp) - (.-params richnavp) (.-params-idx richnavp) - [] structure - (fn [_ _ vals structure] - (result-fn - (if (identical? vals []) - structure - (conj vals structure)))))) - (fn [^ParameterizedRichNav richnavp transform-fn structure] - (exec-rich-transform* (.-rich-nav richnavp) - (.-params richnavp) (.-params-idx richnavp) - [] structure - (fn [_ _ vals structure] - (if (identical? [] vals) - (transform-fn structure) - (apply transform-fn (conj vals structure)))))))) - - -(def LeanPathExecutor - (->ExecutorFunctions - (fn [nav result-fn structure] - (exec-select* nav structure result-fn)) - (fn [nav transform-fn structure] - (exec-transform* nav structure transform-fn)))) - - -(defrecord CompiledPath [executors nav]) - -(defn compiled-path? [o] - (instance? CompiledPath o)) - -(defn no-params-rich-compiled-path [rich-nav] - (->CompiledPath - RichPathExecutor - (->ParameterizedRichNav - rich-nav - nil - 0))) - - -(defn lean-compiled-path [nav] - (->CompiledPath LeanPathExecutor nav)) - - -(declare bind-params*) - -#?( - :clj - (defmacro fast-object-array [i] - `(com.rpl.specter.Util/makeObjectArray ~i)) - - :cljs - (defn fast-object-array [i] - (object-array i))) - - -#?( - :clj - (dh/define-ParamsNeededPath - true - clojure.lang.IFn - invoke - (applyTo [this args] - (let [a (object-array args)] - (com.rpl.specter.impl/bind-params* this a 0)))) - - :cljs - (define-ParamsNeededPath - false - cljs.core/IFn - -invoke - (-invoke [this p01 p02 p03 p04 p05 p06 p07 p08 p09 p10 - p11 p12 p13 p14 p15 p16 p17 p18 p19 p20 - rest] - (let [a (object-array - (concat - [p01 p02 p03 p04 p05 p06 p07 p08 p09 p10 - p11 p12 p13 p14 p15 p16 p17 p18 p19 p20] - rest))] - (com.rpl.specter.impl/bind-params* this a 0))))) - - - -(defn params-needed-path? [o] - (instance? ParamsNeededPath o)) - -(defn extract-nav [p] - (if (params-needed-path? p) - (.-rich-nav ^ParamsNeededPath p) - (let [n (.-nav ^CompiledPath p)] - (if (instance? ParameterizedRichNav n) - (.-rich-nav ^ParameterizedRichNav n) - n)))) - - - -(defn bind-params* [^ParamsNeededPath params-needed-path params idx] - (->CompiledPath - RichPathExecutor - (->ParameterizedRichNav - (.-rich-nav params-needed-path) - params - idx))) - + (defn exec-transform* [this vals structure next-fn] + (p/transform* ^not-native this vals structure next-fn))) (defprotocol PathComposer (do-comp-paths [paths])) +(defn rich-nav? [n] + (instance? com.rpl.specter.protocols.RichNavigator n)) + (defn comp-paths* [p] - (if (compiled-path? p) p (do-comp-paths p))) + (if (rich-nav? p) p (do-comp-paths p))) (defn- seq-contains? [aseq val] @@ -279,12 +135,8 @@ empty? not)) -(defn root-params-nav? [o] - (and (fn? o) (-> o meta :highernav))) - (defn- coerce-object [this] - (cond (root-params-nav? this) (-> this meta :highernav :params-needed-path) - (satisfies? p/ImplicitNav this) (p/implicit-nav this) + (cond (satisfies? p/ImplicitNav this) (p/implicit-nav this) :else (throw-illegal "Not a navigator: " this))) @@ -296,14 +148,6 @@ (coerce-path [this] (coerce-object this)) - CompiledPath - (coerce-path [this] - this) - - ParamsNeededPath - (coerce-path [this] - this) - #?(:clj java.util.List :cljs cljs.core/PersistentVector) (coerce-path [this] (do-comp-paths this)) @@ -326,84 +170,16 @@ (coerce-object this))) -(defn- combine-same-types [[n & _ :as all]] - (let [combiner - (if (satisfies? RichNavigator n) - (fn [curr next] - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (exec-rich-select* curr params params-idx vals structure - (fn [params-next params-idx-next vals-next structure-next] - (exec-rich-select* next params-next params-idx-next - vals-next structure-next next-fn)))) - - (rich-transform* [this params params-idx vals structure next-fn] - (exec-rich-transform* curr params params-idx vals structure - (fn [params-next params-idx-next vals-next structure-next] - (exec-rich-transform* next params-next params-idx-next - vals-next structure-next next-fn)))))) - - (fn [curr next] - (reify Navigator - (select* [this structure next-fn] - (exec-select* curr structure - (fn [structure-next] - (exec-select* next structure-next next-fn)))) - (transform* [this structure next-fn] - (exec-transform* curr structure - (fn [structure-next] - (exec-transform* next structure-next next-fn)))))))] - (reduce combiner all))) - -(defn coerce-rich-navigator [nav] - (if (satisfies? RichNavigator nav) - nav - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (exec-select* nav structure (fn [structure] (next-fn params params-idx vals structure)))) - - (rich-transform* [this params params-idx vals structure next-fn] - (exec-transform* nav structure (fn [structure] (next-fn params params-idx vals structure))))))) - - -(defn extract-rich-nav [p] - (coerce-rich-navigator (extract-nav p))) - -(defn capture-params-internally [path] - (cond - (not (instance? CompiledPath path)) - path - - (satisfies? Navigator (:nav path)) - path - - :else - (let [^ParameterizedRichNav prich-nav (:nav path) - rich-nav (.-rich-nav prich-nav) - params (.-params prich-nav) - params-idx (.-params-idx prich-nav)] - (if (empty? params) - path - (no-params-rich-compiled-path - (reify RichNavigator - (rich-select* [this params2 params-idx2 vals structure next-fn] - (exec-rich-select* rich-nav params params-idx vals structure - (fn [_ _ vals-next structure-next] - (next-fn params2 params-idx2 vals-next structure-next)))) - - (rich-transform* [this params2 params-idx2 vals structure next-fn] - (exec-rich-transform* rich-nav params params-idx vals structure - (fn [_ _ vals-next structure-next] - (next-fn params2 params-idx2 vals-next structure-next)))))))))) - - -(defn comp-paths-internalized [path] - (capture-params-internally (comp-paths* path))) - -(defn nav-type [n] - (if (satisfies? RichNavigator n) - :rich - :lean)) +(defn combine-two-navs [nav1 nav2] + (reify RichNavigator + (select* [this vals structure next-fn] + (exec-select* curr vals structure + (fn [vals-next structure-next] + (exec-select* next vals-next structure-next next-fn)))) + (transform* [this vals structure next-fn] + (exec-transform* curr vals structure + (fn [vals-next structure-next] + (exec-rich-transform* next vals-next structure-next next-fn)))))) (extend-protocol PathComposer nil @@ -414,40 +190,7 @@ (coerce-path o)) #?(:clj java.util.List :cljs cljs.core/PersistentVector) (do-comp-paths [navigators] - (if (empty? navigators) - (coerce-path nil) - (let [coerced (->> navigators - (map coerce-path) - (map capture-params-internally)) - combined (->> coerced - (map extract-nav) - (partition-by nav-type) - (map combine-same-types)) - - result-nav (if (= 1 (count combined)) - (first combined) - (->> combined - (map coerce-rich-navigator) - combine-same-types)) - - needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)] - (if (empty? needs-params-paths) - (if (satisfies? Navigator result-nav) - (lean-compiled-path result-nav) - (no-params-rich-compiled-path result-nav)) - (->ParamsNeededPath - (coerce-rich-navigator result-nav) - (->> needs-params-paths - (map :num-needed-params) - (reduce +)))))))) - - - - -(defn num-needed-params [path] - (if (instance? CompiledPath path) - 0 - (.-num-needed-params ^ParamsNeededPath path))) + (reduce combine-two-navs navigators))) ;; cell implementation idea taken from prismatic schema library @@ -502,41 +245,11 @@ (set-cell! cell ret) ret)) +;; TODO: this used to be a macro for clj... check if that's still important +(defn compiled-traverse* [path result-fn structure] + (exec-select* path [] structure result-fn)) -(defn compiled-nav-field [^CompiledPath p] - (.-nav p)) - -(defn compiled-executors-field [^CompiledPath p] - (.-executors p)) - - -(defn traverse-executor-field [^ExecutorFunctions ex] - (.-traverse-executor ex)) - - -;; amazingly doing this as a macro shows a big effect in the -;; benchmark for getting a value out of a nested map -#?( - :clj - (defmacro compiled-traverse* [path result-fn structure] - `(let [nav# (compiled-nav-field ~path) - ex# (compiled-executors-field ~path)] - ((traverse-executor-field ex#) - nav# - ~result-fn - ~structure))) - - - :cljs - (defn compiled-traverse* [path result-fn structure] - (let [nav (compiled-nav-field path) - ex (compiled-executors-field path)] - ((traverse-executor-field ex) - nav - result-fn - structure)))) - (defn do-compiled-traverse [apath structure] @@ -552,9 +265,7 @@ (fn [elem] (let [curr (get-cell cell)] (set-cell! cell (afn curr elem)))) - structure) - (get-cell cell))))) @@ -563,7 +274,6 @@ result-fn (fn [structure] (let [curr (get-cell res)] (set-cell! res (conj! curr structure))))] - (compiled-traverse* path result-fn structure) (persistent! (get-cell res)))) @@ -590,7 +300,6 @@ (if (identical? curr NONE) (set-cell! res structure) (throw-illegal "More than one element found in structure: " structure))))] - (compiled-traverse* path result-fn structure) (let [ret (get-cell res)] (if (identical? NONE ret) @@ -617,34 +326,15 @@ (defn compiled-selected-any?* [path structure] (not= NONE (compiled-select-any* path structure))) -(defn compiled-transform* - [^com.rpl.specter.impl.CompiledPath path transform-fn structure] - (let [nav (.-nav path) - ^com.rpl.specter.impl.ExecutorFunctions ex (.-executors path)] - ((.-transform-executor ex) nav transform-fn structure))) - - - -(defn params-needed-nav - ^com.rpl.specter.impl.RichNavigator - [^com.rpl.specter.impl.ParamsNeededPath path] - (.-rich-nav path)) - -(defn compiled-path-rich-nav - ^com.rpl.specter.impl.RichNavigator - [^com.rpl.specter.impl.CompiledPath path] - (let [^com.rpl.specter.impl.ParameterizedRichNav pr (.-nav path)] - (.-rich-nav pr))) - - -(defn coerce-compiled->rich-nav [path] - (if (instance? ParamsNeededPath path) - path - (let [nav (.-nav ^CompiledPath path)] - (if (satisfies? Navigator nav) - (no-params-rich-compiled-path (coerce-rich-navigator nav)) - path)))) +;;TODO: could inline cache the transform-fn, or even have a different one +;;if know there are no vals at the end +(defn compiled-transform* [path transform-fn structure] + (exec-transform* nav [] structure + (fn [vals structure] + (if (identical? vals []) + (transform-fn vals) + (apply transform-fn (conj vals structure)))))) (defn fn-invocation? [f] (or #?(:clj (instance? clojure.lang.Cons f)) @@ -652,38 +342,6 @@ #?(:cljs (instance? cljs.core.LazySeq f)) (list? f))) -(defrecord LayeredNav [underlying]) - -(defn layered-nav? [o] (instance? LayeredNav o)) - -(defn layered-nav-underlying [^LayeredNav ln] - (.-underlying ln)) - -(defn verify-layerable! [anav] - (if-not - (or (root-params-nav? anav) - (and (instance? ParamsNeededPath anav) - (> (:num-needed-params anav) 0))) - (throw-illegal "defnavconstructor must be used on a navigator defined with - defnav with at least one parameter"))) - - -(defn layered-wrapper [anav] - (verify-layerable! anav) - (fn ([a1] (->LayeredNav (anav a1))) - ([a1 a2] (->LayeredNav (anav a1 a2))) - ([a1 a2 a3] (->LayeredNav (anav a1 a2 a3))) - ([a1 a2 a3 a4] (->LayeredNav (anav a1 a2 a3 a4))) - ([a1 a2 a3 a4 a5] (->LayeredNav (anav a1 a2 a3 a4 a5))) - ([a1 a2 a3 a4 a5 a6] (->LayeredNav (anav a1 a2 a3 a4 a5 a6))) - ([a1 a2 a3 a4 a5 a6 a7] (->LayeredNav (anav a1 a2 a3 a4 a5 a6 a7))) - ([a1 a2 a3 a4 a5 a6 a7 a8] (->LayeredNav (anav a1 a2 a3 a4 a5 a6 a7 a8))) - ([a1 a2 a3 a4 a5 a6 a7 a8 a9] (->LayeredNav (anav a1 a2 a3 a4 a5 a6 a7 a8 a9))) - ([a1 a2 a3 a4 a5 a6 a7 a8 a9 a10] (->LayeredNav (anav a1 a2 a3 a4 a5 a6 a7 a8 a9 a10))) - ([a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 & args] - (->LayeredNav (apply anav a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 args))))) - - (defrecord LocalSym [val sym]) @@ -698,16 +356,9 @@ [op params code]) (defrecord CachedPathInfo - [precompiled ; can be null - params-maker]) ; can be null + [path-fn]) -(def MUST-CACHE-PATHS (mutable-cell false)) - -(defn must-cache-paths! - ([] (must-cache-paths! true)) - ([v] (set-cell! MUST-CACHE-PATHS v))) - (defn constant-node? [node] (cond (and (instance? VarUse node) (-> node :var meta :dynamic not)) true @@ -743,14 +394,7 @@ (defn- valid-navigator? [v] (or (satisfies? p/ImplicitNav v) - (instance? CompiledPath v))) - -#?(:cljs - (defn handle-params [precompiled params-maker possible-params] - (let [params (fast-object-array (count params-maker))] - (dotimes [i (count params-maker)] - (aset params i ((get possible-params (get params-maker i))))) - (bind-params* precompiled params 0)))) + (rich-nav? v))) (defn filter-select [afn structure next-fn] @@ -763,97 +407,28 @@ (next-fn structure) structure)) -(def pred* - (->ParamsNeededPath - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (let [afn (aget ^objects params params-idx)] - (if (afn structure) - (next-fn params (inc params-idx) vals structure) - NONE))) - - (rich-transform* [this params params-idx vals structure next-fn] - (let [afn (aget ^objects params params-idx)] - (if (afn structure) - (next-fn params (inc params-idx) vals structure) - structure)))) - - 1)) +(defn pred* [afn] + (reify RichNavigator + (select* [this vals structure next-fn] + (if (afn structure) + (next-fn vals structure) + NONE)) + (transform* [this vals structure next-fn] + (if (afn structure) + (next-fn vals structure) + structure)))) -(def collected?* - (->ParamsNeededPath - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (let [afn (aget ^objects params params-idx)] - (if (afn vals) - (next-fn params (inc params-idx) vals structure) - NONE))) - - (rich-transform* [this params params-idx vals structure next-fn] - (let [afn (aget ^objects params params-idx)] - (if (afn vals) - (next-fn params (inc params-idx) vals structure) - structure)))) - - 1)) - - -(def rich-compiled-path-proxy - (->ParamsNeededPath - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (let [apath ^CompiledPath (aget ^objects params params-idx) - pnav ^ParameterizedRichNav (.-nav apath) - nav (.-rich-nav pnav)] - (exec-rich-select* - nav - (.-params pnav) - (.-params-idx pnav) - vals - structure - (fn [_ _ vals-next structure-next] - (next-fn params params-idx vals-next structure-next))))) - - (rich-transform* [this params params-idx vals structure next-fn] - (let [apath ^CompiledPath (aget ^objects params params-idx) - pnav ^ParameterizedRichNav (.-nav apath) - nav (.-rich-nav pnav)] - (exec-rich-transform* - nav - (.-params pnav) - (.-params-idx pnav) - vals - structure - (fn [_ _ vals-next structure-next] - (next-fn params params-idx vals-next structure-next)))))) - - 1)) - - -(def lean-compiled-path-proxy - (->ParamsNeededPath - (reify RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (let [^CompiledPath apath (aget ^objects params params-idx) - ^Navigator nav (.-nav apath)] - (exec-select* - nav - structure - (fn [structure-next] - (next-fn params params-idx vals structure-next))))) - - (rich-transform* [this params params-idx vals structure next-fn] - (let [^CompiledPath apath (aget ^objects params params-idx) - ^Navigator nav (.-nav apath)] - (exec-transform* - nav - structure - (fn [structure-next] - (next-fn params params-idx vals structure-next)))))) - - 1)) - +(defn collected?* [afn] + (reify RichNavigator + (select* [this vals structure next-fn] + (if (afn vals) + (next-fn vals structure) + NONE)) + (transform* [this vals structure next-fn] + (if (afn vals) + (next-fn vals structure) + structure)))) (defn srange-transform* [structure start end next-fn] (let [structurev (vec structure) @@ -885,7 +460,7 @@ (fn [_] (repeatedly (- c (- len 2)) gensym))) ret))) - +;;TODO: all needs to change (defn- magic-precompilation* [p params-atom failed-atom] (let [magic-fail! (fn [& reason] (if (get-cell MUST-CACHE-PATHS) @@ -1105,29 +680,20 @@ (defn compiled-multi-transform* [path structure] (compiled-transform* path multi-transform-error-fn structure)) -#?(:clj - (defn extend-protocolpath* [protpath protpath-prot extensions] - (let [extensions (partition 2 extensions) - m (-> protpath-prot :sigs keys first) - expected-params (num-needed-params protpath)] - (doseq [[atype apath] extensions] - (let [p (comp-paths-internalized apath) - needed-params (num-needed-params p) - rich-nav (extract-rich-nav p)] - - (if-not (= needed-params expected-params) - (throw-illegal "Invalid number of params in extended protocol path, expected " - expected-params " but got " needed-params)) - (extend atype protpath-prot {m (fn [_] rich-nav)})))))) - - -(defn parameterize-path [apath params params-idx] - (if (instance? CompiledPath apath) - apath - (bind-params* apath params params-idx))) - - -(defn mk-jump-next-fn [next-fn init-idx total-params] - (let [jumped (+ init-idx total-params)] - (fn [params params-idx vals structure] - (next-fn params jumped vals structure)))) +;;TODO: need a way to deal with protocol paths... +;;maybe they get extended with a function and produce a `path` +;;but could be recursive +; #?(:clj +; (defn extend-protocolpath* [protpath protpath-prot extensions] +; (let [extensions (partition 2 extensions) +; m (-> protpath-prot :sigs keys first) +; expected-params (num-needed-params protpath)] +; (doseq [[atype apath] extensions] +; (let [p (comp-paths-internalized apath) +; needed-params (num-needed-params p) +; rich-nav (extract-rich-nav p)] +; +; (if-not (= needed-params expected-params) +; (throw-illegal "Invalid number of params in extended protocol path, expected " +; expected-params " but got " needed-params)) +; (extend atype protpath-prot {m (fn [_] rich-nav)})))))) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index d4530b4..12d11e5 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -1,9 +1,7 @@ (ns com.rpl.specter.macros - (:use [com.rpl.specter.protocols :only [Navigator]] - [com.rpl.specter.impl :only [RichNavigator]]) + (:use [com.rpl.specter.protocols :only [RichNavigator]]) (:require [com.rpl.specter.impl :as i] - [clojure.walk :as cljwalk] - [com.rpl.specter.defnavhelpers :as dnh])) + [clojure.walk :as cljwalk])) (defn ^:no-doc gensyms [amt] @@ -17,243 +15,33 @@ grouped)) -(defmacro richnav - "Defines a navigator with full access to collected vals, the parameters array, - and the parameters array index. `next-fn` expects to receive the params array, - a params index, the collected vals, and finally the next structure. - `next-fn` will automatically skip ahead in params array by `num-params`, so the - index passed to it is ignored. - This is the lowest level way of making navigators." - [num-params & impls] - (let [{[s-params & s-body] 'select* - [t-params & t-body] 'transform*} (determine-params-impls impls) - s-next-fn-sym (last s-params) - s-pidx-sym (nth s-params 2) - t-next-fn-sym (last t-params) - t-pidx-sym (nth t-params 2)] - - `(let [num-params# ~num-params - nav# (reify RichNavigator - (~'rich-select* ~s-params - (let [~s-next-fn-sym (i/mk-jump-next-fn ~s-next-fn-sym ~s-pidx-sym num-params#)] - ~@s-body)) - (~'rich-transform* ~t-params - (let [~t-next-fn-sym (i/mk-jump-next-fn ~t-next-fn-sym ~t-pidx-sym num-params#)] - ~@t-body)))] - - (if (zero? num-params#) - (i/no-params-rich-compiled-path nav#) - (i/->ParamsNeededPath nav# num-params#))))) - - -(defmacro ^:no-doc lean-nav* [& impls] - `(reify Navigator ~@impls)) - -(defn ^:no-doc operation-with-bindings [bindings params-sym params-idx-sym op-maker] - (let [bindings (partition 2 bindings) - binding-fn-syms (gensyms (count bindings)) - binding-syms (map first bindings) - fn-exprs (map second bindings) - binding-fn-declarations (vec (mapcat vector binding-fn-syms fn-exprs)) - binding-declarations (vec (mapcat (fn [s f] [s `(~f ~params-sym ~params-idx-sym)]) - binding-syms - binding-fn-syms)) - body (op-maker binding-declarations)] - `(let [~@binding-fn-declarations] - ~body))) - -(defmacro ^:no-doc rich-nav-with-bindings [num-params-code bindings & impls] - (let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select* - [[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*} - (determine-params-impls impls) - params-sym (gensym "params") - params-idx-sym (gensym "params-idx")] - - (operation-with-bindings - bindings - params-sym - params-idx-sym - (fn [binding-declarations] - `(reify RichNavigator - (~'rich-select* [this# ~params-sym ~params-idx-sym vals# ~s-structure-sym next-fn#] - (let [~@binding-declarations - next-params-idx# (+ ~params-idx-sym ~num-params-code) - ~s-next-fn-sym (fn [structure#] - (next-fn# ~params-sym - next-params-idx# - vals# - structure#))] - ~@s-body)) - - (~'rich-transform* [this# ~params-sym ~params-idx-sym vals# ~t-structure-sym next-fn#] - (let [~@binding-declarations - next-params-idx# (+ ~params-idx-sym ~num-params-code) - ~t-next-fn-sym (fn [structure#] - (next-fn# ~params-sym - next-params-idx# - vals# - structure#))] - ~@t-body))))))) - - - -(defmacro ^:no-doc collector-with-bindings [num-params-code bindings impl] - (let [[_ [_ structure-sym] & body] impl - params-sym (gensym "params") - params-idx-sym (gensym "params")] - (operation-with-bindings - bindings - params-sym - params-idx-sym - (fn [binding-declarations] - `(let [num-params# ~num-params-code - cfn# (fn [~params-sym ~params-idx-sym vals# ~structure-sym next-fn#] - (let [~@binding-declarations] - (next-fn# ~params-sym (+ ~params-idx-sym num-params#) (conj vals# (do ~@body)) ~structure-sym)))] - - (reify RichNavigator - (~'rich-select* [this# params# params-idx# vals# structure# next-fn#] - (cfn# params# params-idx# vals# structure# next-fn#)) - (~'rich-transform* [this# params# params-idx# vals# structure# next-fn#] - (cfn# params# params-idx# vals# structure# next-fn#)))))))) - - -(defn- delta-param-bindings [params] - (->> params - (map-indexed (fn [i p] [p `(dnh/param-delta ~i)])) - (apply concat) - vec)) - - -(defmacro nav - "Defines a navigator with late bound parameters. This navigator can be precompiled - with other navigators without knowing the parameters. When precompiled with other - navigators, the resulting path takes in parameters for all navigators in the path - that needed parameters (in the order in which they were declared)." - [params & impls] +(defmacro richnav [params & impls] (if (empty? params) - `(i/lean-compiled-path (lean-nav* ~@impls)) - `(vary-meta - (fn ~params (i/lean-compiled-path (lean-nav* ~@impls))) - assoc - :highernav - {:type :lean - :params-needed-path - (i/->ParamsNeededPath - (rich-nav-with-bindings ~(count params) - ~(delta-param-bindings params) - ~@impls) - - ~(count params))}))) + (reify RichNavigator ~@impls) + `(fn ~params + (reify RichNavigator + ~@impls)))) -(defmacro collector - "Defines a Collector with late bound parameters. This collector can be precompiled - with other selectors without knowing the parameters. When precompiled with other - selectors, the resulting selector takes in parameters for all selectors in the path - that needed parameters (in the order in which they were declared). - " - [params body] - `(let [rich-nav# (collector-with-bindings ~(count params) - ~(delta-param-bindings params) - ~body)] +(defmacro nav [params & impls] + (let [{[[_ s-structure-sym s-next-fn-sym] & s-body] 'select* + [[_ t-structure-sym t-next-fn-sym] & t-body] 'transform*} (determine-params-impls impls)] + `(richnav ~params + (~'select* [this# vals# ~s-structure-sym next-fn#] + (let [~s-next-fn-sym (fn [s#] (next-fn# vals# s#))] + ~@s-body)) + (~'transform* [this# vals# ~t-structure-sym next-fn#] + (let [~t-next-fn-sym (fn [s#] (next-fn# vals# s#))] + ~@t-body))))) - (if ~(empty? params) - (i/no-params-rich-compiled-path rich-nav#) - (vary-meta - (fn ~params - (i/no-params-rich-compiled-path - (collector-with-bindings 0 [] - ~body))) - assoc - :highernav - {:type :rich - :params-needed-path - (i/->ParamsNeededPath - rich-nav# - ~(count params))})))) - - - -(defn ^:no-doc fixed-pathed-operation [bindings op-maker] - (let [bindings (partition 2 bindings) - late-path-syms (map first bindings) - paths-code (vec (map second bindings)) - delta-syms (vec (gensyms (count bindings))) - compiled-syms (vec (gensyms (count bindings))) - runtime-bindings (vec (mapcat - (fn [l c d] - `[~l (dnh/bound-params ~c ~d)]) - - late-path-syms - compiled-syms - delta-syms)) - total-params-sym (gensym "total-params") - body (op-maker runtime-bindings compiled-syms total-params-sym)] - `(let [compiled# (doall (map i/comp-paths* ~paths-code)) - ~compiled-syms compiled# - deltas# (cons 0 (reductions + (map i/num-needed-params compiled#))) - ~delta-syms deltas# - ~total-params-sym (last deltas#)] - - ~body))) - - -(defmacro fixed-pathed-nav - "This helper is used to define navigators that take in a fixed number of other - paths as input. Those paths may require late-bound params, so this helper - will create a parameterized navigator if that is the case. If no late-bound params - are required, then the result is executable." - [bindings & impls] - (fixed-pathed-operation bindings - (fn [runtime-bindings compiled-syms total-params-sym] - (let [late-syms (map first (partition 2 bindings)) - lean-bindings (mapcat vector late-syms compiled-syms)] - `(if (zero? ~total-params-sym) - (let [~@lean-bindings] - (i/lean-compiled-path (lean-nav* ~@impls))) - - (i/->ParamsNeededPath - (rich-nav-with-bindings ~total-params-sym - ~runtime-bindings - ~@impls) - - ~total-params-sym)))))) - - - - -(defmacro fixed-pathed-collector - "This helper is used to define collectors that take in a fixed number of - paths as input. That path may require late-bound params, so this helper - will create a parameterized navigator if that is the case. If no late-bound params - are required, then the result is executable." - [bindings & body] - (fixed-pathed-operation bindings - (fn [runtime-bindings compiled-syms total-params-sym] - (let [late-syms (map first (partition 2 bindings)) - lean-bindings (mapcat vector late-syms compiled-syms)] - `(if (zero? ~total-params-sym) - (let [~@lean-bindings] - (i/no-params-rich-compiled-path - (collector-with-bindings 0 [] ~@body))) - (i/->ParamsNeededPath - (collector-with-bindings ~total-params-sym - ~runtime-bindings - ~@body) - - ~total-params-sym)))))) - - -(defmacro paramsfn [params [structure-sym] & impl] - `(nav ~params - (~'select* [this# structure# next-fn#] - (let [afn# (fn [~structure-sym] ~@impl)] - (i/filter-select afn# structure# next-fn#))) - - (~'transform* [this# structure# next-fn#] - (let [afn# (fn [~structure-sym] ~@impl)] - (i/filter-transform afn# structure# next-fn#))))) +(defmacro collector [params [_ [_ structure-sym] & body] impl] + (let [cfn# (fn [vals# ~structure-sym next-fn#] + (next-fn# (conj vals# (do ~@body)) ~structure-sym))] + `(richnav ~params + (~'select* [this# vals# structure# next-fn#] + (cfn# vals# structure# next-fn#)) + (~'transform* [this# vals# structure# next-fn#] + (cfn# vals# structure# next-fn#))))) (defn- helper-name [name method-name] (symbol (str name "-" method-name))) @@ -269,14 +57,76 @@ ~@helpers (def ~name (nav ~params ~@impls))))) +(defrichnav [name params & impls] + `(def ~name (richnav ~params ~@impls))) + (defmacro defcollector [name & body] `(def ~name (collector ~@body))) +(defmacro late-bound-nav [bindings & impl]) + ;;TODO + ;; if bindings are static, then immediately return a navigator + ;; otherwise, return a function from params -> navigator (using nav) + ;; function has metadata about what each arg should correspond to + + ;;TODO: + ;; during inline caching analysis, defpathedfn can return: + ;; - a path (in a sequence - vector or list from &), which can contain both static and dynamic params + ;; - a navigator implementation + ;; - a late-bound-nav or late-bound-collector + ;; - which can have within the late paths other late-bound paths + ;; - a record containing a function that takes in params, and then a vector of + ;; what those params are (exactly what was put into bindings) + ;; - should explicitly say in late-bound-nav which ones are paths and which aren't + ;; - can use ^:path metadata? or wrap in: (late-path path) + ;; - a non-vector constant (which will have indirect-nav run on it) + ;; + + ;; when `path` passes args to a pathedfn: + ;; - needs to wrap all dynamic portions in "dynamicparam" + ;; (VarUse, LocalSym, etc.) + ;; - it should descend into vectors as well + + + ;; inline caching should do the following: + ;; - escape path as it's doing now (recursing into vectors) + ;; - go through path and for each navigator position: + ;; - if a localsym: then it's a dynamic call to (if (navigator? ...) ... (indirect-nav)) + ;; - if a varuse: if dynamic, then it's a dynamic call as above + ;; - if static, then get the value. if a navigator then done, otherwise call indirect-nav + ;; - if specialform: it's a dynamic call to if (navigator? ...) as above + ;; - if fninvocation: + ;; - if not pathedfn: + ;; - if params are constant, then invoke. if return is not navigator, then call indirect-nav + ;; - otherwise, label that point as "dynamic invocation" with the args + ;; - if pathedfn: + ;; - take all arguments that have anything dynamic in them and wrap in dynamicparam + ;; - including inside vectors (just one level + ;; - call the function: + ;; - if return is constant, then do indirect-nav or use the nav + ;; - if return is a sequence, then treat it as path for that point to be merged in + ;; , strip "dynamicparam", and recurse inside the vector + ;; - should also flatten the vector + ;; - if return is a late-bound record, then: + ;; - label point as dynamic invocation with the args + ;; - args marked as "latepath" TODO + ;; - if sequence: then flatten and recurse + ;; - if constant, then call indirect-nav + + ;; for all (if (navigator ...)... (indirect-nav)) calls, use metadata to determine whether + ;; return is definitely a navigator in which case that dynamic code can be omitted + ;; annotation could be :tag or :direct-nav + ;; defnav needs to annotate return appropriately + + + (defn- protpath-sym [name] (-> name (str "-prot") symbol)) +;;TODO: redesign so can still have parameterized protpaths... +;;TODO: mainly need recursion (defmacro defprotocolpath "Defines a navigator that chooses the path to take based on the type of the value at the current point. May be specified with parameters to @@ -301,19 +151,19 @@ m (-> name (str "-retrieve") symbol) num-params (count params) ssym (gensym "structure") - rargs [(gensym "params") (gensym "pidx") (gensym "vals") ssym (gensym "next-fn")] + rargs [(gensym "vals") ssym (gensym "next-fn")] retrieve `(~m ~ssym)] `(do (defprotocol ~prot-name (~m [structure#])) (let [nav# (reify RichNavigator - (~'rich-select* [this# ~@rargs] + (~'select* [this# ~@rargs] (let [inav# ~retrieve] - (i/exec-rich-select* inav# ~@rargs))) + (i/exec-select* inav# ~@rargs))) (~'rich-transform* [this# ~@rargs] (let [inav# ~retrieve] - (i/exec-rich-transform* inav# ~@rargs))))] + (i/exec-transform* inav# ~@rargs))))] (def ~name (if (= ~num-params 0) @@ -328,7 +178,7 @@ (vary-meta (symbol (str name "-declared")) assoc :no-doc true)) - +;;TODO: redesign so can be recursive (defmacro declarepath ([name] `(declarepath ~name [])) @@ -402,43 +252,15 @@ (defmacro defpathedfn "Defines a higher order navigator that itself takes in one or more paths - as input. This macro is generally used in conjunction with [[fixed-pathed-nav]] - or [[variable-pathed-nav]]. When inline factoring is applied to a path containing - one of these higher order navigators, it will automatically interepret all - arguments as paths, factor them accordingly, and set up the callsite to - provide the parameters dynamically. Use ^:notpath metadata on arguments - to indicate non-path arguments that should not be factored – note that in order - to be inline factorable, these arguments must be statically resolvable (e.g. a - top level var). See `transformed` for an example." + as input. When inline caching is applied to a path containing + one of these higher order navigators, it will apply inline caching and + compilation to the subpaths as well. Use ^:notpath metadata on arguments + to indicate non-path arguments that should not be compiled" [name & args] (let [[name args] (name-with-attributes name args) name (vary-meta name assoc :pathedfn true)] `(defn ~name ~@args))) -(defmacro defnavconstructor [name & args] - (let [[name [[csym anav] & body-or-bodies]] (name-with-attributes name args) - bodies (if (-> body-or-bodies first vector?) [body-or-bodies] body-or-bodies) - - checked-code - (doall - (for [[args & body] bodies] - `(~args - (let [ret# (do ~@body)] - (if (i/layered-nav? ret#) - (i/layered-nav-underlying ret#) - (i/throw-illegal "Expected result navigator '" (quote ~anav) - "' from nav constructor '" (quote ~name) "'" - " constructed with the provided constructor '" (quote ~csym) - "'"))))))] - - `(def ~name - (vary-meta - (let [~csym (i/layered-wrapper ~anav)] - (fn ~@checked-code)) - assoc :layerednav (or (-> ~anav meta :highernav :type) :rich))))) - - - (defn ^:no-doc ic-prepare-path [locals-set path] (cond @@ -610,59 +432,45 @@ (defmacro select "Navigates to and returns a sequence of all the elements specified by the path. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-select* (path ~apath) ~structure)) (defmacro select-one! "Returns exactly one element, throws exception if zero or multiple elements found. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-select-one!* (path ~apath) ~structure)) (defmacro select-one "Like select, but returns either one element or nil. Throws exception if multiple elements found. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-select-one* (path ~apath) ~structure)) (defmacro select-first "Returns first element found. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-select-first* (path ~apath) ~structure)) (defmacro select-any "Returns any element found or [[NONE]] if nothing selected. This is the most efficient of the various selection operations. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-select-any* (path ~apath) ~structure)) (defmacro selected-any? "Returns true if any element was selected, false otherwise. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-selected-any?* (path ~apath) ~structure)) (defmacro transform "Navigates to each value specified by the path and replaces it by the result of running the transform-fn on it. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath transform-fn structure] `(i/compiled-transform* (path ~apath) ~transform-fn ~structure)) @@ -671,27 +479,21 @@ inline in the path using `terminal`. Error is thrown if navigation finishes at a non-`terminal` navigator. `terminal-val` is a wrapper around `terminal` and is the `multi-transform` equivalent of `setval`. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/compiled-multi-transform* (path ~apath) ~structure)) (defmacro setval "Navigates to each value specified by the path and replaces it by `aval`. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath aval structure] `(i/compiled-setval* (path ~apath) ~aval ~structure)) (defmacro traverse "Return a reducible object that traverses over `structure` to every element specified by the path. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath structure] `(i/do-compiled-traverse (path ~apath) ~structure)) @@ -701,9 +503,7 @@ what's used to transform the data structure, while user-ret will be added to the user-ret sequence in the final return. replace-in is useful for situations where you need to know the specific values of what was transformed in the data structure. - This macro will attempt to do inline factoring and caching of the path, falling - back to compiling the path on every invocation if it's not possible to - factor/cache the path." + This macro will do inline caching of the path." [apath transform-fn structure & args] `(i/compiled-replace-in* (path ~apath) ~transform-fn ~structure ~@args)) @@ -713,5 +513,4 @@ at each collected value as individual arguments, or `(collected? v ...)` syntax to capture all the collected values as a single vector." [params & body] - (let [platform (if (contains? &env :locals) :cljs :clj)] - `(i/collected?* (~'fn [~params] ~@body)))) + `(i/collected?* (~'fn [~params] ~@body))) diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index cbd734b..8dabcca 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -287,42 +287,27 @@ -(defn if-select [params params-idx vals structure next-fn then-tester then-nav then-params else-nav] - (let [test? (then-tester structure) - sel (if test? - then-nav - else-nav) - idx (if test? params-idx (+ params-idx then-params))] - (i/exec-rich-select* - sel - params - idx - vals - structure - next-fn))) +(defn if-select [vals structure next-fn then-tester then-nav else-nav] + (i/exec-select* + (if (then-tester structure) then-nav else-nav) + vals + structure + next-fn)) -(defn if-transform [params params-idx vals structure next-fn then-tester then-nav then-params else-nav] - (let [test? (then-tester structure) - tran (if test? - then-nav - else-nav) - idx (if test? params-idx (+ params-idx then-params))] - (i/exec-rich-transform* - tran - params - idx - vals - structure - next-fn))) +(defn if-transform [vals structure next-fn then-tester then-nav else-nav] + (i/exec-transform* + (if (then-tester structure) then-nav else-nav) + vals + structure + next-fn)) -(defn terminal* [params params-idx vals structure] - (let [afn (aget ^objects params params-idx)] - (if (identical? vals []) - (afn structure) - (apply afn (conj vals structure))))) +(defn terminal* [afn vals structure] + (if (identical? vals []) + (afn structure) + (apply afn (conj vals structure)))) @@ -467,12 +452,3 @@ (if (and (i/fn-invocation? structure) (i/fn-invocation? ret)) (with-meta ret (meta structure)) ret)))) - - -(def DISPENSE* - (i/no-params-rich-compiled-path - (reify i/RichNavigator - (rich-select* [this params params-idx vals structure next-fn] - (next-fn params params-idx [] structure)) - (rich-transform* [this params params-idx vals structure next-fn] - (next-fn params params-idx [] structure))))) diff --git a/src/clj/com/rpl/specter/protocols.cljc b/src/clj/com/rpl/specter/protocols.cljc index eee9a8e..0683a5d 100644 --- a/src/clj/com/rpl/specter/protocols.cljc +++ b/src/clj/com/rpl/specter/protocols.cljc @@ -1,9 +1,9 @@ (ns com.rpl.specter.protocols) -(defprotocol Navigator +(defprotocol RichNavigator "Do not use this protocol directly. All navigators must be created using com.rpl.specter.macros namespace." - (select* [this structure next-fn] + (select* [this vals structure next-fn] "An implementation of `select*` must call `next-fn` on each subvalue of `structure`. The result of `select*` is specified as follows: @@ -12,7 +12,7 @@ 2. `NONE` if all calls to `next-fn` return `NONE` 3. Otherwise, any non-`NONE` return value from calling `next-fn` ") - (transform* [this structure next-fn] + (transform* [this vals structure next-fn] "An implementation of `transform*` must use `next-fn` to transform any subvalues of `structure` and then merge those transformed values back into `structure`. Everything else in `structure` must be unchanged."))