initial rewriting

This commit is contained in:
Nathan Marz 2016-08-29 22:01:17 -04:00
parent a7b743c75d
commit f511cd4fca
7 changed files with 330 additions and 1034 deletions

View file

@ -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)))

View file

@ -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)))

View file

@ -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)))

View file

@ -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)}))))))

View file

@ -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)))

View file

@ -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)))))

View file

@ -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."))