initial impl working for basic use cases

This commit is contained in:
Nathan Marz 2016-05-20 16:57:53 -04:00
parent ac5efb2eb9
commit d3a462aa06
4 changed files with 230 additions and 17 deletions

View file

@ -7,9 +7,10 @@
:test-paths ["test", "target/test-classes"] :test-paths ["test", "target/test-classes"]
:jar-exclusions [#"\.cljx"] :jar-exclusions [#"\.cljx"]
:auto-clean false :auto-clean false
:dependencies [[org.clojure/tools.macro "0.1.2"]]
:profiles {:provided {:dependencies :profiles {:provided {:dependencies
[[org.clojure/clojure "1.6.0"] [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "0.0-3211"]]} [org.clojure/clojurescript "1.7.122"]]}
:dev {:dependencies :dev {:dependencies
[[org.clojure/test.check "0.7.0"]] [[org.clojure/test.check "0.7.0"]]
:plugins :plugins

View file

@ -7,6 +7,7 @@
fixed-pathed-path fixed-pathed-path
defcollector defcollector
defpath defpath
defpathedfn
]] ]]
) )
(:use [com.rpl.specter.protocols :only [StructurePath]] (:use [com.rpl.specter.protocols :only [StructurePath]]
@ -15,7 +16,8 @@
variable-pathed-path variable-pathed-path
fixed-pathed-path fixed-pathed-path
defcollector defcollector
defpath]] defpath
defpathedfn]]
) )
(:require [com.rpl.specter.impl :as i] (:require [com.rpl.specter.impl :as i]
[clojure.set :as set]) [clojure.set :as set])
@ -248,7 +250,7 @@
(transform* [this structure next-fn] (transform* [this structure next-fn]
(i/codewalk-until afn next-fn structure))) (i/codewalk-until afn next-fn structure)))
(defn subselect (defpathedfn subselect
"Navigates to a sequence that contains the results of (select ...), "Navigates to a sequence that contains the results of (select ...),
but is a view to the original structure that can be transformed. but is a view to the original structure that can be transformed.
@ -320,7 +322,7 @@
(swap! structure next-fn) (swap! structure next-fn)
structure))) structure)))
(defn selected? (defpathedfn selected?
"Filters the current value based on whether a path finds anything. "Filters the current value based on whether a path finds anything.
e.g. (selected? :vals ALL even?) keeps the current element only if an e.g. (selected? :vals ALL even?) keeps the current element only if an
even number exists for the :vals key. even number exists for the :vals key.
@ -341,7 +343,7 @@
structure structure
next-fn)))) next-fn))))
(defn not-selected? [& path] (defpathedfn not-selected? [& path]
(fixed-pathed-path [late path] (fixed-pathed-path [late path]
(select* [this structure next-fn] (select* [this structure next-fn]
(i/filter-select (i/filter-select
@ -354,7 +356,7 @@
structure structure
next-fn)))) next-fn))))
(defn filterer (defpathedfn filterer
"Navigates to a view of the current sequence that only contains elements that "Navigates to a view of the current sequence that only contains elements that
match the given path. An element matches the selector path if calling select match the given path. An element matches the selector path if calling select
on that element with the path yields anything other than an empty sequence. on that element with the path yields anything other than an empty sequence.
@ -365,7 +367,7 @@
[& path] [& path]
(subselect ALL (selected? path))) (subselect ALL (selected? path)))
(defn transformed (defpathedfn transformed
"Navigates to a view of the current value by transforming it with the "Navigates to a view of the current value by transforming it with the
specified path and update-fn. specified path and update-fn.
@ -425,13 +427,13 @@
(def NIL->LIST (nil->val '())) (def NIL->LIST (nil->val '()))
(def NIL->VECTOR (nil->val [])) (def NIL->VECTOR (nil->val []))
(defn collect [& path] (defpathedfn collect [& path]
(pathed-collector [late path] (pathed-collector [late path]
(collect-val [this structure] (collect-val [this structure]
(compiled-select late structure) (compiled-select late structure)
))) )))
(defn collect-one [& path] (defpathedfn collect-one [& path]
(pathed-collector [late path] (pathed-collector [late path]
(collect-val [this structure] (collect-val [this structure]
(compiled-select-one late structure) (compiled-select-one late structure)
@ -450,7 +452,7 @@
(collect-val [this structure] (collect-val [this structure]
val )) val ))
(defn cond-path (defpathedfn cond-path
"Takes in alternating cond-path path cond-path path... "Takes in alternating cond-path path cond-path path...
Tests the structure if selecting with cond-path returns anything. Tests the structure if selecting with cond-path returns anything.
If so, it uses the following path for this portion of the navigation. If so, it uses the following path for this portion of the navigation.
@ -473,13 +475,13 @@
structure structure
)))) ))))
(defn if-path (defpathedfn if-path
"Like cond-path, but with if semantics." "Like cond-path, but with if semantics."
([cond-p if-path] (cond-path cond-p if-path)) ([cond-p if-path] (cond-path cond-p if-path))
([cond-p if-path else-path] ([cond-p if-path else-path]
(cond-path cond-p if-path nil else-path))) (cond-path cond-p if-path nil else-path)))
(defn multi-path (defpathedfn multi-path
"A path that branches on multiple paths. For updates, "A path that branches on multiple paths. For updates,
applies updates to the paths in order." applies updates to the paths in order."
[& paths] [& paths]
@ -498,13 +500,13 @@
compiled-paths compiled-paths
)))) ))))
(defn stay-then-continue (defpathedfn stay-then-continue
"Navigates to the current element and then navigates via the provided path. "Navigates to the current element and then navigates via the provided path.
This can be used to implement pre-order traversal." This can be used to implement pre-order traversal."
[& path] [& path]
(multi-path STAY path)) (multi-path STAY path))
(defn continue-then-stay (defpathedfn continue-then-stay
"Navigates to the provided path and then to the current element. This can be used "Navigates to the provided path and then to the current element. This can be used
to implement post-order traversal." to implement post-order traversal."
[& path] [& path]

View file

@ -429,7 +429,7 @@
(walk/walk (partial walk-until pred on-match-fn) identity structure) (walk/walk (partial walk-until pred on-match-fn) identity structure)
)) ))
(defn- fn-invocation? [f] (defn fn-invocation? [f]
(or (instance? clojure.lang.Cons f) (or (instance? clojure.lang.Cons f)
(instance? clojure.lang.LazySeq f) (instance? clojure.lang.LazySeq f)
(list? f))) (list? f)))
@ -615,6 +615,149 @@
(.-transformer tfns))) (.-transformer tfns)))
(defrecord LocalSym
[val sym])
(defrecord VarUse
[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
])
(def CACHE
#+clj (java.util.concurrent.ConcurrentHashMap.)
#+cljs (atom {})
)
#+clj
(defn add-cache! [k v]
(.put ^java.util.concurrent.ConcurrentHashMap CACHE k v))
#+clj
(defn get-cache [k]
(.get ^java.util.concurrent.ConcurrentHashMap CACHE k))
#+cljs
(defn add-cache! [k v]
(swap! CACHE (fn [m] (assoc m k v))))
#+cljs
(defn get-cache [k]
(get @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)))
(defn magic-fail! [failed-atom]
(reset! failed-atom true)
nil)
(defn- magic-precompilation* [p params-atom failed-atom]
(cond
(vector? p)
(mapv
#(magic-precompilation* % params-atom failed-atom)
p)
(instance? LocalSym p)
(magic-fail! failed-atom)
(instance? VarUse p)
(let [v (:var p)
vv (var-get v)]
(if (and (-> v meta :dynamic not)
(valid-navigator? vv))
vv
(magic-fail! failed-atom)
))
(instance? SpecialFormUse p)
(magic-fail! failed-atom)
(instance? FnInvocation p)
(let [op (:op p)
ps (:params p)]
(if (instance? VarUse op)
(let [v (:var op)
vv (var-get v)]
(if (-> v meta :dynamic)
(magic-fail! failed-atom)
(cond
(instance? ParamsNeededPath vv)
;;TODO: if all params are constants, then just bind the path right here
;;otherwise, add the params
(do
(swap! params-atom 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)
))
:else
(magic-fail! failed-atom)
)))
(magic-fail! failed-atom)
))
:else
(magic-fail! failed-atom)
))
(defn magic-precompilation [prepared-path used-locals]
(let [params-atom (atom [])
failed-atom (atom false)
path (magic-precompilation* prepared-path params-atom failed-atom)
]
(if @failed-atom
(->CachedPathInfo nil nil)
(let [precompiled (comp-paths* path)
params-code (mapv extract-original-code @params-atom)
array-sym (gensym "array")
params-maker
(if-not (empty? params-code)
(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
))))
]
;; TODO: error if precompiled is compiledpath and there are params or
;; precompiled is paramsneededpath and there are no params...
(->CachedPathInfo precompiled params-maker)
))
))
#+clj #+clj
(defn extend-protocolpath* [protpath protpath-prot extensions] (defn extend-protocolpath* [protpath protpath-prot extensions]

View file

@ -1,5 +1,7 @@
(ns com.rpl.specter.macros (ns com.rpl.specter.macros
(:require [com.rpl.specter.impl :as i]) (:require [com.rpl.specter.impl :as i]
[clojure.walk :as walk]
[clojure.tools.macro :as m])
) )
(defn gensyms [amt] (defn gensyms [amt]
@ -311,3 +313,68 @@
(defmacro extend-protocolpath [protpath & extensions] (defmacro extend-protocolpath [protpath & extensions]
`(i/extend-protocolpath* ~protpath ~(protpath-sym protpath) ~(vec extensions))) `(i/extend-protocolpath* ~protpath ~(protpath-sym protpath) ~(vec extensions)))
(defmacro defpathedfn [name & args]
(let [[n args] (m/name-with-attributes name args)]
`(def ~n (vary-meta (fn ~@args) assoc :pathedfn true))))
(defn ic-prepare-path [locals-set path]
(cond
(vector? path)
(mapv #(ic-prepare-path locals-set %) path)
(symbol? path)
(if (contains? locals-set path)
`(com.rpl.specter.impl/->LocalSym ~path (quote ~path))
`(com.rpl.specter.impl/->VarUse (var ~path) (quote ~path))
)
(i/fn-invocation? path)
(let [[op & params] path]
(if (special-symbol? op)
`(com.rpl.specter.impl/->SpecialFormUse ~path (quote ~path))
`(com.rpl.specter.impl/->FnInvocation
~(ic-prepare-path locals-set op)
~(mapv #(ic-prepare-path locals-set %) params)
(quote ~path)))
)
:else
path
))
;; still possible to mess this up with alter-var-root!
(defmacro ic! [& path] ; "inline cache"
(let [local-syms (-> &env keys set)
used-locals (vec (i/walk-select local-syms vector path))
prepared-path (ic-prepare-path local-syms (walk/macroexpand-all (vec path)))
;; TODO: will turning this into a keyword make it faster?
cache-id (str (java.util.UUID/randomUUID))
]
`(let [info# (i/get-cache ~cache-id)
^com.rpl.specter.impl.CachedPathInfo info#
(if info#
info#
(let [info# (i/magic-precompilation
~prepared-path
~(mapv (fn [e] `(quote ~e)) used-locals)
)]
(i/add-cache! ~cache-id info#)
info#
))
precompiled# (.-precompiled info#)
params-maker# (.-params-maker info#)]
(cond (nil? precompiled#)
~path
(and precompiled# (nil? params-maker#))
precompiled#
:else
(i/bind-params* precompiled# (params-maker# ~@used-locals) 0)
))
))