Initial Commit

This commit is contained in:
akko
2024-10-08 11:47:30 +02:00
commit 85b6b7360f
31 changed files with 2889 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
(ns emptyhead.thought.crud
"Implements CRUD operations on thoughts.
Since thoughts are ideas, 'missing' operations here are implemented in [[emptyhead.idea.crud]]."
(:require [emptyhead.idea.protocol :as prtc]
[emptyhead.idea.crud :as idea]
[emptyhead.idea.property :as prop]
[emptyhead.contract.eval :as contract]))
(defn make-thought
"Helper function to make thought object.
You may want `register-thought!` instead."
[operator & {:keys [data ext-contract ext-stages transient]
:or {data {} ext-contract {}
ext-stages [[:thought operator]]
transient true}}]
(hash-map :operator operator
:data data
:ext-contract ext-contract
:ext-stages ext-stages
:return {}
:transient (not (false? transient))))
(defn register-thought!
"Create a thought and register it in the state.
Returns a reference to the created thought."
[operator & {:keys [data ext-contract ext-stages transient]
:as args}]
(idea/have-idea!
:prefix (str "thought_" (name operator) "_")
:properties [[:thought]]
:data (make-thought operator args)))
(defn contract
"Get the extension contract of a `thought`.
Returns the contract."
[thought]
(prtc/val-fn :ext-contract thought))
(defn stages
"Get the extension stages of a `thought`.
Returns the list of stages."
[thought]
(prtc/val-fn :ext-stages thought))
(defn operator
"Get the operator id of a `thought`.
Returns the operator keyword."
[thought]
(prtc/val-fn :operator thought))

View File

@@ -0,0 +1,31 @@
(ns emptyhead.thought.define
"Utilities for defining new thoughts."
(:require [emptyhead.thought.crud :as thought]
[emptyhead.thought.eval :as eval]
[emptyhead.idea.crud :as idea]))
(defn register-implementation!
[operator impl]
(idea/have-idea! :prefix (str "impl_thought_" (name operator))
:properties [[:thought-impl operator]]
:data {:implementation impl}))
(defn register-constructor!
[operator & {:keys [constr-fn defaults]
:or {constr-fn identity
defaults {}}}]
(let [constr-op (keyword (str (name operator) ".construct"))]
(register-implementation!
constr-op
(fn [thought & [parent]]
[parent
(thought/register-thought!
operator
(merge defaults {:operator operator}
(constr-fn (merge parent thought))))]))))
(defn define!
[operator impl & {:keys [constr-fn defaults]
:as constr-args}]
(register-implementation! operator impl)
(register-constructor! operator constr-args))

View File

@@ -0,0 +1,45 @@
(ns emptyhead.thought.eval
"Implements evaluation of thoughts."
(:require [emptyhead.idea.protocol :as prtc]
[emptyhead.thought.extend :as extend]
[emptyhead.thought.crud :as thought]
[emptyhead.util.logging :as logging]
[emptyhead.thought.return :as return]
[emptyhead.idea.property :as prop]
[emptyhead.idea.crud :as idea]))
(defn- impl! [thought & [parent]]
(let [impl-idea (prop/just-property [:thought-impl (thought/operator thought)])]
(if-not impl-idea
(logging/error (str "No implementation for thought `" (thought/operator thought) "`.")
{:thought thought :parent parent :type :unimplemented-thought})
((prtc/copy-fn :implementation impl-idea) thought parent))))
(def root-thought (thought/make-thought :root))
;; at what point do returns get cleared?
(defn execute!
"Execute `thought` with `parent`, applying aspects to `thought` according to its :extension-stages.
Returns (potentially modified) `parent`."
[thought & [parent]]
(loop [th (prtc/copy thought)
parent (or parent root-thought)]
(let [cur (first (thought/stages thought))
[extensions th] (extend/pop-stage th)
;; Execute extensions, potentially modifying th
th (reduce #(execute! %2 %1) th extensions)
;; If it's time for `thought`'s implementation to run, do so,
;; potentially modifying `parent`.
[parent return]
(if (= cur [:thought (thought/operator th)])
(impl! th parent)
parent)
;; Fold return value into `parent`.
parent (return/with-return parent (thought/operator th) return)]
;; Recur if there's remaining aspects, otherwise return `parent`.
(if (not-empty (thought/stages th))
(recur th parent)
parent))))

View File

@@ -0,0 +1,60 @@
(ns emptyhead.thought.extend
(:require [emptyhead.contract.eval :as contract]
[emptyhead.idea.property :as prop]
[emptyhead.idea.protocol :as prtc]
[emptyhead.thought.crud :as thought]
[clojure.set :refer [union]]))
(defn register-extension
"Register `thought` as extension for one or more `stages`."
[thought & stages]
(doseq [stage stages]
(prop/register-property! thought [:_extends stage]))
thought)
(defn remove-extension!
"Remove `thought` as extension for one or more `stages`."
[thought & stages]
(doseq [stage stages]
(prop/remove-property! thought [:_extends stage]))
thought)
(defn- get-extensions
"Get extensions for `stage`;
e.g. for `stage` of [:foo :bar :baz], get extensions for [:foo :bar :baz], [:foo :bar], [:foo]"
[stage]
(loop [property-segment stage
acc []]
(if (empty? property-segment)
acc
(recur (butlast property-segment)
(union acc (prop/with-property [:_extends property-segment]))))))
(defn extensions
"Get extensions of `thought`.
Returns a map of {stage => #{thought}}."
[thought]
(let [stages (thought/stages thought)
filter-fn #(contract/evaluate (thought/contract thought) %)
aspects (map
#(->> % get-extensions (filter filter-fn))
stages)]
(zipmap stages aspects)))
(defn pop-stage
"Get aspects of next stage of thought execution.
Returns a tuple of (#{extension-dhammas},
a copy of `thought` with its first extension-stage removed)."
[thought]
(let [stages (thought/stages thought)
aspects (filter #(contract/evaluate (thought/contract thought) %)
(get-extensions (first stages)))
modified (prtc/copy-fn #(assoc % :ext-stages (rest stages))
thought)]
(list aspects modified)))
(defn has-stage?
"Return true if `thought` has extension stage `stage`.
Also works if e.g. `stage` is [:foo] and `thought` has [:foo :bar]."
[thought stage]
(some #(= stage (take (count stage) %)) (thought/stages thought)))

View File

@@ -0,0 +1,16 @@
(ns emptyhead.thought.return
"Utilities for handling thoughts' return values to their parents."
(:require [emptyhead.thought.crud :as thought]
[emptyhead.idea.protocol :as prtc]))
(defn with-return [thought namespace data]
(if data
(prtc/copy-fn
#(update-in % [:return namespace] (fnil conj []) data) thought)
thought))
(defn return-vals [thought namespace]
(prtc/copy-fn #(get-in % [:return namespace]) thought))
(defn clear-returns [thought]
(prtc/copy-fn #(assoc % :return {}) thought))