specter/src/clj/com/rpl/specter/impl.cljx

953 lines
28 KiB
Clojure

(ns com.rpl.specter.impl
#+cljs (:require-macros
[com.rpl.specter.prot-opt-invoke
:refer [mk-optimized-invocation]]
[com.rpl.specter.defhelpers :refer [define-ParamsNeededPath]]
)
(:use [com.rpl.specter.protocols :only
[select* transform* collect-val]])
(: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]
)
#+clj
(:import [com.rpl.specter Util])
)
(defn spy [e]
(println "SPY:")
(println (pr-str e))
e)
(defprotocol PathComposer
(comp-paths* [paths]))
(defn- smart-str* [o]
(if (coll? o)
(pr-str o)
(str o)))
(defn smart-str [& elems]
(apply str (map smart-str* elems)))
#+clj
(defmacro throw* [etype & args]
`(throw (new ~etype (smart-str ~@args))))
#+clj
(defmacro throw-illegal [& args]
`(throw* IllegalArgumentException ~@args))
#+cljs
(defn throw-illegal [& args]
(throw (js/Error. (apply str args)))
)
(defn benchmark [iters afn]
(time
(dotimes [_ iters]
(afn))))
(deftype ExecutorFunctions [type select-executor transform-executor])
(def RichPathExecutor
(->ExecutorFunctions
:richpath
(fn [params params-idx selector structure]
(selector params params-idx [] structure
(fn [_ _ vals structure]
(if-not (empty? vals) [(conj vals structure)] [structure]))))
(fn [params params-idx transformer transform-fn structure]
(transformer params params-idx [] structure
(fn [_ _ vals structure]
(if (empty? vals)
(transform-fn structure)
(apply transform-fn (conj vals structure))))))
))
(def LeanPathExecutor
(->ExecutorFunctions
:leanpath
(fn [params params-idx selector structure]
(selector structure (fn [structure] [structure])))
(fn [params params-idx transformer transform-fn structure]
(transformer structure transform-fn))
))
(defrecord TransformFunctions [executors selector transformer])
(defrecord CompiledPath [transform-fns params params-idx])
(defn compiled-path? [o]
(instance? CompiledPath o))
(defn no-params-compiled-path [transform-fns]
(->CompiledPath transform-fns nil 0))
(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 bind-params* [^ParamsNeededPath params-needed-path params idx]
(->CompiledPath
(.-transform-fns params-needed-path)
params
idx))
(defn- seq-contains? [aseq val]
(->> aseq
(filter (partial = val))
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)))
(defprotocol CoercePath
(coerce-path [this]))
(extend-protocol CoercePath
nil ; needs its own path because it doesn't count as an Object
(coerce-path [this]
(coerce-structure-path nil))
CompiledPath
(coerce-path [this]
this)
ParamsNeededPath
(coerce-path [this]
this)
#+clj java.util.List #+cljs cljs.core/PersistentVector
(coerce-path [this]
(comp-paths* this))
#+cljs cljs.core/IndexedSeq
#+cljs (coerce-path [this]
(coerce-path (vec this)))
#+cljs cljs.core/EmptyList
#+cljs (coerce-path [this]
(coerce-path (vec this)))
#+cljs cljs.core/List
#+cljs (coerce-path [this]
(coerce-path (vec this)))
#+cljs cljs.core/LazySeq
#+cljs (coerce-path [this]
(coerce-path (vec this)))
#+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))
)))
(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)
(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)
))))
(fn [curr next]
(fn [structure next-fn]
(curr structure (fn [structure] (next structure next-fn)))))
)]
(reduce (fn [^TransformFunctions curr ^TransformFunctions next]
(->TransformFunctions
exs
(combiner (.-selector curr) (.-selector next))
(combiner (.-transformer curr) (.-transformer next))
))
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 capture-params-internally [path]
(if-not (instance? CompiledPath path)
path
(let [params (:params path)
params-idx (:params-idx path)
selector (-> path :transform-fns :selector)
transformer (-> path :transform-fns :transformer)]
(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
(fn [_ _ vals-next structure-next]
(next-fn x-params x-params-idx vals-next structure-next)
)))
(fn [x-params x-params-idx vals structure next-fn]
(transformer params params-idx vals structure
(fn [_ _ vals-next structure-next]
(next-fn x-params x-params-idx vals-next structure-next)
))))
)))))
(extend-protocol PathComposer
nil
(comp-paths* [sp]
(coerce-path sp))
#+clj Object #+cljs default
(comp-paths* [sp]
(coerce-path sp))
#+clj java.util.List #+cljs cljs.core/PersistentVector
(comp-paths* [structure-paths]
(if (empty? structure-paths)
(coerce-path nil)
(let [coerced (->> structure-paths
(map coerce-path)
(map capture-params-internally))
combined (->> coerced
(map :transform-fns)
(partition-by extype)
(map combine-same-types)
)
result-tfn (if (= 1 (count combined))
(first combined)
(->> combined
(map coerce-tfns-rich)
combine-same-types)
)
needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)]
(if (empty? needs-params-paths)
(no-params-compiled-path result-tfn)
(->ParamsNeededPath
(coerce-tfns-rich result-tfn)
(->> needs-params-paths
(map :num-needed-params)
(reduce +))
))
))))
(defn num-needed-params [path]
(if (instance? CompiledPath path)
0
(:num-needed-params path)))
;; cell implementation idea taken from prismatic schema library
(defprotocol PMutableCell
#+clj (get_cell [cell])
(set_cell [cell x]))
(deftype MutableCell [^:volatile-mutable q]
PMutableCell
#+clj (get_cell [cell] q)
(set_cell [this x] (set! q x)))
(defn mutable-cell
([] (mutable-cell nil))
([init] (MutableCell. init)))
(defn set-cell! [cell val]
(set_cell cell val))
(defn get-cell [cell]
#+clj (get_cell cell) #+cljs (.-q cell)
)
(defn update-cell! [cell afn]
(let [ret (afn (get-cell cell))]
(set-cell! cell ret)
ret))
(defn- append [coll elem]
(-> coll vec (conj elem)))
(defprotocol SetExtremes
(set-first [s val])
(set-last [s val]))
(defn- set-first-list [l v]
(cons v (rest l)))
(defn- set-last-list [l v]
(append (butlast l) v))
(extend-protocol SetExtremes
#+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector
(set-first [v val]
(assoc v 0 val))
(set-last [v val]
(assoc v (-> v count dec) val))
#+clj Object #+cljs default
(set-first [l val]
(set-first-list l val))
(set-last [l val]
(set-last-list l val)
))
(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 (instance? clojure.lang.Cons f)
(instance? clojure.lang.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
))))
(defn- conj-all! [cell elems]
(set-cell! cell (concat (get-cell cell) elems)))
(defn compiled-select*
[^com.rpl.specter.impl.CompiledPath path structure]
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
((.-select-executor ex) (.-params path) (.-params-idx path) (.-selector tfns) structure)
))
(defn compiled-transform*
[^com.rpl.specter.impl.CompiledPath path transform-fn structure]
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
((.-transform-executor ex) (.-params path) (.-params-idx path) (.-transformer tfns) transform-fn structure)
))
(defn not-selected?*
[compiled-path structure]
(->> structure
(compiled-select* compiled-path)
empty?))
(defn selected?*
[compiled-path structure]
(not (not-selected?* compiled-path structure)))
;; returns vector of all results
(defn walk-select [pred continue-fn structure]
(let [ret (mutable-cell [])
walker (fn this [structure]
(if (pred structure)
(conj-all! ret (continue-fn structure))
(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))
))
#+clj
(defn all-select [structure next-fn]
(into [] (r/mapcat next-fn structure)))
#+cljs
(defn all-select [structure next-fn]
(into [] (mapcat #(next-fn %)) structure))
#+cljs
(defn queue? [coll]
(= (type coll) (type #queue [])))
#+clj
(defn queue? [coll]
(instance? clojure.lang.PersistentQueue coll))
#+clj
(defn 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)
(->> structure (r/map vec) (r/map next-fn) (into empty-structure))
:else
(->> structure (r/map next-fn) (into empty-structure))
)))
#+cljs
(defn 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)))
(deftype ValCollect [])
(extend-protocol p/Collector
ValCollect
(collect-val [this structure]
structure))
(deftype PosNavigator [getter setter])
(extend-protocol p/Navigator
PosNavigator
(select* [this structure next-fn]
(if-not (empty? structure)
(next-fn ((.-getter this) structure))))
(transform* [this structure next-fn]
(if (empty? structure)
structure
((.-setter this) structure (next-fn ((.-getter this) 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
)))
(extend-protocol p/Navigator
nil
(select* [this structure next-fn]
(next-fn structure))
(transform* [this structure next-fn]
(next-fn structure)
))
(defn retrieve-cond-selector [cond-paths structure]
(->> cond-paths
(partition 2)
(drop-while (fn [[c-selector _]]
(->> structure
(compiled-select* c-selector)
empty?)))
first
second
))
(defn filter-select [afn structure next-fn]
(if (afn structure)
(next-fn structure)))
(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))
(defn layered-nav-underlying [^LayeredNav ln]
(.-underlying ln))
(defn verify-layerable! [anav]
(if-not
(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])
(defrecord VarUse
[val var sym])
(defrecord SpecialFormUse
[val code])
(defrecord FnInvocation
;; op and params elems can be any of the above
[op params code])
(defrecord CachedPathInfo
[precompiled ; can be null
params-maker ; can be null
])
(defonce PATH-CACHE
#+clj (java.util.concurrent.ConcurrentHashMap.)
;;TODO: according to @dnolen, can forgo this for instead doing
;;inline defs at runtime
#+cljs (atom {})
)
(def MUST-CACHE-PATHS (mutable-cell false))
(defn must-cache-paths!
([] (must-cache-paths! true))
([v] (set-cell! MUST-CACHE-PATHS v)))
#+clj
(defn add-path-cache! [k v]
(.put ^java.util.concurrent.ConcurrentHashMap PATH-CACHE k v))
#+clj
(defn get-path-cache [^String k]
(.get ^java.util.concurrent.ConcurrentHashMap PATH-CACHE k))
#+cljs
(defn add-path-cache! [k v]
(swap! PATH-CACHE (fn [m] (assoc m k v))))
#+cljs
(defn get-path-cache [k]
(get @PATH-CACHE k))
(defn- extract-original-code [p]
(cond
(instance? LocalSym p) (:sym p)
(instance? VarUse p) (:sym p)
(instance? SpecialFormUse p) (:code p)
(instance? FnInvocation p) (:code p)
:else p
))
(defn- valid-navigator? [v]
(or (structure-path? v)
(satisfies? p/Collector 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)
))
(def pred*
(->ParamsNeededPath
(->TransformFunctions
RichPathExecutor
(fn [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)
)))
(fn [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
))
(def rich-compiled-path-proxy
(->ParamsNeededPath
(->TransformFunctions
RichPathExecutor
(fn [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)
vals
structure
(fn [_ _ vals-next structure-next]
(next-fn params params-idx vals-next structure-next))
)))
(fn [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)
vals
structure
(fn [_ _ vals-next structure-next]
(next-fn params params-idx vals-next structure-next))
))))
1
))
(defn- magic-precompilation* [p params-atom failed-atom]
(let [magic-fail! (fn [& reason]
(if (get-cell MUST-CACHE-PATHS)
(println "Failed to cache path:" (apply str reason)))
(reset! failed-atom true)
nil)]
(cond
(vector? p)
(mapv
#(magic-precompilation* % params-atom failed-atom)
p)
(instance? LocalSym p)
(magic-fail! "Local symbol " (:sym p) " where navigator expected")
(instance? VarUse p)
(let [v (:var p)
vv (:val p)]
(cond (-> v meta :dynamic) (magic-fail! "Var " (:sym p) " is dynamic")
(valid-navigator? vv) vv
:else (magic-fail! "Var " (:sym p) " is not a navigator")
))
(instance? SpecialFormUse p)
(if (->> p :code first (contains? #{'fn* 'fn}))
(do
(swap! params-atom conj (:code p))
pred*
)
(magic-fail! "Special form " (:code p) " where navigator expected")
)
(instance? FnInvocation p)
(let [op (:op p)
ps (:params p)]
(if (instance? VarUse op)
(let [v (:var op)
vv (:val op)]
(if (-> v meta :dynamic)
(magic-fail! "Var " (:sym op) " is dynamic")
(cond
(instance? ParamsNeededPath vv)
;;TODO: if all params are constants, then just bind the path right here
;;otherwise, add the params
;; - could extend this to see if it contains nested function calls which
;; are only on constants
(do
(swap! params-atom #(vec (concat % ps)))
vv
)
(and (fn? vv) (-> vv meta :pathedfn))
(let [subpath (mapv #(magic-precompilation* % params-atom failed-atom)
ps)]
(if @failed-atom
nil
(apply vv subpath)
))
(and (fn? vv) (-> vv meta :layerednav))
(do
(swap! params-atom conj (:code p))
rich-compiled-path-proxy
)
:else
(magic-fail! "Var " (:sym op) " must be either a parameterized "
"navigator or a higher order pathed constructor function")
)))
(magic-fail! "Code at " (extract-original-code p) " is in "
"function invocation position and must be either a parameterized "
"navigator or a higher order pathed constructor function"
)
))
:else
(cond (set? p)
(do (swap! params-atom conj p)
pred*)
(keyword? p)
p
;; in case anyone extends String for their own use case
(and (string? p) (valid-navigator? p))
p
:else
(magic-fail! "Code " p " is not a valid navigator or can't be factored")
)
)))
#+clj
(defn mk-params-maker [params-code possible-params-code used-locals]
(let [array-sym (gensym "array")]
(eval
`(fn [~@used-locals]
(let [~array-sym (fast-object-array ~(count params-code))]
~@(map-indexed
(fn [i c]
`(aset ~array-sym ~i ~c))
params-code
)
~array-sym
)))))
#+cljs
(defn mk-params-maker [params-code possible-params-code used-locals]
(let [indexed (->> possible-params-code
(map-indexed (comp vec reverse vector))
(into {}))]
;;TODO: may be more efficient as an array
(mapv (fn [c] (get indexed c)) params-code)))
;; possible-params-code is for cljs impl that can't use eval
(defn magic-precompilation [prepared-path used-locals possible-params-code]
(let [params-atom (atom [])
failed-atom (atom false)
path (magic-precompilation* prepared-path params-atom failed-atom)
]
(if @failed-atom
(if (get-cell MUST-CACHE-PATHS)
(throw-illegal "Failed to cache path")
(->CachedPathInfo nil nil))
(let [precompiled (comp-paths* path)
params-code (mapv extract-original-code @params-atom)
params-maker (if-not (empty? params-code)
(mk-params-maker params-code possible-params-code used-locals))
]
;; TODO: error if precompiled is compiledpath and there are params or
;; precompiled is paramsneededpath and there are no params...
(->CachedPathInfo precompiled params-maker)
))
))
(defn compiled-select-one* [path structure]
(let [res (compiled-select* path structure)]
(when (> (count res) 1)
(throw-illegal "More than one element found for params: " path structure))
(first res)
))
(defn compiled-select-one!* [path structure]
(let [res (compiled-select* path structure)]
(when (not= 1 (count res)) (throw-illegal "Expected exactly one element for params: " path structure))
(first res)
))
(defn compiled-select-first* [path structure]
(first (compiled-select* path structure)))
(defn compiled-setval* [path val structure]
(compiled-transform* path (fn [_] val) structure))
(defn compiled-replace-in*
[path transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
(let [state (mutable-cell nil)]
[(compiled-transform* path
(fn [& args]
(let [res (apply transform-fn args)]
(if res
(let [[ret user-ret] res]
(->> user-ret
(merge-fn (get-cell state))
(set-cell! state))
ret)
(last args)
)))
structure)
(get-cell state)]
))
#+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* apath)
rp (assoc p :transform-fns (coerce-tfns-rich (:transform-fns p)))
needed-params (num-needed-params rp)]
(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)})
))))