Welcome TrieCompiler

This commit is contained in:
Tommi Reiman 2019-02-28 09:54:44 +02:00
parent 778a8b97b9
commit 3aae55bd8c
4 changed files with 185 additions and 124 deletions

View file

@ -97,11 +97,16 @@
(defn linear-router (defn linear-router
"Creates a linear-router from resolved routes and optional "Creates a linear-router from resolved routes and optional
expanded options. See [[router]] for available options." expanded options. See [[router]] for available options, plus the following:
| key | description |
| -----------------------------|-------------|
| `:reitit.core/trie-compiler` | Optional trie-compiler."
([compiled-routes] ([compiled-routes]
(linear-router compiled-routes {})) (linear-router compiled-routes {}))
([compiled-routes opts] ([compiled-routes opts]
(let [names (impl/find-names compiled-routes opts) (let [compiler (::trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts)
[pl nl] (reduce [pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p) (let [{:keys [path-params] :as route} (impl/parse p)
@ -113,7 +118,8 @@
[[] {}] [[] {}]
compiled-routes) compiled-routes)
lookup (impl/fast-map nl) lookup (impl/fast-map nl)
matcher (trie/linear-matcher pl) matcher (trie/linear-matcher compiler pl)
match-by-path (trie/matcher matcher compiler)
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
(reify (reify
@ -129,7 +135,7 @@
(route-names [_] (route-names [_]
names) names)
(match-by-path [_ path] (match-by-path [_ path]
(if-let [match (trie/lookup matcher path)] (if-let [match (match-by-path path)]
(-> (:data match) (-> (:data match)
(assoc :path-params (:params match)) (assoc :path-params (:params match))
(assoc :path path)))) (assoc :path path))))
@ -186,11 +192,16 @@
(defn trie-router (defn trie-router
"Creates a special prefix-tree router from resolved routes and optional "Creates a special prefix-tree router from resolved routes and optional
expanded options. See [[router]] for available options." expanded options. See [[router]] for available options, plus the following:
| key | description |
| -----------------------------|-------------|
| `:reitit.core/trie-compiler` | Optional trie-compiler."
([compiled-routes] ([compiled-routes]
(trie-router compiled-routes {})) (trie-router compiled-routes {}))
([compiled-routes opts] ([compiled-routes opts]
(let [names (impl/find-names compiled-routes opts) (let [compiler (::trie-compiler opts (trie/compiler))
names (impl/find-names compiled-routes opts)
[pl nl] (reduce [pl nl] (reduce
(fn [[pl nl] [p {:keys [name] :as data} result]] (fn [[pl nl] [p {:keys [name] :as data} result]]
(let [{:keys [path-params] :as route} (impl/parse p) (let [{:keys [path-params] :as route} (impl/parse p)
@ -201,7 +212,8 @@
(if name (assoc nl name f) nl)])) (if name (assoc nl name f) nl)]))
[nil {}] [nil {}]
compiled-routes) compiled-routes)
pl (trie/compile pl) matcher (trie/compile pl compiler)
match-by-path (trie/matcher matcher compiler)
lookup (impl/fast-map nl) lookup (impl/fast-map nl)
routes (impl/uncompile-routes compiled-routes)] routes (impl/uncompile-routes compiled-routes)]
^{:type ::router} ^{:type ::router}
@ -218,7 +230,7 @@
(route-names [_] (route-names [_]
names) names)
(match-by-path [_ path] (match-by-path [_ path]
(if-let [match (trie/lookup pl path)] (if-let [match (match-by-path path)]
(-> (:data match) (-> (:data match)
(assoc :path-params (:params match)) (assoc :path-params (:params match))
(assoc :path path)))) (assoc :path path))))

View file

@ -19,6 +19,15 @@
(depth [this]) (depth [this])
(length [this])) (length [this]))
(defprotocol TrieCompiler
(data-matcher [this params data])
(static-matcher [this path matcher])
(wild-matcher [this key end matcher])
(catch-all-matcher [this key params data])
(linear-matcher [this matchers])
(prettify [this matcher])
(path-matcher [this matcher]))
(defn assoc-param [match k v] (defn assoc-param [match k v]
(let [params (:params match)] (let [params (:params match)]
(assoc match :params (assoc params k v)))) (assoc match :params (assoc params k v))))
@ -168,25 +177,35 @@
(update :children dissoc "")) (update :children dissoc ""))
node'))) node')))
#?(:cljs (defn decode [path start end percent?]
(defn decode [path start end percent?]
(let [param (subs path start end)] (let [param (subs path start end)]
(if percent? (js/decodeURIComponent param) param)))) (if percent?
#?(:cljs (js/decodeURIComponent param)
:clj (URLDecoder/decode
(if (.contains ^String param "+")
(.replace ^String param "+" "%2B")
param)
"UTF-8"))
param)))
(defn data-matcher [params data] ;;
#?(:clj (Trie/dataMatcher params data) ;; Compilers
:cljs (let [match (->Match params data)] ;;
(defn clojure-trie-compiler []
(reify
TrieCompiler
(data-matcher [_ params data]
(let [match (->Match params data)]
(reify Matcher (reify Matcher
(match [_ i max _] (match [_ i max _]
(if (= i max) (if (= i max)
match)) match))
(view [_] data) (view [_] data)
(depth [_] 1) (depth [_] 1)
(length [_]))))) (length [_]))))
(static-matcher [_ path matcher]
(defn static-matcher [path matcher] (let [size (count path)]
#?(:clj (Trie/staticMatcher ^String path ^Trie$Matcher matcher)
:cljs (let [size (count path)]
(reify Matcher (reify Matcher
(match [_ i max p] (match [_ i max p]
(if-not (< max (+ i size)) (if-not (< max (+ i size))
@ -197,11 +216,9 @@
(recur (inc j))))))) (recur (inc j)))))))
(view [_] [path (view matcher)]) (view [_] [path (view matcher)])
(depth [_] (inc (depth matcher))) (depth [_] (inc (depth matcher)))
(length [_] (count path)))))) (length [_] (count path)))))
(wild-matcher [_ key end matcher]
(defn wild-matcher [key end matcher] (reify Matcher
#?(:clj (Trie/wildMatcher key (if end (Character. end)) matcher)
:cljs (reify Matcher
(match [_ i max path] (match [_ i max path]
(if (and (< i max) (not= (get path i) end)) (if (and (< i max) (not= (get path i) end))
(loop [percent? false, j i] (loop [percent? false, j i]
@ -216,21 +233,17 @@
(recur percent? (inc j)))))))) (recur percent? (inc j))))))))
(view [_] [key (view matcher)]) (view [_] [key (view matcher)])
(depth [_] (inc (depth matcher))) (depth [_] (inc (depth matcher)))
(length [_])))) (length [_])))
(catch-all-matcher [_ key params data]
(defn catch-all-matcher [key params data] (let [match (->Match params data)]
#?(:clj (Trie/catchAllMatcher key params data)
:cljs (let [match (->Match params data)]
(reify Matcher (reify Matcher
(match [_ i max path] (match [_ i max path]
(if (< i max) (assoc-param match key (decode path i max true)))) (if (< i max) (assoc-param match key (decode path i max true))))
(view [_] [key [data]]) (view [_] [key [data]])
(depth [_] 1) (depth [_] 1)
(length [_]))))) (length [_]))))
(linear-matcher [_ matchers]
(defn linear-matcher [matchers] (let [matchers (vec (reverse (sort-by (juxt depth length) matchers)))
#?(:clj (Trie/linearMatcher matchers)
:cljs (let [matchers (vec (reverse (sort-by (juxt depth length) matchers)))
size (count matchers)] size (count matchers)]
(reify Matcher (reify Matcher
(match [_ i max path] (match [_ i max path]
@ -239,11 +252,38 @@
(or (match (get matchers j) i max path) (or (match (get matchers j) i max path)
(recur (inc j)))))) (recur (inc j))))))
(view [_] (mapv view matchers)) (view [_] (mapv view matchers))
(depth [_] (apply max 0 (map depth matchers))) (depth [_] (inc (apply max 0 (map depth matchers))))
(length [_]))))) (length [_]))))
(prettify [_ matcher]
(view matcher))
(path-matcher [_ matcher]
(fn [path]
(if-let [match (match matcher 0 (count path) path)]
(->Match (:params match) (:data match)))))))
#?(:clj
(defn java-trie-compiler []
(reify
TrieCompiler
(data-matcher [_ params data]
(Trie/dataMatcher params data))
(static-matcher [_ path matcher]
(Trie/staticMatcher ^String path ^Trie$Matcher matcher))
(wild-matcher [_ key end matcher]
(Trie/wildMatcher key (if end (Character. end)) matcher))
(catch-all-matcher [_ key params data]
(Trie/catchAllMatcher key params data))
(linear-matcher [_ matchers]
(Trie/linearMatcher matchers))
(prettify [_ matcher]
(-> matcher str read-string eval))
(path-matcher [_ matcher]
(fn [path]
(if-let [match ^Trie$Match (Trie/lookup ^Trie$Matcher matcher ^String path)]
(->Match (.params match) (.data match))))))))
;; ;;
;; public api ;; Managing Tries
;; ;;
(defn insert (defn insert
@ -259,33 +299,42 @@
params (zipmap (->> parts (remove string?) (map :value)) (repeat nil))] params (zipmap (->> parts (remove string?) (map :value)) (repeat nil))]
(-insert (or node (-node {})) (split-path path) params data)))) (-insert (or node (-node {})) (split-path path) params data))))
(defn compile [{:keys [data params children wilds catch-all] :or {params {}}}] (defn compiler []
#?(:cljs (clojure-trie-compiler)
:clj (java-trie-compiler)))
(defn compile
([options]
(compile options (compiler)))
([{:keys [data params children wilds catch-all] :or {params {}}} compiler]
(let [ends (fn [{:keys [children]}] (or (keys children) ["/"])) (let [ends (fn [{:keys [children]}] (or (keys children) ["/"]))
matchers (-> [] matchers (-> []
(cond-> data (conj (data-matcher params data))) (cond-> data (conj (data-matcher compiler params data)))
(into (for [[p c] children] (static-matcher p (compile c)))) (into (for [[p c] children] (static-matcher compiler p (compile c compiler))))
(into (into
(for [[p c] wilds] (for [[p c] wilds]
(let [p (:value p) (let [p (:value p)
ends (ends c)] ends (ends c)]
(if (next ends) (if (next ends)
(ex/fail! (str "Trie compliation error: wild " p " has two terminators: " ends)) (ex/fail! (str "Trie compliation error: wild " p " has two terminators: " ends))
(wild-matcher p (ffirst ends) (compile c)))))) (wild-matcher compiler p (ffirst ends) (compile c compiler))))))
(into (for [[p c] catch-all] (catch-all-matcher (:value p) params (:data c)))))] (into (for [[p c] catch-all] (catch-all-matcher compiler (:value p) params (:data c)))))]
(cond (cond
(> (count matchers) 1) (linear-matcher matchers) (> (count matchers) 1) (linear-matcher compiler matchers)
(= (count matchers) 1) (first matchers) (= (count matchers) 1) (first matchers)
:else (data-matcher {} nil)))) :else (data-matcher compiler {} nil)))))
(defn pretty [matcher] (defn pretty
#?(:clj (-> matcher str read-string eval) ([trie]
:cljs (view matcher))) (pretty trie (compiler)))
([trie compiler]
(prettify compiler trie)))
(defn lookup [matcher path] (defn matcher
#?(:clj (if-let [match ^Trie$Match (Trie/lookup ^Trie$Matcher matcher ^String path)] ([trie]
(->Match (.params match) (.data match))) (matcher trie (compiler)))
:cljs (if-let [match (match matcher 0 (count path) path)] ([trie compiler]
(->Match (:params match) (:data match))))) (path-matcher compiler trie)))
;; ;;
;; spike ;; spike

