Skip to content

Commit

Permalink
Rewrite build to loop/recur (#12)
Browse files Browse the repository at this point in the history
Closes #11 

Co-authored-by: KGOH <[email protected]>
  • Loading branch information
darkleaf and KGOH authored Oct 11, 2024
1 parent 544278c commit 5db4a1a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 74 deletions.
115 changes: 65 additions & 50 deletions src/darkleaf/di/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -64,50 +64,68 @@
(.addSuppressed a b)
a))

(declare find-or-build)

(defn- missing-dependency! [ctx key]
(throw (ex-info (str "Missing dependency " key)
{:type ::missing-dependency
:path (:under-construction ctx)
:key key})))

(defn- circular-dependency! [ctx key]
(throw (ex-info (str "Circular dependency " key)
{:type ::circular-dependency
:path (:under-construction ctx)
:key key})))

(defn- resolve-dep [{:as ctx, :keys [under-construction]} acc key dep-type]
(if (seq-contains? under-construction key)
(circular-dependency! ctx key)
(if-some [obj (find-or-build ctx key)]
(assoc acc key obj)
(if (= :optional dep-type)
acc
(missing-dependency! ctx key)))))

(defn- resolve-deps [ctx deps]
(reduce-kv (partial resolve-dep ctx)
{}
deps))

(defn- find-obj [{:keys [*built-map]} key]
(get @*built-map key))

(defn- build-obj [{:as ctx, :keys [registry *built-map *stop-list]} key]
(let [ctx (update ctx :under-construction conj key)
factory (registry key)
declared-deps (p/dependencies factory)
resolved-deps (resolve-deps ctx declared-deps)
obj (p/build factory resolved-deps)]
(vswap! *stop-list conj #(p/demolish factory obj))
(vswap! *built-map assoc key obj)
obj))

(defn- find-or-build [ctx key]
(?? (find-obj ctx key)
(build-obj ctx key)))
(defn- missing-dependency! [stack]
(let [key (-> stack peek :key)]
(throw (ex-info (str "Missing dependency " key)
{:type ::missing-dependency
:stack (map :key stack)}))))

(defn- circular-dependency! [stack]
(let [key (-> stack peek :key)]
(throw (ex-info (str "Circular dependency " key)
{:type ::circular-dependency
:stack (map :key stack)}))))

(defn- update-head [stack f & args]
(let [head (peek stack)
tail (pop stack)]
(conj tail (apply f head args))))

(defn- stack-frame [key dep-type factory]
{:key key
:dep-type dep-type
:factory factory
:remaining-deps (seq (p/dependencies factory))})

(defn- build-obj [built-map factory]
(let [declared-deps (p/dependencies factory)
built-deps (select-keys built-map (keys declared-deps))]
(p/build factory built-deps)))

(defn- build [{:keys [registry *stop-list]} key]
(loop [stack (list (stack-frame key :required (registry key)))
built-map {}]
(if (empty? stack)
(built-map key)

(let [head (peek stack)
tail (pop stack)
key (:key head)
dep-type (:dep-type head)
factory (:factory head)
remaining-deps (:remaining-deps head)]

(cond
(contains? built-map key)
(recur tail built-map)

(seq-contains? (map :key tail) key)
(circular-dependency! stack)

(seq remaining-deps)
(let [[key dep-type] (first remaining-deps)]
(recur (-> stack
(update-head update :remaining-deps rest)
(conj (stack-frame key dep-type (registry key))))
built-map))

:else
(let [obj (build-obj built-map factory)]
(vswap! *stop-list conj #(p/demolish factory obj))
(case [obj dep-type]
[nil :optional] (recur tail built-map)
[nil :required] (missing-dependency! stack)
(recur tail (assoc built-map key obj)))))))))

(defn- try-run [proc]
(try
Expand Down Expand Up @@ -135,8 +153,7 @@

(defn- try-build [ctx key]
(try
(?? (build-obj ctx key)
(missing-dependency! ctx key))
(build ctx key)
(catch Throwable ex
(let [exs (try-stop-started ctx)
exs (cons ex exs)]
Expand Down Expand Up @@ -242,10 +259,8 @@

middlewares (concat [with-env with-ns root-registry] middlewares)
registry (apply-middleware nil-registry middlewares)
ctx {:*built-map (volatile! {})
:*stop-list (volatile! '())
:under-construction []
:registry registry}
ctx {:registry registry
:*stop-list (volatile! '())}
obj (try-build ctx key)]
^{:type ::root
::print obj}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(ns darkleaf.di.depencencies-test
(ns darkleaf.di.dependencies-test
(:require
[clojure.test :as t]
[darkleaf.di.core :as di]
Expand Down Expand Up @@ -75,26 +75,24 @@
:done)

(t/deftest error-path-test
(t/is (= {:path [`root-path `b-path] :key `missing-path}
(-> (di/start `root-path)
try-ex-data
(select-keys [:path :key])))))
(t/is (= {:type ::di/missing-dependency
:stack [`missing-path `b-path `root-path]}
(try-ex-data (di/start `root-path)))))


(defn parent
[{::syms [missing-key]}]
:done)


(t/deftest missing-dependency-test
(t/is (= {:path [] :key `missing-root}
(-> (di/start `missing-root)
try-ex-data
(select-keys [:path :key]))))
(t/is (= {:type ::di/missing-dependency
:stack [`missing-root]}
(try-ex-data (di/start `missing-root))))

(t/is (= {:path [`parent] :key `missing-key}
(-> (di/start `parent)
try-ex-data
(select-keys [:path :key])))))
(t/is (= {:type ::di/missing-dependency
:stack [`missing-key `parent]}
(try-ex-data (di/start `parent)))))


(defn recursion-a
Expand All @@ -110,14 +108,11 @@
:done)

(t/deftest circular-dependency-test
(t/is (= {:path [`recursion-a `recursion-b]
:key `recursion-a}
(-> (di/start `recursion-a)
try-ex-data
(select-keys [:path :key]))))

(t/is (= {:path [`recursion-c]
:key `recursion-c}
(-> (di/start `recursion-c)
try-ex-data
(select-keys [:path :key])))))
(t/is (= {:type ::di/circular-dependency
:stack [`recursion-a `recursion-b `recursion-a]}
(try-ex-data (di/start `recursion-a))))


(t/is (= {:type ::di/circular-dependency
:stack [`recursion-c `recursion-c]}
(try-ex-data (di/start `recursion-c)))))

0 comments on commit 5db4a1a

Please sign in to comment.