diff --git a/src/clj/com/rpl/specter.cljc b/src/clj/com/rpl/specter.cljc index d5458bf..0acb7fc 100644 --- a/src/clj/com/rpl/specter.cljc +++ b/src/clj/com/rpl/specter.cljc @@ -328,6 +328,7 @@ In a transform, that subset in the original set is changed to the new value of the subset."} subset + {:inline-next-fn true} [aset] (select* [this structure next-fn] (next-fn (set/intersection structure aset))) @@ -344,6 +345,7 @@ In a transform, that submap in the original map is changed to the new value of the submap."} submap + {:inline-next-fn true} [m-keys] (select* [this structure next-fn] (next-fn (select-keys structure m-keys))) @@ -371,7 +373,7 @@ (select* [this structure next-fn] (n/walk-select afn next-fn structure)) (transform* [this structure next-fn] - (n/codewalk-until afn next-fn structure))) + (i/codewalk-until afn next-fn structure))) (defpathedfn subselect "Navigates to a sequence that contains the results of (select ...), @@ -397,6 +399,7 @@ (defnav ^{:doc "Navigates to the specified key, navigating to nil if it does not exist."} keypath + {:inline-next-fn true} [key] (select* [this structure next-fn] (next-fn (get structure key))) @@ -407,6 +410,7 @@ (defnav ^{:doc "Navigates to the key only if it exists in the map."} must + {:inline-next-fn true} [k] (select* [this structure next-fn] (if (contains? structure k) @@ -421,6 +425,7 @@ (defnav ^{:doc "Navigates to result of running `afn` on the currently navigated value."} view + {:inline-next-fn true} [afn] (select* [this structure next-fn] (next-fn (afn structure))) @@ -433,6 +438,7 @@ transforms, the transformed value then has `unparse-fn` run on it to get the final value at this point."} parser + {:inline-next-fn true} [parse-fn unparse-fn] (select* [this structure next-fn] (next-fn (parse-fn structure))) @@ -518,6 +524,7 @@ ^{: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 + {:inline-next-fn true} [afn] (select* [this structure next-fn] (if (afn structure) (next-fn structure) NONE)) @@ -546,6 +553,7 @@ ^{:doc "Navigates to the provided val if the structure is nil. Otherwise it stays navigated at the structure."} nil->val + {:inline-next-fn true} [v] (select* [this structure next-fn] (next-fn (if (nil? structure) v structure))) diff --git a/src/clj/com/rpl/specter/impl.cljc b/src/clj/com/rpl/specter/impl.cljc index 3c99f55..a5479dd 100644 --- a/src/clj/com/rpl/specter/impl.cljc +++ b/src/clj/com/rpl/specter/impl.cljc @@ -9,6 +9,7 @@ (:require [com.rpl.specter.protocols :as p] [clojure.string :as s] + [clojure.walk :as walk] #?(:clj [com.rpl.specter.defhelpers :as dh]) #?(:clj [riddley.walk :as riddley])) @@ -652,6 +653,14 @@ #?(: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)))) + (defrecord LayeredNav [underlying]) (defn layered-nav? [o] (instance? LayeredNav o)) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index 35327e3..4ae626d 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -62,13 +62,13 @@ `(let [~@binding-fn-declarations] ~body))) -(defmacro ^:no-doc rich-nav-with-bindings [num-params-code bindings & impls] + +(defn- rich-nav-with-bindings-not-inlined [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 @@ -95,6 +95,44 @@ structure#))] ~@t-body))))))) +(defn inline-next-fn [body next-fn-sym extra-params] + (i/codewalk-until + #(and (i/fn-invocation? %) (= next-fn-sym (first %))) + (fn [code] + (let [code (map #(inline-next-fn % next-fn-sym extra-params) code)] + (concat [next-fn-sym] extra-params (rest code)))) + body)) + +(defn- rich-nav-with-bindings-inlined [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") + vals-sym (gensym "vals") + next-params-idx-sym (gensym "next-params-idx") + s-body (inline-next-fn s-body s-next-fn-sym [params-sym next-params-idx-sym vals-sym]) + t-body (inline-next-fn t-body t-next-fn-sym [params-sym next-params-idx-sym vals-sym])] + (operation-with-bindings + bindings + params-sym + params-idx-sym + (fn [binding-declarations] + `(reify RichNavigator + (~'rich-select* [this# ~params-sym ~params-idx-sym ~vals-sym ~s-structure-sym ~s-next-fn-sym] + (let [~@binding-declarations + ~next-params-idx-sym (+ ~params-idx-sym ~num-params-code)] + ~@s-body)) + + (~'rich-transform* [this# ~params-sym ~params-idx-sym ~vals-sym ~t-structure-sym ~t-next-fn-sym] + (let [~@binding-declarations + ~next-params-idx-sym (+ ~params-idx-sym ~num-params-code)] + ~@t-body))))))) + +(defmacro ^:no-doc rich-nav-with-bindings [opts num-params-code bindings & impls] + (if (:inline-next-fn opts) + (rich-nav-with-bindings-inlined num-params-code bindings impls) + (rich-nav-with-bindings-not-inlined num-params-code bindings impls))) (defmacro ^:no-doc collector-with-bindings [num-params-code bindings impl] @@ -130,21 +168,25 @@ 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] - (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) + [& impl] + (let [[opts params & impls] (if (map? (first impl)) + impl + (cons {} impl))] + (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 ~opts + ~(count params) + ~(delta-param-bindings params) + ~@impls) - ~(count params))}))) + ~(count params))})))) (defmacro collector @@ -214,7 +256,8 @@ (i/lean-compiled-path (lean-nav* ~@impls))) (i/->ParamsNeededPath - (rich-nav-with-bindings ~total-params-sym + (rich-nav-with-bindings {} + ~total-params-sym ~runtime-bindings ~@impls) diff --git a/src/clj/com/rpl/specter/navs.cljc b/src/clj/com/rpl/specter/navs.cljc index cbd734b..2ea2d0f 100644 --- a/src/clj/com/rpl/specter/navs.cljc +++ b/src/clj/com/rpl/specter/navs.cljc @@ -460,14 +460,6 @@ (walk/walk (partial walk-until pred on-match-fn) identity structure))) -(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 (i/fn-invocation? structure) (i/fn-invocation? ret)) - (with-meta ret (meta structure)) - ret)))) - (def DISPENSE* (i/no-params-rich-compiled-path