View file

@ -98,7 +98,7 @@
;; 0.8µs (return route-data) ;; 0.8µs (return route-data)
;; 0.8µs (fix payloads) ;; 0.8µs (fix payloads)
#_(cc/quick-bench #_(cc/quick-bench
(trie/lookup reitit-tree "/v1/orgs/1/topics" {})) (trie/matcher reitit-tree "/v1/orgs/1/topics" {}))
;; 0.9µs (initial) ;; 0.9µs (initial)
;; 0.5µs (protocols) ;; 0.5µs (protocols)
@ -114,7 +114,7 @@
;; 0.30µs (iterate arrays) ;; 0.30µs (iterate arrays)
;; 0.28µs (list-params) ;; 0.28µs (list-params)
(cc/quick-bench (cc/quick-bench
(trie/lookup trie-matcher "/v1/orgs/1/topics"))) (trie/matcher trie-matcher "/v1/orgs/1/topics")))
(comment (comment
(bench!)) (bench!))
@ -123,6 +123,6 @@
(comment (comment
(p/lookup pedestal-tree "/v1/orgs/1/topics") (p/lookup pedestal-tree "/v1/orgs/1/topics")
(trie/lookup trie-matcher "/v1/orgs/1/topics") (trie/matcher trie-matcher "/v1/orgs/1/topics")
#_(segment/lookup segment-matcher "/v1/orgs/1/topics")) #_(segment/lookup segment-matcher "/v1/orgs/1/topics"))

View file

@ -15,23 +15,23 @@
(deftest tests (deftest tests
(is (= (trie/->Match {} {:a 1}) (is (= (trie/->Match {} {:a 1})
(-> (trie/insert nil "/foo" {:a 1}) ((-> (trie/insert nil "/foo" {:a 1})
(trie/compile) (trie/compile)
(trie/lookup "/foo")))) (trie/matcher)) "/foo")))
(is (= (trie/->Match {} {:a 1}) (is (= (trie/->Match {} {:a 1})
(-> (trie/insert nil "/foo" {:a 1}) ((-> (trie/insert nil "/foo" {:a 1})
(trie/insert "/foo/*bar" {:b 1}) (trie/insert "/foo/*bar" {:b 1})
(trie/compile) (trie/compile)
(trie/lookup "/foo")))) (trie/matcher)) "/foo")))
(is (= (trie/->Match {:bar "bar"} {:b 1}) (is (= (trie/->Match {:bar "bar"} {:b 1})
(-> (trie/insert nil "/foo" {:a 1}) ((-> (trie/insert nil "/foo" {:a 1})
(trie/insert "/foo/*bar" {:b 1}) (trie/insert "/foo/*bar" {:b 1})
(trie/compile) (trie/compile)
(trie/lookup "/foo/bar")))) (trie/matcher)) "/foo/bar")))
(is (= (trie/->Match {} {:a 1}) (is (= (trie/->Match {} {:a 1})
(-> (trie/insert nil "" {:a 1}) ((-> (trie/insert nil "" {:a 1})
(trie/compile) (trie/compile)
(trie/lookup ""))))) (trie/matcher)) ""))))