Skip to content

Instantly share code, notes, and snippets.

@yvern
Created January 20, 2023 19:04
Show Gist options
  • Save yvern/5a269cd9c7c8b933c32963e8e36deb0d to your computer and use it in GitHub Desktop.
Save yvern/5a269cd9c7c8b933c32963e8e36deb0d to your computer and use it in GitHub Desktop.
(ns yvern.defrule)
(def clara-inserts
"The forms we care about, capable of inserting facts."
#{'insert! 'insert-all!})
(def descend*
"Transducer to descend into inner forms, looping for those we care about, the ones that insert facts.
Unfortunately we can't really figure out if someone defines another function that calls `insert!`."
(comp (map macroexpand)
(drop 1)
(filter (some-fn #(some clara-inserts (flatten %))
clara-inserts))
cat))
(defn- check-fact-type
[inserts]
(some #{:fact-type} (tree-seq coll? seq inserts)))
(defn- descend
"Recursively checks forms that contain `insert!`s based on whether the verb/head is:
`insert!`s (exiting),
`let` forms where the body needs to be checked,
or any other symbol, meaning a macro/function call around an `insert!`, which could change control flow."
[[verb & args]]
(case verb
(insert! insert-all!)
(when-not (every? check-fact-type args)
(ex-info "Facts must have the `:fact-type` key" {}))
(let clojure.core/let let* clojure.core/let* do clojure.core/do) ; we can add further forms here, but to use as a param we need to use `cond` or write a macro
(recur (sequence descend* args))
(-> "`%s` or anything that expands to it is not allowed around `insert!` or `insert-all!` inside a `defrule`"
(format verb)
(ex-info {}))))
(defmacro defrule
[form]
(some->> form
(sequence (comp (drop-while (complement #{'=>})) descend*))
descend
throw))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment