From 3ba0926251bef2eafb4b39d2707c686880f00b6a Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Thu, 4 Aug 2016 11:28:47 -0400 Subject: [PATCH] refactor codebase to use reified navigator objects instead of individual functions, force all navigators to be defined using defnav, add ImplicitNav protocol --- src/clj/com/rpl/specter.cljx | 179 +++-- src/clj/com/rpl/specter/defhelpers.clj | 2 +- src/clj/com/rpl/specter/impl.cljx | 984 ++++++------------------ src/clj/com/rpl/specter/macros.clj | 140 ++-- src/clj/com/rpl/specter/navs.cljx | 500 ++++++++++++ src/clj/com/rpl/specter/protocols.cljx | 7 + src/clj/com/rpl/specter/transients.cljx | 25 +- 7 files changed, 898 insertions(+), 939 deletions(-) create mode 100644 src/clj/com/rpl/specter/navs.cljx diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index f143824..ca36043 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -14,7 +14,7 @@ [com.rpl.specter.util-macros :refer [doseqres]] ) - (:use [com.rpl.specter.protocols :only [Navigator]] + (:use [com.rpl.specter.protocols :only [ImplicitNav]] #+clj [com.rpl.specter.macros :only [pathed-collector variable-pathed-nav @@ -26,7 +26,7 @@ defnavconstructor]] #+clj [com.rpl.specter.util-macros :only [doseqres]] ) - (:require [com.rpl.specter.impl :as i] + (:require [com.rpl.specter [impl :as i] [navs :as n]] [clojure.set :as set]) ) @@ -172,19 +172,14 @@ (defn params-reset [params-path] ;; TODO: error if not paramsneededpath - (let [s (i/params-needed-selector params-path) - t (i/params-needed-transformer params-path) + (let [nav (i/params-needed-nav params-path) needed (i/num-needed-params params-path)] - (i/->ParamsNeededPath - (i/->TransformFunctions - i/RichPathExecutor - (fn [params params-idx vals structure next-fn] - (s params (- params-idx needed) vals structure next-fn) - ) - (fn [params params-idx vals structure next-fn] - (t params (- params-idx needed) vals structure next-fn) - )) - 0))) + (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 @@ -199,16 +194,15 @@ structure )) + (defnav ^{:doc "Stays navigated at the current point. Essentially a no-op navigator."} STAY - [] (select* [this structure next-fn] (next-fn structure)) (transform* [this structure next-fn] (next-fn structure))) - (def ^{:doc "For usage with `multi-transform`, defines an endpoint in the navigation that will have the parameterized transform function run. The transform @@ -216,10 +210,10 @@ given as the first arguments"} terminal (richnav 1 - (select* [params params-idx vals structure next-fn] + (select* [this params params-idx vals structure next-fn] (i/throw-illegal "'terminal' should only be used in multi-transform")) - (transform* [params params-idx vals structure next-fn] - (i/terminal* params params-idx vals structure) + (transform* [this params params-idx vals structure next-fn] + (n/terminal* params params-idx vals structure) ))) (defnavconstructor terminal-val @@ -229,11 +223,15 @@ [v] (p (i/fast-constantly v))) -(def +(defnav ^{:doc "Navigate to every element of the collection. For maps navigates to a vector of `[key value]`."} ALL - (comp-paths (i/->AllNavigator))) + [] + (select* [this structure next-fn] + (n/all-select structure next-fn)) + (transform* [this structure next-fn] + (n/all-transform structure next-fn))) (defnav ^{:doc "Navigate to each value of the map. This is more efficient than @@ -245,23 +243,25 @@ (next-fn v) )) (transform* [this structure next-fn] - (i/map-vals-transform structure next-fn) + (n/map-vals-transform structure next-fn) )) -(def VAL (i/->ValCollect)) +(defcollector VAL [] + (collect-val [this structure] + structure)) (def ^{:doc "Navigate to the last element of the collection. If the collection is empty navigation is stopped at this point."} LAST - (comp-paths (i/->PosNavigator i/get-last i/update-last))) + (n/PosNavigator i/get-last i/update-last)) (def ^{:doc "Navigate to the first element of the collection. If the collection is empty navigation is stopped at this point."} FIRST - (comp-paths (i/->PosNavigator i/get-first i/update-first))) + (n/PosNavigator i/get-first i/update-first)) (defnav ^{:doc "Uses start-fn and end-fn to determine the bounds of the subsequence @@ -269,9 +269,9 @@ srange-dynamic [start-fn end-fn] (select* [this structure next-fn] - (i/srange-select structure (start-fn structure) (end-fn structure) next-fn)) + (n/srange-select structure (start-fn structure) (end-fn structure) next-fn)) (transform* [this structure next-fn] - (i/srange-transform structure (start-fn structure) (end-fn structure) next-fn) + (n/srange-transform structure (start-fn structure) (end-fn structure) next-fn) )) (defnav @@ -280,9 +280,9 @@ srange [start end] (select* [this structure next-fn] - (i/srange-select structure start end next-fn)) + (n/srange-select structure start end next-fn)) (transform* [this structure next-fn] - (i/srange-transform structure start end next-fn) + (n/srange-transform structure start end next-fn) )) (defnav @@ -290,15 +290,15 @@ continuous-subseqs [pred] (select* [this structure next-fn] - (doseqres NONE [[s e] (i/matching-ranges structure pred)] - (i/srange-select structure s e 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]] - (i/srange-transform structure s e next-fn)) + (n/srange-transform structure s e next-fn)) structure - (reverse (i/matching-ranges structure pred)) + (reverse (n/matching-ranges structure pred)) ))) (defnav @@ -309,7 +309,7 @@ (next-fn [])) (transform* [this structure next-fn] (let [to-prepend (next-fn [])] - (i/prepend-all structure to-prepend) + (n/prepend-all structure to-prepend) ))) (defnav @@ -320,7 +320,7 @@ (next-fn [])) (transform* [this structure next-fn] (let [to-append (next-fn [])] - (i/append-all structure to-append) + (n/append-all structure to-append) ))) (defnav @@ -360,18 +360,18 @@ walker [afn] (select* [this structure next-fn] - (i/walk-select afn next-fn structure)) + (n/walk-select afn next-fn structure)) (transform* [this structure next-fn] - (i/walk-until afn next-fn structure))) + (n/walk-until afn next-fn structure))) (defnav ^{:doc "Like `walker` but maintains metadata of any forms traversed."} codewalker [afn] (select* [this structure next-fn] - (i/walk-select afn next-fn structure)) + (n/walk-select afn next-fn structure)) (transform* [this structure next-fn] - (i/codewalk-until afn next-fn structure))) + (n/codewalk-until afn next-fn structure))) (defpathedfn subselect "Navigates to a sequence that contains the results of (select ...), @@ -464,13 +464,13 @@ afn (fixed-pathed-nav [late path] (select* [this structure next-fn] - (i/filter-select - #(i/selected?* late %) + (n/filter-select + #(n/selected?* late %) structure next-fn)) (transform* [this structure next-fn] - (i/filter-transform - #(i/selected?* late %) + (n/filter-transform + #(n/selected?* late %) structure next-fn))))) @@ -479,13 +479,13 @@ (fn [s] (not (afn s))) (fixed-pathed-nav [late path] (select* [this structure next-fn] - (i/filter-select - #(i/not-selected?* late %) + (n/filter-select + #(n/not-selected?* late %) structure next-fn)) (transform* [this structure next-fn] - (i/filter-transform - #(i/not-selected?* late %) + (n/filter-transform + #(n/not-selected?* late %) structure next-fn))))) @@ -514,28 +514,6 @@ (transform* [this structure next-fn] (next-fn (compiled-transform late update-fn structure))))) -(extend-type #+clj clojure.lang.Keyword #+cljs cljs.core/Keyword - Navigator - (select* [kw structure next-fn] - (next-fn (get structure kw))) - (transform* [kw structure next-fn] - (assoc structure kw (next-fn (get structure kw))) - )) - -(extend-type #+clj clojure.lang.AFn #+cljs function - Navigator - (select* [afn structure next-fn] - (i/filter-select afn structure next-fn)) - (transform* [afn structure next-fn] - (i/filter-transform afn structure next-fn))) - -(extend-type #+clj clojure.lang.PersistentHashSet #+cljs cljs.core/PersistentHashSet - Navigator - (select* [aset structure next-fn] - (i/filter-select aset structure next-fn)) - (transform* [aset structure next-fn] - (i/filter-transform aset structure next-fn))) - (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."} @@ -543,6 +521,23 @@ i/pred* ) +(extend-type nil + ImplicitNav + (implicit-nav [this] STAY)) + +(extend-type #+clj clojure.lang.Keyword #+cljs cljs.core/Keyword + ImplicitNav + (implicit-nav [this] (keypath this)) + ) + +(extend-type #+clj clojure.lang.AFn #+cljs function + ImplicitNav + (implicit-nav [this] (pred this))) + +(extend-type #+clj clojure.lang.PersistentHashSet #+cljs cljs.core/PersistentHashSet + ImplicitNav + (implicit-nav [this] (pred this))) + (defnav ^{:doc "Navigates to the provided val if the structure is nil. Otherwise it stays navigated at the structure."} @@ -615,7 +610,7 @@ (def ^{:doc "Drops all collected values for subsequent navigation."} - DISPENSE i/DISPENSE*) + DISPENSE n/DISPENSE*) (defpathedfn if-path @@ -627,11 +622,11 @@ else-comp (i/comp-paths* else-path) then-needed (i/num-needed-params then-comp) else-needed (i/num-needed-params else-comp) - [then-s then-t] (i/extract-rich-tfns then-comp) - [else-s else-t] (i/extract-rich-tfns else-comp)] + then-nav (i/extract-rich-nav then-comp) + else-nav (i/extract-rich-nav else-comp)] (if-let [afn (i/extract-basic-filter-fn cond-p)] (richnav (+ then-needed else-needed) - (select* [params params-idx vals structure next-fn] + (select* [this params params-idx vals structure next-fn] (i/if-select params params-idx @@ -639,11 +634,11 @@ structure next-fn afn - then-s + then-nav then-needed - else-s + else-nav )) - (transform* [params params-idx vals structure next-fn] + (transform* [this params params-idx vals structure next-fn] (i/if-transform params params-idx @@ -651,14 +646,14 @@ structure next-fn afn - then-t + then-nav then-needed - else-t + else-nav )))) (let [cond-comp (i/comp-paths* cond-p) cond-needed (i/num-needed-params cond-comp)] (richnav (+ then-needed else-needed cond-needed) - (select* [params params-idx vals structure next-fn] + (select* [this params params-idx vals structure next-fn] (let [late-cond (i/parameterize-path cond-comp params params-idx)] (i/if-select params @@ -667,11 +662,11 @@ structure next-fn #(i/selected?* late-cond %) - then-s + then-nav then-needed - else-s + else-nav ))) - (transform* [params params-idx vals structure next-fn] + (transform* [this params params-idx vals structure next-fn] (let [late-cond (i/parameterize-path cond-comp params params-idx)] (i/if-transform params @@ -680,9 +675,9 @@ structure next-fn #(i/selected?* late-cond %) - then-t + then-nav then-needed - else-t + else-nav )))))))) (defpathedfn cond-path @@ -713,20 +708,20 @@ (let [comp1 (i/comp-paths* path1) comp2 (i/comp-paths* path2) comp1-needed (i/num-needed-params comp1) - [s1 t1] (i/extract-rich-tfns comp1) - [s2 t2] (i/extract-rich-tfns comp2) + nav1 (i/extract-rich-nav comp1) + nav2 (i/extract-rich-nav comp2) ] (richnav (+ comp1-needed (i/num-needed-params comp2)) - (select* [params params-idx vals structure next-fn] - (let [res1 (s1 params params-idx vals structure next-fn) - res2 (s2 params (+ params-idx comp1-needed) vals structure next-fn)] + (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* [params params-idx vals structure next-fn] - (let [s1 (t1 params params-idx vals structure next-fn)] - (t2 params (+ params-idx comp1-needed) vals s1 next-fn) + (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) ))))) ([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 index 31a4a39..e45eed7 100644 --- a/src/clj/com/rpl/specter/defhelpers.clj +++ b/src/clj/com/rpl/specter/defhelpers.clj @@ -13,7 +13,7 @@ ~@setters (com.rpl.specter.impl/bind-params* this# ~a 0) )))] - `(defrecord ~'ParamsNeededPath [~'transform-fns ~'num-needed-params] + `(defrecord ~'ParamsNeededPath [~'rich-nav ~'num-needed-params] ~fn-type ~@impls ~var-arity-impl diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 44b3ce9..c2026c9 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -6,11 +6,10 @@ [com.rpl.specter.util-macros :refer [doseqres]] ) (:use [com.rpl.specter.protocols :only - [select* transform* collect-val]] + [select* transform* collect-val Navigator]] #+clj [com.rpl.specter.util-macros :only [doseqres]] ) (:require [com.rpl.specter.protocols :as p] - [clojure.walk :as walk] #+clj [clojure.core.reducers :as r] [clojure.string :as s] #+clj [com.rpl.specter.defhelpers :as dh] @@ -105,20 +104,71 @@ (dotimes [_ iters] (afn)))) -(deftype ExecutorFunctions [type traverse-executor transform-executor]) +(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* 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-select* this params params-idx vals structure next-fn)) + +#+clj +(defmacro exec-select* [this & args] + (let [hinted (with-meta this {:tag com.rpl.specter.protocols.Navigator})] + `(.select* ~hinted ~@args) + )) + +#+cljs +(defn exec-select* [this params params-idx vals structure next-fn] + (p/select* this params params-idx vals structure next-fn)) + +#+clj +(defmacro exec-transform* [this & args] + (let [hinted (with-meta this {:tag com.rpl.specter.protocols.Navigator})] + `(.transform* ~hinted ~@args) + )) + +#+cljs +(defn exec-transform* [this params params-idx vals structure next-fn] + (p/transform* this params params-idx vals structure next-fn)) + (def RichPathExecutor (->ExecutorFunctions - :richpath - (fn [params params-idx selector result-fn structure] - (selector params params-idx [] structure + (fn [^ParameterizedRichNav richnavp result-fn structure] + (exec-rich-select* (.-rich-nav nav) + (.-params richnavp) (.-params-idx richnavp) + [] structure (fn [_ _ vals structure] (result-fn (if (identical? vals []) structure (conj vals structure)))))) - (fn [params params-idx transformer transform-fn structure] - (transformer params params-idx [] structure + (fn [^ParameterizedRichNav richnavp transform-fn structure] + (exec-rich-transform* (.-rich-nav nav) + (.-params richnavp) (.-params-idx richnavp) + [] structure (fn [_ _ vals structure] (if (identical? [] vals) (transform-fn structure) @@ -127,22 +177,28 @@ (def LeanPathExecutor (->ExecutorFunctions - :leanpath - (fn [params params-idx selector result-fn structure] - (selector structure result-fn)) - (fn [params params-idx transformer transform-fn structure] - (transformer structure transform-fn)) + (fn [nav result-fn structure] + (exec-select* nav structure result-fn)) + (fn [nav transform-fn structure] + (exec-transform* nav structure transform-fn)) )) -(defrecord TransformFunctions [executors selector transformer]) - -(defrecord CompiledPath [transform-fns params params-idx]) +(defrecord CompiledPath [executors nav]) (defn compiled-path? [o] (instance? CompiledPath o)) -(defn no-params-compiled-path [transform-fns] - (->CompiledPath transform-fns nil 0)) +(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*) @@ -184,11 +240,24 @@ (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 - (.-transform-fns params-needed-path) - params - idx)) + RichPathExecutor + (->ParameterizedRichNav + (.-rich-nav params-needed-path) + params + idx + ))) (defprotocol PathComposer (do-comp-paths [paths])) @@ -203,93 +272,18 @@ empty? not)) -(defn no-prot-error-str [obj] - (str "Protocol implementation cannot be found for object. - Extending Specter protocols should not be done inline in a deftype definition - because that prevents Specter from finding the protocol implementations for - optimized performance. Instead, you should extend the protocols via an - explicit extend-protocol call. \n" obj)) - -#+clj -(defn find-protocol-impl! [prot obj] - (let [ret (find-protocol-impl prot obj)] - (if (= ret obj) - (throw-illegal (no-prot-error-str obj)) - ret - ))) - -#+clj -(defn structure-path-impl [this] - (if (fn? this) - ;;TODO: this isn't kosher, it uses knowledge of internals of protocols - (-> p/Navigator :impls (get clojure.lang.AFn)) - (find-protocol-impl! p/Navigator this))) - -#+clj -(defn collector-impl [this] - (find-protocol-impl! p/Collector this)) - - -#+cljs -(defn structure-path-impl [obj] - {:select* (mk-optimized-invocation p/Navigator obj select* 2) - :transform* (mk-optimized-invocation p/Navigator obj transform* 2) - }) - -#+cljs -(defn collector-impl [obj] - {:collect-val (mk-optimized-invocation p/Collector obj collect-val 1) - }) - -(defn coerce-collector [this] - (let [cfn (->> this - collector-impl - :collect-val - ) - afn (fn [params params-idx vals structure next-fn] - (next-fn params params-idx (conj vals (cfn this structure)) structure) - )] - (no-params-compiled-path - (->TransformFunctions RichPathExecutor afn afn) - ))) - - -(defn coerce-structure-path [this] - (let [pimpl (structure-path-impl this) - selector (:select* pimpl) - transformer (:transform* pimpl)] - (no-params-compiled-path - (->TransformFunctions - LeanPathExecutor - (fn [structure next-fn] - (selector this structure next-fn)) - (fn [structure next-fn] - (transformer this structure next-fn))) - ))) - -(defn coerce-structure-path-rich [this] - (let [pimpl (structure-path-impl this) - selector (:select* pimpl) - transformer (:transform* pimpl)] - (no-params-compiled-path - (->TransformFunctions - RichPathExecutor - (fn [params params-idx vals structure next-fn] - (selector this structure (fn [structure] (next-fn params params-idx vals structure)))) - (fn [params params-idx vals structure next-fn] - (transformer this structure (fn [structure] (next-fn params params-idx vals structure))))) - ))) - -(defn structure-path? [obj] - (or (fn? obj) (satisfies? p/Navigator obj))) +(defn- coerce-object [this] + (cond (satisfies? p/ImplicitNav this) (p/implicit-nav this) + :else (throw-illegal "Not a navigator: " this) + )) (defprotocol CoercePath (coerce-path [this])) (extend-protocol CoercePath - nil ; needs its own path because it doesn't count as an Object + nil ; needs its own coercer because it doesn't count as an Object (coerce-path [this] - (coerce-structure-path nil)) + (coerce-object this)) CompiledPath (coerce-path [this] @@ -298,7 +292,7 @@ ParamsNeededPath (coerce-path [this] this) - + #+clj java.util.List #+cljs cljs.core/PersistentVector (coerce-path [this] (do-comp-paths this)) @@ -318,110 +312,112 @@ #+clj Object #+cljs default (coerce-path [this] - (cond (structure-path? this) (coerce-structure-path this) - (satisfies? p/Collector this) (coerce-collector this) - :else (throw-illegal (no-prot-error-str this)) - ))) + (coerce-object this))) -(defn extype [^TransformFunctions f] - (let [^ExecutorFunctions exs (.-executors f)] - (.-type exs) - )) - -(defn- combine-same-types [[^TransformFunctions f & _ :as all]] - (let [^ExecutorFunctions exs (.-executors f) - - t (.-type exs) - - combiner - (if (= t :richpath) +(defn- combine-same-types [n & _ :as all]] + (let [combiner + (if (satisfies? RichNavigator n) (fn [curr next] - (fn [params params-idx vals structure next-fn] - (curr params params-idx vals structure - (fn [params-next params-idx-next vals-next structure-next] - (next params-next params-idx-next vals-next structure-next next-fn) - )))) + (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] - (fn [structure next-fn] - (curr structure (fn [structure] (next structure next-fn))))) - )] + (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 (fn [^TransformFunctions curr ^TransformFunctions next] - (->TransformFunctions - exs - (combiner (.-selector curr) (.-selector next)) - (combiner (.-transformer curr) (.-transformer next)) - )) - all))) + (reduce combiner all))) -(defn coerce-tfns-rich [^TransformFunctions tfns] - (if (= (extype tfns) :richpath) - tfns - (let [selector (.-selector tfns) - transformer (.-transformer tfns)] - (->TransformFunctions - RichPathExecutor - (fn [params params-idx vals structure next-fn] - (selector structure (fn [structure] (next-fn params params-idx vals structure)))) - (fn [params params-idx vals structure next-fn] - (transformer structure (fn [structure] (next-fn params params-idx vals structure)))) - )))) +(defn coerce-rich-navigator [nav] + (if (satisfies? RichNavigator nav) + nav + (let [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] - (if-not (instance? CompiledPath path) + (cond + (not (instance? CompiledPath path)) path - (let [params (:params path) - params-idx (:params-idx path) - selector (-> path :transform-fns :selector) - transformer (-> path :transform-fns :transformer)] + + (instance? Navigator (:nav path)) + path + + :else + (let [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-compiled-path - (->TransformFunctions - RichPathExecutor - (fn [x-params x-params-idx vals structure next-fn] - (selector params params-idx vals structure + (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 x-params x-params-idx vals-next structure-next) + (next-fn params2 params-idx2 vals-next structure-next) ))) - (fn [x-params x-params-idx vals structure next-fn] - (transformer params params-idx vals structure + (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 x-params x-params-idx vals-next structure-next) - )))) - ))))) + (next-fn params2 params-idx2 vals-next structure-next) + ))))))))) (extend-protocol PathComposer nil - (do-comp-paths [sp] - (coerce-path sp)) + (do-comp-paths [o] + (coerce-path o)) #+clj Object #+cljs default - (do-comp-paths [sp] - (coerce-path sp)) + (do-comp-paths [o] + (coerce-path o)) #+clj java.util.List #+cljs cljs.core/PersistentVector - (do-comp-paths [structure-paths] - (if (empty? structure-paths) + (do-comp-paths [navigators] + (if (empty? navigators) (coerce-path nil) - (let [coerced (->> structure-paths + (let [coerced (->> navigators (map coerce-path) (map capture-params-internally)) combined (->> coerced - (map :transform-fns) - (partition-by extype) + (map extract-nav) + (partition-by class) (map combine-same-types) ) - result-tfn (if (= 1 (count combined)) + result-nav (if (= 1 (count combined)) (first combined) (->> combined - (map coerce-tfns-rich) + (map coerce-rich-navigator) combine-same-types) ) needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)] (if (empty? needs-params-paths) - (no-params-compiled-path result-tfn) + (no-params-rich-compiled-path result-nav) (->ParamsNeededPath - (coerce-tfns-rich result-tfn) + (coerce-rich-navigator result-nav) (->> needs-params-paths (map :num-needed-params) (reduce +)) @@ -479,214 +475,37 @@ (set-cell! cell ret) ret)) -(defn- append [coll elem] - (-> coll vec (conj elem))) - -(defprotocol AddExtremes - (append-all [structure elements]) - (prepend-all [structure elements])) - -(extend-protocol AddExtremes - nil - (append-all [_ elements] - elements) - (prepend-all [_ elements] - elements) - - #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector - (append-all [structure elements] - (reduce conj structure elements)) - (prepend-all [structure elements] - (let [ret (transient [])] - (as-> ret <> - (reduce conj! <> elements) - (reduce conj! <> structure) - (persistent! <>) - ))) - - #+clj Object #+cljs default - (append-all [structure elements] - (concat structure elements)) - (prepend-all [structure elements] - (concat elements structure)) - ) -(defprotocol UpdateExtremes - (update-first [s afn]) - (update-last [s afn])) +(defn compiled-nav-field [^CompiledPath p] + (.-nav p)) -(defprotocol GetExtremes - (get-first [s]) - (get-last [s])) +(defn compiled-executors-field [^CompiledPath p] + (.-executors p)) -(defprotocol FastEmpty - (fast-empty? [s])) - -(defn- update-first-list [l afn] - (cons (afn (first l)) (rest l))) - -(defn- update-last-list [l afn] - (append (butlast l) (afn (last l)))) - -#+clj -(defn vec-count [^clojure.lang.IPersistentVector v] - (.length v)) - -#+cljs -(defn vec-count [v] - (count v)) - -#+clj -(defn transient-vec-count [^clojure.lang.ITransientVector v] - (.count v)) - -#+cljs -(defn transient-vec-count [v] - (count v)) - -(extend-protocol UpdateExtremes - #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector - (update-first [v afn] - (let [val (nth v 0)] - (assoc v 0 (afn val)) - )) - (update-last [v afn] - ;; type-hinting vec-count to ^int caused weird errors with case - (let [c (int (vec-count v))] - (case c - 1 (let [[e] v] [(afn e)]) - 2 (let [[e1 e2] v] [e1 (afn e2)]) - (let [i (dec c)] - (assoc v i (afn (nth v i))) - )))) - #+clj Object #+cljs default - (update-first [l val] - (update-first-list l val)) - (update-last [l val] - (update-last-list l val) - )) - -(extend-protocol GetExtremes - #+clj clojure.lang.IPersistentVector #+cljs cljs.core/PersistentVector - (get-first [v] - (nth v 0)) - (get-last [v] - (peek v)) - #+clj Object #+cljs default - (get-first [s] - (first s)) - (get-last [s] - (last s) - )) - - -(extend-protocol FastEmpty - nil - (fast-empty? [_] true) - - #+clj clojure.lang.IPersistentVector #+cljs cljs.core/PersistentVector - (fast-empty? [v] - (= 0 (vec-count v))) - #+clj clojure.lang.ITransientVector #+cljs cljs.core/TransientVector - (fast-empty? [v] - (= 0 (transient-vec-count v))) - #+clj Object #+cljs default - (fast-empty? [s] - (empty? s)) - ) - -(defn walk-until [pred on-match-fn structure] - (if (pred structure) - (on-match-fn structure) - (walk/walk (partial walk-until pred on-match-fn) identity structure) - )) - -(defn fn-invocation? [f] - (or #+clj (instance? clojure.lang.Cons f) - #+clj (instance? clojure.lang.LazySeq f) - #+cljs (instance? cljs.core.LazySeq f) - (list? f))) - -(defn codewalk-until [pred on-match-fn structure] - (if (pred structure) - (on-match-fn structure) - (let [ret (walk/walk (partial codewalk-until pred on-match-fn) identity structure)] - (if (and (fn-invocation? structure) (fn-invocation? ret)) - (with-meta ret (meta structure)) - ret - )))) - - -(def collected?* - (->ParamsNeededPath - (->TransformFunctions - RichPathExecutor - (fn [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 - ))) - (fn [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 DISPENSE* - (no-params-compiled-path - (->TransformFunctions - RichPathExecutor - (fn [params params-idx vals structure next-fn] - (next-fn params params-idx [] structure)) - (fn [params params-idx vals structure next-fn] - (next-fn params params-idx [] structure)) - ))) - -(defn transform-fns-field [^CompiledPath path] - (.-transform-fns path)) - -(defn executors-field [^TransformFunctions tfns] - (.-executors tfns)) (defn traverse-executor-field [^ExecutorFunctions ex] (.-traverse-executor ex)) -(defn params-field [^CompiledPath path] - (.-params path)) - -(defn params-idx-field [^CompiledPath path] - (.-params-idx path)) - -(defn selector-field [^TransformFunctions tfns] - (.-selector tfns)) ;; 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 [tfns# (transform-fns-field ~path) - ex# (executors-field tfns#)] + `(let [nav# (compiled-nav-field ~path) + ex# (compiled-executors-field ~path)] ((traverse-executor-field ex#) - (params-field ~path) - (params-idx-field ~path) - (selector-field tfns#) + nav# ~result-fn ~structure) )) #+cljs (defn compiled-traverse* [path result-fn structure] - (let [tfns (transform-fns-field path) - ex (executors-field tfns)] + (let [nav (compiled-nav-field path) + ex (compiled-executors-field path)] ((traverse-executor-field ex) - (params-field path) - (params-idx-field path) - (selector-field tfns) + nav result-fn structure) )) @@ -771,386 +590,36 @@ (defn compiled-transform* [^com.rpl.specter.impl.CompiledPath path transform-fn structure] - (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path) + (let [nav (.-nav path) ^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)] - ((.-transform-executor ex) (.-params path) (.-params-idx path) (.-transformer tfns) transform-fn structure) + ((.-transform-executor ex) nav transform-fn structure) )) -(defn not-selected?* - [compiled-path structure] - (->> structure - (compiled-select-any* compiled-path) - (identical? NONE))) -(defn selected?* - [compiled-path structure] - (not (not-selected?* compiled-path structure))) +(defn params-needed-nav + ^com.rpl.specter.impl.RichNavigator + [^com.rpl.specter.impl.ParamsNeededPath path] + (.-rich-nav path)) -(defn walk-select [pred continue-fn structure] - (let [ret (mutable-cell NONE) - walker (fn this [structure] - (if (pred structure) - (let [r (continue-fn structure)] - (if-not (identical? r NONE) - (set-cell! ret r)) - r - ) - (walk/walk this identity structure) - ))] - (walker structure) - (get-cell ret) +(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 key-select [akey structure next-fn] - (next-fn (get structure akey))) - -(defn key-transform [akey structure next-fn] - (assoc structure akey (next-fn (get structure akey)) - )) - -(defn all-select [structure next-fn] - (doseqres NONE [e structure] - (next-fn e))) - -#+cljs -(defn queue? [coll] - (= (type coll) (type #queue []))) - -#+clj -(defn queue? [coll] - (instance? clojure.lang.PersistentQueue coll)) - -(defprotocol AllTransformProtocol - (all-transform [structure next-fn])) - -(defn- non-transient-map-all-transform [structure next-fn empty-map] - (reduce-kv - (fn [m k v] - (let [[newk newv] (next-fn [k v])] - (assoc m newk newv) - )) - empty-map - structure - )) - -(extend-protocol AllTransformProtocol - nil - (all-transform [structure next-fn] - nil - ) - - ;; in cljs they're PersistentVector so don't need a special case - #+clj clojure.lang.MapEntry - #+clj - (all-transform [structure next-fn] - (let [newk (next-fn (key structure)) - newv (next-fn (val structure))] - (clojure.lang.MapEntry. newk newv) - )) - - #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector - (all-transform [structure next-fn] - (mapv next-fn structure)) - - #+clj - clojure.lang.PersistentArrayMap - #+clj - (all-transform [structure next-fn] - (let [k-it (.keyIterator structure) - v-it (.valIterator structure)] - (loop [ret {}] - (if (.hasNext k-it) - (let [k (.next k-it) - v (.next v-it) - [newk newv] (next-fn [k v])] - (recur (assoc ret newk newv))) - ret - )))) - - #+cljs - cljs.core/PersistentArrayMap - #+cljs - (all-transform [structure next-fn] - (non-transient-map-all-transform structure next-fn {}) - ) - - #+clj clojure.lang.PersistentTreeMap #+cljs cljs.core/PersistentTreeMap - (all-transform [structure next-fn] - (non-transient-map-all-transform structure next-fn (empty structure)) - ) - - #+clj clojure.lang.PersistentHashMap #+cljs cljs.core/PersistentHashMap - (all-transform [structure next-fn] - (persistent! - (reduce-kv - (fn [m k v] - (let [[newk newv] (next-fn [k v])] - (assoc! m newk newv) - )) - (transient - #+clj clojure.lang.PersistentHashMap/EMPTY #+cljs cljs.core.PersistentHashMap.EMPTY - ) - structure - ))) - - - #+clj - Object - #+clj - (all-transform [structure next-fn] - (let [empty-structure (empty structure)] - (cond (and (list? empty-structure) (not (queue? empty-structure))) - ;; this is done to maintain order, otherwise lists get reversed - (doall (map next-fn structure)) - - (map? structure) - ;; reduce-kv is much faster than doing r/map through call to (into ...) - (reduce-kv - (fn [m k v] - (let [[newk newv] (next-fn [k v])] - (assoc m newk newv) - )) - empty-structure - structure - ) - - :else - (->> structure (r/map next-fn) (into empty-structure)) - ))) - - #+cljs - default - #+cljs - (all-transform [structure next-fn] - (let [empty-structure (empty structure)] - (if (and (list? empty-structure) (not (queue? empty-structure))) - ;; this is done to maintain order, otherwise lists get reversed - (doall (map next-fn structure)) - (into empty-structure (map #(next-fn %)) structure) - ))) - ) - -(deftype AllNavigator []) - -(extend-protocol p/Navigator - AllNavigator - (select* [this structure next-fn] - (all-select structure next-fn)) - (transform* [this structure next-fn] - (all-transform structure next-fn))) - -(defprotocol MapValsTransformProtocol - (map-vals-transform [structure next-fn])) - -(defn map-vals-non-transient-transform [structure empty-map next-fn] - (reduce-kv - (fn [m k v] - (assoc m k (next-fn v))) - empty-map - structure)) - -(extend-protocol MapValsTransformProtocol - nil - (map-vals-transform [structure next-fn] - nil - ) - - #+clj - clojure.lang.PersistentArrayMap - #+clj - (map-vals-transform [structure next-fn] - (let [k-it (.keyIterator structure) - v-it (.valIterator structure)] - (loop [ret {}] - (if (.hasNext k-it) - (let [k (.next k-it) - v (.next v-it)] - (recur (assoc ret k (next-fn v)))) - ret - )))) - - #+cljs - cljs.core/PersistentArrayMap - #+cljs - (map-vals-transform [structure next-fn] - (map-vals-non-transient-transform structure {} next-fn) - ) - - #+clj clojure.lang.PersistentTreeMap #+cljs cljs.core/PersistentTreeMap - (map-vals-transform [structure next-fn] - (map-vals-non-transient-transform structure (empty structure) next-fn) - ) - - #+clj clojure.lang.PersistentHashMap #+cljs cljs.core/PersistentHashMap - (map-vals-transform [structure next-fn] - (persistent! - (reduce-kv - (fn [m k v] - (assoc! m k (next-fn v))) - (transient - #+clj clojure.lang.PersistentHashMap/EMPTY #+cljs cljs.core.PersistentHashMap.EMPTY - ) - structure - ))) - - #+clj Object #+cljs default - (map-vals-transform [structure next-fn] - (reduce-kv - (fn [m k v] - (assoc m k (next-fn v))) - (empty structure) - structure)) - ) - - -(deftype ValCollect []) - -(extend-protocol p/Collector - ValCollect - (collect-val [this structure] - structure)) - -(deftype PosNavigator [getter updater]) - -(extend-protocol p/Navigator - PosNavigator - (select* [this structure next-fn] - (if-not (fast-empty? structure) - (next-fn ((.-getter this) structure)) - NONE)) - (transform* [this structure next-fn] - (if (fast-empty? structure) - structure - ((.-updater this) structure next-fn)))) - -(defn srange-select [structure start end next-fn] - (next-fn (-> structure vec (subvec start end)))) - -(defn srange-transform [structure start end next-fn] - (let [structurev (vec structure) - newpart (next-fn (-> structurev (subvec start end))) - res (concat (subvec structurev 0 start) - newpart - (subvec structurev end (count structure)))] - (if (vector? structure) - (vec res) - res - ))) - -(defn- matching-indices [aseq p] - (keep-indexed (fn [i e] (if (p e) i)) aseq)) - -(defn matching-ranges [aseq p] - (first - (reduce - (fn [[ranges curr-start curr-last :as curr] i] - (cond - (nil? curr-start) - [ranges i i] - - (= i (inc curr-last)) - [ranges curr-start i] - - :else - [(conj ranges [curr-start (inc curr-last)]) i i] - )) - [[] nil nil] - (concat (matching-indices aseq p) [-1]) - ))) - -(extend-protocol p/Navigator - nil - (select* [this structure next-fn] - (next-fn structure)) - (transform* [this structure next-fn] - (next-fn structure) - )) - -(deftype TransientEndNavigator []) - -(extend-protocol p/Navigator - TransientEndNavigator - (select* [this structure next-fn] - (next-fn [])) - (transform* [this structure next-fn] - (let [res (next-fn [])] - (reduce conj! structure res)))) - -(defn extract-basic-filter-fn [path] - (cond (fn? path) +(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 + )))) - (and (coll? path) - (every? fn? path)) - (reduce - (fn [combined afn] - (fn [structure] - (and (combined structure) (afn structure)) - )) - path - ))) +;;TODO: continue from here - -(defn if-select [params params-idx vals structure next-fn then-tester then-s then-params else-s] - (let [test? (then-tester structure) - sel (if test? - then-s - else-s) - idx (if test? params-idx (+ params-idx then-params))] - (sel params - idx - vals - structure - next-fn - ))) - -(defn if-transform [params params-idx vals structure next-fn then-tester then-t then-params else-t] - (let [test? (then-tester structure) - tran (if test? - then-t - else-t) - idx (if test? params-idx (+ params-idx then-params))] - (tran params - idx - 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 filter-select [afn structure next-fn] - (if (afn structure) - (next-fn structure) - NONE)) - -(defn filter-transform [afn structure next-fn] - (if (afn structure) - (next-fn structure) - structure)) - -(defn compiled-selector [^com.rpl.specter.impl.CompiledPath path] - (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)] - (.-selector tfns))) - -(defn compiled-transformer [^com.rpl.specter.impl.CompiledPath path] - (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)] - (.-transformer tfns))) - -(defn params-needed-selector [^com.rpl.specter.impl.ParamsNeededPath path] - (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)] - (.-selector tfns))) - -(defn params-needed-transformer [^com.rpl.specter.impl.ParamsNeededPath path] - (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)] - (.-transformer tfns))) - (defrecord LayeredNav [underlying]) (defn layered-nav? [o] (instance? LayeredNav o)) @@ -1216,8 +685,7 @@ )) (defn- valid-navigator? [v] - (or (structure-path? v) - (satisfies? p/Collector v) + (or (satisfies? p/ImplicitNav v) (instance? CompiledPath v))) #+cljs @@ -1230,15 +698,14 @@ (def pred* (->ParamsNeededPath - (->TransformFunctions - RichPathExecutor - (fn [params params-idx vals structure next-fn] + (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 ))) - (fn [params params-idx vals structure next-fn] + (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) @@ -1249,27 +716,28 @@ (def rich-compiled-path-proxy (->ParamsNeededPath - (->TransformFunctions - RichPathExecutor - (fn [params params-idx vals structure next-fn] + (reify RichNavigator + (rich-select* [this params params-idx vals structure next-fn] (let [apath ^CompiledPath (aget ^objects params params-idx) - transform-fns ^TransformFunctions (.-transform-fns apath) - selector (.-selector transform-fns)] - (selector - (.-params apath) - (.-params-idx apath) + 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)) ))) - (fn [params params-idx vals structure next-fn] + (rich-transform* [this params params-idx vals structure next-fn] (let [apath ^CompiledPath (aget ^objects params params-idx) - transform-fns ^TransformFunctions (.-transform-fns apath) - transformer (.-transformer transform-fns)] - (transformer - (.-params apath) - (.-params-idx apath) + pnav ^ParameterizedRichNav (.-nav apath) + nav (.-rich-nav pnav)] + (exec-rich-transform* + nav + (.-params pnav) + (.-params-idx pnav) vals structure (fn [_ _ vals-next structure-next] @@ -1518,12 +986,13 @@ expected-params (num-needed-params protpath)] (doseq [[atype apath] extensions] (let [p (comp-paths* apath) - rp (assoc p :transform-fns (coerce-tfns-rich (:transform-fns p))) - needed-params (num-needed-params rp)] + 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 [_] rp)}) + (extend atype protpath-prot {m (fn [_] rich-nav)}) )))) (defn parameterize-path [apath params params-idx] @@ -1532,13 +1001,8 @@ (bind-params* apath params params-idx) )) -(defn extract-rich-tfns [apath] - (let [tfns (coerce-tfns-rich (:transform-fns apath))] - [(:selector tfns) (:transformer tfns)] - )) - (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) - ))) \ No newline at end of file + ))) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index 7fb4f46..f2be67b 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -23,18 +23,16 @@ [[_ t-structure-sym t-next-fn-sym] & transform-body]] (determine-params-impls impl1 impl2)] (if (= 0 num-params) - `(i/no-params-compiled-path - (i/->TransformFunctions - i/LeanPathExecutor - (fn [~s-structure-sym ~s-next-fn-sym] + `(i/lean-compiled-path + (reify Navigator + (~'select* [this# ~s-structure-sym ~s-next-fn-sym] ~@select-body) - (fn [~t-structure-sym ~t-next-fn-sym] + (~'transform* [this# ~t-structure-sym ~t-next-fn-sym] ~@transform-body) )) `(i/->ParamsNeededPath - (i/->TransformFunctions - i/RichPathExecutor - (fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~s-structure-sym next-fn#] + (reify i/RichNavigator + (~'rich-select* [this# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~s-structure-sym next-fn#] (let [~s-next-fn-sym (fn [structure#] (next-fn# ~PARAMS-SYM @@ -44,7 +42,7 @@ ~@bindings] ~@select-body )) - (fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~t-structure-sym next-fn#] + (~'rich-transform* [this# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~t-structure-sym next-fn#] (let [~t-next-fn-sym (fn [structure#] (next-fn# ~PARAMS-SYM @@ -68,10 +66,12 @@ ~structure-sym) ))] (i/->ParamsNeededPath - (i/->TransformFunctions - i/RichPathExecutor - collector# - collector# ) + (reify RichNavigator + (~'rich-select* [this# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~structure-sym next-fn#] + (collector# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~structure-sym next-fn#)) + (~'rich-transform* [this# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~structure-sym next-fn#] + (collector# ~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~structure-sym next-fn#)) + ) ~num-params ))) @@ -139,18 +139,17 @@ t-pidx-sym (second t-params) ] `(let [num-params# ~num-params - tfns# (i/->TransformFunctions - i/RichPathExecutor - (fn ~s-params - (let [~s-next-fn-sym (i/mk-jump-next-fn ~s-next-fn-sym ~s-pidx-sym num-params#)] - ~@s-body)) - (fn ~t-params - (let [~t-next-fn-sym (i/mk-jump-next-fn ~t-next-fn-sym ~t-pidx-sym num-params#)] - ~@t-body)) - )] + nav# (reify i/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-compiled-path tfns#) - (i/->ParamsNeededPath tfns# num-params#) + (i/no-params-rich-compiled-path nav#) + (i/->ParamsNeededPath nav# num-params#) )))) (defmacro paramsfn [params [structure-sym] & impl] @@ -239,6 +238,7 @@ (defn- protpath-sym [name] (-> name (str "-prot") symbol)) + (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 @@ -268,43 +268,29 @@ ] `(do (defprotocol ~prot-name (~m [structure#])) - (def ~name - (if (= ~num-params 0) - (i/no-params-compiled-path - (i/->TransformFunctions - i/RichPathExecutor - (fn ~rargs - (let [path# ~retrieve - selector# (i/compiled-selector path#)] - (selector# ~@rargs) - )) - (fn ~rargs - (let [path# ~retrieve - transformer# (i/compiled-transformer path#)] - (transformer# ~@rargs) - )))) - (i/->ParamsNeededPath - (i/->TransformFunctions - i/RichPathExecutor - (fn ~rargs - (let [path# ~retrieve - selector# (i/params-needed-selector path#)] - (selector# ~@rargs) - )) - (fn ~rargs - (let [path# ~retrieve - transformer# (i/params-needed-transformer path#)] - (transformer# ~@rargs) - ))) - ~num-params - ) - )))))) + (let [nav# (reify i/RichNavigator + (~'rich-select* [this# ~@rargs] + (let [inav# ~retrieve] + (i/exec-rich-select* inav# ~@rargs) + )) + (~'rich-transform* [this# ~@rargs] + (let [inav# ~retrieve] + (i/exec-rich-transform* inav# ~@rargs) + )))] + (def ~name + (if (= ~num-params 0) + (i/no-params-rich-compiled-path nav#) + (i/->ParamsNeededPath nav# ~num-params) + ))))))) + + (defn ^:no-doc declared-name [name] (vary-meta (symbol (str name "-declared")) assoc :no-doc true)) + (defmacro declarepath ([name] `(declarepath ~name [])) @@ -317,32 +303,32 @@ (declare ~declared) (def ~name (if (= ~num-params 0) - (i/no-params-compiled-path - (i/->TransformFunctions - i/RichPathExecutor - (fn ~rargs - (let [selector# (i/compiled-selector ~declared)] - (selector# ~@rargs) + (i/no-params-rich-compiled-path + (reify i/RichNavigator + (~'rich-select* [this# ~@rargs] + (let [inav# (i/compiled-path-rich-nav ~declared)] + (i/exec-rich-select* inav# ~@rargs) )) - (fn ~rargs - (let [transformer# (i/compiled-transformer ~declared)] - (transformer# ~@rargs) + (~'rich-transform* [this# ~@rargs] + (let [inav# (i/compiled-path-rich-nav ~declared)] + (i/exec-rich-transform* inav# ~@rargs) )))) (i/->ParamsNeededPath - (i/->TransformFunctions - i/RichPathExecutor - (fn ~rargs - (let [selector# (i/params-needed-selector ~declared)] - (selector# ~@rargs) - )) - (fn ~rargs - (let [transformer# (i/params-needed-transformer ~declared)] - (transformer# ~@rargs) - ))) + (reify i/RichNavigator + (~'rich-select* [this# ~@rargs] + (let [inav# (i/params-needed-nav ~declared)] + (i/exec-rich-select* inav# ~@rargs) + )) + (~'rich-transform* [this# ~@rargs] + (let [inav# (i/params-needed-nav ~declared)] + (i/exec-rich-transform* inav# ~@rargs) + ))) ~num-params ) )))))) +;;TODO: continue refactoring from here ******* +;;TODO: need to convert to use first-class navigators (defmacro providepath [name apath] `(let [comped# (i/comp-paths* ~apath) expected-params# (i/num-needed-params ~name) @@ -351,10 +337,8 @@ (i/throw-illegal "Invalid number of params in provided path, expected " expected-params# " but got " needed-params#)) (def ~(declared-name name) - (update-in comped# - [:transform-fns] - i/coerce-tfns-rich) - ))) + (i/coerce-compiled->rich-nav comped#) + ))) (defmacro extend-protocolpath "Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]." diff --git a/src/clj/com/rpl/specter/navs.cljx b/src/clj/com/rpl/specter/navs.cljx new file mode 100644 index 0000000..6062271 --- /dev/null +++ b/src/clj/com/rpl/specter/navs.cljx @@ -0,0 +1,500 @@ +(ns com.rpl.specter.navs + (:use [com.rpl.specter macros] + [com.rpl.specter.util-macros :only [doseqres]]) + (:require [com.rpl.specter [impl :as i]] + [clojure [walk :as walk]]) + ) + +(defn- append [coll elem] + (-> coll vec (conj elem))) + +(defn not-selected?* + [compiled-path structure] + (->> structure + (i/compiled-select-any* compiled-path) + (identical? NONE))) + +(defn selected?* + [compiled-path structure] + (not (not-selected?* compiled-path structure))) + +(defn walk-select [pred continue-fn structure] + (let [ret (i/mutable-cell NONE) + walker (fn this [structure] + (if (pred structure) + (let [r (continue-fn structure)] + (if-not (identical? r NONE) + (set-cell! ret r)) + r + ) + (walk/walk this identity structure) + ))] + (walker structure) + (get-cell ret) + )) + +(defn key-select [akey structure next-fn] + (next-fn (get structure akey))) + +(defn key-transform [akey structure next-fn] + (assoc structure akey (next-fn (get structure akey)) + )) + +(defn all-select [structure next-fn] + (doseqres NONE [e structure] + (next-fn e))) + +#+cljs +(defn queue? [coll] + (= (type coll) (type #queue []))) + +#+clj +(defn queue? [coll] + (instance? clojure.lang.PersistentQueue coll)) + +(defprotocol AllTransformProtocol + (all-transform [structure next-fn])) + +(defn- non-transient-map-all-transform [structure next-fn empty-map] + (reduce-kv + (fn [m k v] + (let [[newk newv] (next-fn [k v])] + (assoc m newk newv) + )) + empty-map + structure + )) + +(extend-protocol AllTransformProtocol + nil + (all-transform [structure next-fn] + nil + ) + + ;; in cljs they're PersistentVector so don't need a special case + #+clj clojure.lang.MapEntry + #+clj + (all-transform [structure next-fn] + (let [newk (next-fn (key structure)) + newv (next-fn (val structure))] + (clojure.lang.MapEntry. newk newv) + )) + + #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector + (all-transform [structure next-fn] + (mapv next-fn structure)) + + #+clj + clojure.lang.PersistentArrayMap + #+clj + (all-transform [structure next-fn] + (let [k-it (.keyIterator structure) + v-it (.valIterator structure)] + (loop [ret {}] + (if (.hasNext k-it) + (let [k (.next k-it) + v (.next v-it) + [newk newv] (next-fn [k v])] + (recur (assoc ret newk newv))) + ret + )))) + + #+cljs + cljs.core/PersistentArrayMap + #+cljs + (all-transform [structure next-fn] + (non-transient-map-all-transform structure next-fn {}) + ) + + #+clj clojure.lang.PersistentTreeMap #+cljs cljs.core/PersistentTreeMap + (all-transform [structure next-fn] + (non-transient-map-all-transform structure next-fn (empty structure)) + ) + + #+clj clojure.lang.PersistentHashMap #+cljs cljs.core/PersistentHashMap + (all-transform [structure next-fn] + (persistent! + (reduce-kv + (fn [m k v] + (let [[newk newv] (next-fn [k v])] + (assoc! m newk newv) + )) + (transient + #+clj clojure.lang.PersistentHashMap/EMPTY #+cljs cljs.core.PersistentHashMap.EMPTY + ) + structure + ))) + + + #+clj + Object + #+clj + (all-transform [structure next-fn] + (let [empty-structure (empty structure)] + (cond (and (list? empty-structure) (not (queue? empty-structure))) + ;; this is done to maintain order, otherwise lists get reversed + (doall (map next-fn structure)) + + (map? structure) + ;; reduce-kv is much faster than doing r/map through call to (into ...) + (reduce-kv + (fn [m k v] + (let [[newk newv] (next-fn [k v])] + (assoc m newk newv) + )) + empty-structure + structure + ) + + :else + (->> structure (r/map next-fn) (into empty-structure)) + ))) + + #+cljs + default + #+cljs + (all-transform [structure next-fn] + (let [empty-structure (empty structure)] + (if (and (list? empty-structure) (not (queue? empty-structure))) + ;; this is done to maintain order, otherwise lists get reversed + (doall (map next-fn structure)) + (into empty-structure (map #(next-fn %)) structure) + ))) + ) + +(defprotocol MapValsTransformProtocol + (map-vals-transform [structure next-fn])) + +(defn map-vals-non-transient-transform [structure empty-map next-fn] + (reduce-kv + (fn [m k v] + (assoc m k (next-fn v))) + empty-map + structure)) + +(extend-protocol MapValsTransformProtocol + nil + (map-vals-transform [structure next-fn] + nil + ) + + #+clj + clojure.lang.PersistentArrayMap + #+clj + (map-vals-transform [structure next-fn] + (let [k-it (.keyIterator structure) + v-it (.valIterator structure)] + (loop [ret {}] + (if (.hasNext k-it) + (let [k (.next k-it) + v (.next v-it)] + (recur (assoc ret k (next-fn v)))) + ret + )))) + + #+cljs + cljs.core/PersistentArrayMap + #+cljs + (map-vals-transform [structure next-fn] + (map-vals-non-transient-transform structure {} next-fn) + ) + + #+clj clojure.lang.PersistentTreeMap #+cljs cljs.core/PersistentTreeMap + (map-vals-transform [structure next-fn] + (map-vals-non-transient-transform structure (empty structure) next-fn) + ) + + #+clj clojure.lang.PersistentHashMap #+cljs cljs.core/PersistentHashMap + (map-vals-transform [structure next-fn] + (persistent! + (reduce-kv + (fn [m k v] + (assoc! m k (next-fn v))) + (transient + #+clj clojure.lang.PersistentHashMap/EMPTY #+cljs cljs.core.PersistentHashMap.EMPTY + ) + structure + ))) + + #+clj Object #+cljs default + (map-vals-transform [structure next-fn] + (reduce-kv + (fn [m k v] + (assoc m k (next-fn v))) + (empty structure) + structure)) + ) + +(defn srange-select [structure start end next-fn] + (next-fn (-> structure vec (subvec start end)))) + +(defn srange-transform [structure start end next-fn] + (let [structurev (vec structure) + newpart (next-fn (-> structurev (subvec start end))) + res (concat (subvec structurev 0 start) + newpart + (subvec structurev end (count structure)))] + (if (vector? structure) + (vec res) + res + ))) + +(defn- matching-indices [aseq p] + (keep-indexed (fn [i e] (if (p e) i)) aseq)) + +(defn matching-ranges [aseq p] + (first + (reduce + (fn [[ranges curr-start curr-last :as curr] i] + (cond + (nil? curr-start) + [ranges i i] + + (= i (inc curr-last)) + [ranges curr-start i] + + :else + [(conj ranges [curr-start (inc curr-last)]) i i] + )) + [[] nil nil] + (concat (matching-indices aseq p) [-1]) + ))) + +(defn extract-basic-filter-fn [path] + (cond (fn? path) + path + + (and (coll? path) + (every? fn? path)) + (reduce + (fn [combined afn] + (fn [structure] + (and (combined structure) (afn structure)) + )) + path + ))) + + + +(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-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 terminal* [params params-idx vals structure] + (let [afn (aget ^objects params params-idx)] + (if (identical? vals []) + (afn structure) + (apply afn (conj vals structure))) + )) + +(defn filter-select [afn structure next-fn] + (if (afn structure) + (next-fn structure) + NONE)) + +(defn filter-transform [afn structure next-fn] + (if (afn structure) + (next-fn structure) + structure)) + + + +(defnav PosNavigator [getter updater] + (select* [this structure next-fn] + (if-not (i/fast-empty? structure) + (next-fn (getter structure)) + NONE)) + (transform* [this structure next-fn] + (if (i/fast-empty? structure) + structure + (updater structure next-fn)))) + +(defprotocol AddExtremes + (append-all [structure elements]) + (prepend-all [structure elements])) + +(extend-protocol AddExtremes + nil + (append-all [_ elements] + elements) + (prepend-all [_ elements] + elements) + + #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector + (append-all [structure elements] + (reduce conj structure elements)) + (prepend-all [structure elements] + (let [ret (transient [])] + (as-> ret <> + (reduce conj! <> elements) + (reduce conj! <> structure) + (persistent! <>) + ))) + + #+clj Object #+cljs default + (append-all [structure elements] + (concat structure elements)) + (prepend-all [structure elements] + (concat elements structure)) + ) + + +(defprotocol UpdateExtremes + (update-first [s afn]) + (update-last [s afn])) + +(defprotocol GetExtremes + (get-first [s]) + (get-last [s])) + +(defprotocol FastEmpty + (fast-empty? [s])) + +(defn- update-first-list [l afn] + (cons (afn (first l)) (rest l))) + +(defn- update-last-list [l afn] + (append (butlast l) (afn (last l)))) + +#+clj +(defn vec-count [^clojure.lang.IPersistentVector v] + (.length v)) + +#+cljs +(defn vec-count [v] + (count v)) + +#+clj +(defn transient-vec-count [^clojure.lang.ITransientVector v] + (.count v)) + +#+cljs +(defn transient-vec-count [v] + (count v)) + +(extend-protocol UpdateExtremes + #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector + (update-first [v afn] + (let [val (nth v 0)] + (assoc v 0 (afn val)) + )) + (update-last [v afn] + ;; type-hinting vec-count to ^int caused weird errors with case + (let [c (int (vec-count v))] + (case c + 1 (let [[e] v] [(afn e)]) + 2 (let [[e1 e2] v] [e1 (afn e2)]) + (let [i (dec c)] + (assoc v i (afn (nth v i))) + )))) + #+clj Object #+cljs default + (update-first [l val] + (update-first-list l val)) + (update-last [l val] + (update-last-list l val) + )) + +(extend-protocol GetExtremes + #+clj clojure.lang.IPersistentVector #+cljs cljs.core/PersistentVector + (get-first [v] + (nth v 0)) + (get-last [v] + (peek v)) + #+clj Object #+cljs default + (get-first [s] + (first s)) + (get-last [s] + (last s) + )) + + +(extend-protocol FastEmpty + nil + (fast-empty? [_] true) + + #+clj clojure.lang.IPersistentVector #+cljs cljs.core/PersistentVector + (fast-empty? [v] + (= 0 (vec-count v))) + #+clj clojure.lang.ITransientVector #+cljs cljs.core/TransientVector + (fast-empty? [v] + (= 0 (transient-vec-count v))) + #+clj Object #+cljs default + (fast-empty? [s] + (empty? s)) + ) + +(defn walk-until [pred on-match-fn structure] + (if (pred structure) + (on-match-fn structure) + (walk/walk (partial walk-until pred on-match-fn) identity structure) + )) + +(defn fn-invocation? [f] + (or #+clj (instance? clojure.lang.Cons f) + #+clj (instance? clojure.lang.LazySeq f) + #+cljs (instance? cljs.core.LazySeq f) + (list? f))) + +(defn codewalk-until [pred on-match-fn structure] + (if (pred structure) + (on-match-fn structure) + (let [ret (walk/walk (partial codewalk-until pred on-match-fn) identity structure)] + (if (and (fn-invocation? structure) (fn-invocation? ret)) + (with-meta ret (meta structure)) + ret + )))) + + +(def collected?* + (i/->ParamsNeededPath + (reify i/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 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.cljx b/src/clj/com/rpl/specter/protocols.cljx index 8ccbfc8..f734e3a 100644 --- a/src/clj/com/rpl/specter/protocols.cljx +++ b/src/clj/com/rpl/specter/protocols.cljx @@ -1,6 +1,8 @@ (ns com.rpl.specter.protocols) (defprotocol Navigator + "Do not use this protocol directly. All navigators must be created using + com.rpl.specter.macros namespace." (select* [this structure next-fn] "An implementation of `select*` must call `next-fn` on each subvalue of `structure`. The result of `select*` is specified @@ -17,4 +19,9 @@ )) (defprotocol Collector + "Do not use this protocol directly. All navigators must be created using + com.rpl.specter.macros namespace." (collect-val [this structure])) + +(defprotocol ImplicitNav + (implicit-nav [obj])) diff --git a/src/clj/com/rpl/specter/transients.cljx b/src/clj/com/rpl/specter/transients.cljx index 7bd1e55..b7f7edf 100644 --- a/src/clj/com/rpl/specter/transients.cljx +++ b/src/clj/com/rpl/specter/transients.cljx @@ -8,7 +8,7 @@ [com.rpl.specter.macros :only [defnav defpathedfn]]) - (:require [com.rpl.specter.impl :as i] + (:require [com.rpl.specter [navs :as n]] [com.rpl.specter :refer [subselect selected?]])) (defnav @@ -21,9 +21,17 @@ (transform* [this structure next-fn] (assoc! structure key (next-fn (get structure key))))) -(def END! - "Navigates to an empty (persistent) vector at the end of a transient vector." - (i/comp-paths* (i/->TransientEndNavigator))) + + +(defnav + ^{:doc "Navigates to an empty (persistent) vector at the end of a transient vector."} + END! + [] + (select* [this structure next-fn] + (next-fn [])) + (transform* [this structure next-fn] + (let [res (next-fn [])] + (reduce conj! structure res)))) (defn- t-get-first [tv] @@ -31,7 +39,7 @@ (defn- t-get-last [tv] - (nth tv (dec (i/transient-vec-count tv)))) + (nth tv (dec (n/transient-vec-count tv)))) (defn- t-update-first [tv next-fn] @@ -39,16 +47,17 @@ (defn- t-update-last [tv next-fn] - (let [i (dec (i/transient-vec-count tv))] + (let [i (dec (n/transient-vec-count tv))] (assoc! tv i (next-fn (nth tv i))))) + (def FIRST! "Navigates to the first element of a transient vector." - (i/->PosNavigator t-get-first t-update-first)) + (n/PosNavigator t-get-first t-update-first)) (def LAST! "Navigates to the last element of a transient vector." - (i/->PosNavigator t-get-last t-update-last)) + (n/PosNavigator t-get-last t-update-last)) #+clj (defn- select-keys-from-transient-map