Initial Commit
This commit is contained in:
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
node_modules/
|
||||
public/js
|
||||
|
||||
/target
|
||||
/checkouts
|
||||
/src/gen
|
||||
|
||||
pom.xml
|
||||
pom.xml.asc
|
||||
*.iml
|
||||
*.jar
|
||||
*.log
|
||||
.shadow-cljs
|
||||
.idea
|
||||
.lein-*
|
||||
.nrepl-*
|
||||
.DS_Store
|
||||
|
||||
.hgignore
|
||||
.hg/
|
37
org/todo.org
Normal file
37
org/todo.org
Normal file
@@ -0,0 +1,37 @@
|
||||
#+title: Catbox To-Do
|
||||
#+todo: WAITING(w) TODO(t) IN_PROGRESS(i) | DONE(d)
|
||||
* Emptyhead
|
||||
#+BEGIN: kanban :mirrored t :layout ("..." . 50) :scope tree :depth 2
|
||||
| DONE | IN_PROGRESS | TODO | WAITING |
|
||||
|-----------------------------------+-----------------------------------+-----------------------------+-----------------------------------|
|
||||
| [[file:todo.org::*Move idea metadata into own field][Move idea metadata into own field]] | | | |
|
||||
| | | [[file:todo.org::*Implement contracts][Implement contracts]] | |
|
||||
| | | | [[file:todo.org::*Test thought execution][Test thought execution]] |
|
||||
| | | [[file:todo.org::*Write documentation][Write documentation]] | |
|
||||
| | [[file:todo.org::*Write thought stdlib][Write thought stdlib]] | | |
|
||||
| | [[file:todo.org::*Thought definition infrastructure][Thought definition infrastructure]] | | |
|
||||
| | | | [[file:todo.org::*Contract type checking everywhere][Contract type checking everywhere]] |
|
||||
| [[file:todo.org::*Return system][Return system]] | | | |
|
||||
| | [[file:todo.org::*Improve have-idea!][Improve have-idea!]] | | |
|
||||
| | | [[file:todo.org::*Implement 'describe' system][Implement 'describe' system]] | |
|
||||
#+END:
|
||||
|
||||
** DONE Move idea metadata into own field
|
||||
** TODO Implement contracts
|
||||
** WAITING Test thought execution
|
||||
** TODO Write documentation
|
||||
** IN_PROGRESS Write thought stdlib
|
||||
*** TODO Expose emptyhead functionality
|
||||
**** TODO Property add/remove
|
||||
**** TODO Idea create/update/delete
|
||||
**** TODO Thought extend/remove extension
|
||||
*** TODO Create new thoughts through composition
|
||||
*** TODO Message passing between thoughts
|
||||
** IN_PROGRESS Thought definition infrastructure
|
||||
** WAITING Contract type checking everywhere
|
||||
Waits on implementing contracts
|
||||
** DONE Return system
|
||||
** IN_PROGRESS Improve have-idea!
|
||||
** TODO Implement 'describe' system
|
||||
* Nothoughts
|
||||
* Catbox
|
2237
package-lock.json
generated
Normal file
2237
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
Normal file
11
package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "game",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"shadow-cljs": "2.28.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"pixi.js": "^8.4.1"
|
||||
}
|
||||
}
|
BIN
public/assets/Sprite-0002.aseprite
Normal file
BIN
public/assets/Sprite-0002.aseprite
Normal file
Binary file not shown.
BIN
public/assets/prart_tile_square_32x32.png
Normal file
BIN
public/assets/prart_tile_square_32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 283 B |
BIN
public/assets/prart_tile_square_32x32_green.png
Normal file
BIN
public/assets/prart_tile_square_32x32_green.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 288 B |
BIN
public/assets/prart_tile_square_32x32_red.png
Normal file
BIN
public/assets/prart_tile_square_32x32_red.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 B |
BIN
public/assets/test.png
Normal file
BIN
public/assets/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 B |
11
public/index.html
Normal file
11
public/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>acme frontend</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
17
shadow-cljs.edn
Normal file
17
shadow-cljs.edn
Normal file
@@ -0,0 +1,17 @@
|
||||
;; shadow-cljs configuration
|
||||
{:source-paths
|
||||
["src/dev"
|
||||
"src/cljs"
|
||||
"src/clj"]
|
||||
|
||||
:dependencies
|
||||
[]
|
||||
|
||||
:dev-http {8008 "public"}
|
||||
|
||||
:builds
|
||||
{:game
|
||||
{:target :browser
|
||||
:modules
|
||||
{:main
|
||||
{:init-fn nothoughts.core.init/init}}}}}
|
7
src/cljs/emptyhead/contract/eval.cljs
Normal file
7
src/cljs/emptyhead/contract/eval.cljs
Normal file
@@ -0,0 +1,7 @@
|
||||
(ns emptyhead.contract.eval
|
||||
"Implements contract evaluation on ideas."
|
||||
(:require [emptyhead.idea.protocol :as prtc]))
|
||||
|
||||
;; TODO stub
|
||||
(defn evaluate [contract idea]
|
||||
true)
|
59
src/cljs/emptyhead/idea/crud.cljs
Normal file
59
src/cljs/emptyhead/idea/crud.cljs
Normal file
@@ -0,0 +1,59 @@
|
||||
(ns emptyhead.idea.crud
|
||||
"Functions for Creating, Updating and Deleting ideas.
|
||||
The 'R' in 'CRUD' is implemented by [[emptyhead.idea.protocol/value]]."
|
||||
(:require [emptyhead.idea.state :refer [state]]
|
||||
[emptyhead.idea.property :as prop]
|
||||
[emptyhead.idea.protocol :as prtc]))
|
||||
|
||||
(defn- register-idea!
|
||||
"Helper function to scaffold an 'empty' idea."
|
||||
[ref]
|
||||
(swap! state assoc-in [ref :_meta :_properties] #{})
|
||||
(swap! state assoc-in [ref :_meta :_reference] ref)
|
||||
ref)
|
||||
|
||||
(defn swap-idea!
|
||||
"Swap data inside `idea` with given `data`.
|
||||
Returns a reference to `idea`."
|
||||
[idea data]
|
||||
(swap! state assoc
|
||||
(prtc/reference idea)
|
||||
(merge data {:_meta (prtc/val-fn :_meta idea)}))
|
||||
(prtc/reference idea))
|
||||
|
||||
(defn extend-idea!
|
||||
"Merge `data` into state of `idea`.
|
||||
Returns a reference to `idea`."
|
||||
[idea data]
|
||||
(swap! state assoc (prtc/reference idea) (prtc/val-fn merge idea data))
|
||||
(prtc/reference idea))
|
||||
|
||||
(defn mutate-idea!
|
||||
"Evaluate `fun` on `idea` with optional extra `args`, then replace `idea` by the result.
|
||||
Returns a reference to `idea`."
|
||||
[fun idea & args]
|
||||
(swap-idea! idea (prtc/val-fn fun idea args))
|
||||
(prtc/reference idea))
|
||||
|
||||
(defn forget-idea!
|
||||
"Delete `idea` from the state.
|
||||
Returns a copy of the `idea`."
|
||||
[idea]
|
||||
(let [val (prtc/value idea)]
|
||||
(apply prop/remove-property! idea (prop/properties idea))
|
||||
(prtc/ref-fn #(swap! state dissoc %) idea)
|
||||
(prtc/copy val)))
|
||||
|
||||
(defn have-idea!
|
||||
"Instantiate up to `count` new ideas, optionally prefixing reference symbol with `prefix`.
|
||||
Additionally allows you to immediately attach `properties` and `data`.
|
||||
Returns a single idea or a list of ideas depending on whether `count` was given."
|
||||
[& {:keys [prefix count properties data]
|
||||
:or {count 1 prefix "idea_" properties []}}]
|
||||
(let [fun #(register-idea! (gensym prefix))
|
||||
ideas (take count (repeatedly fun))]
|
||||
(run! #(apply prop/register-property! % properties) ideas)
|
||||
(when data (run! #(extend-idea! % data) ideas))
|
||||
(if (= count 1)
|
||||
(first ideas)
|
||||
ideas)))
|
68
src/cljs/emptyhead/idea/property.cljs
Normal file
68
src/cljs/emptyhead/idea/property.cljs
Normal file
@@ -0,0 +1,68 @@
|
||||
(ns emptyhead.idea.property
|
||||
"Implements 'properties' - hierarchical tags for ideas."
|
||||
(:require [clojure.set :as stdset]
|
||||
[emptyhead.idea.protocol :as prtc]
|
||||
[emptyhead.idea.state :as state]))
|
||||
|
||||
(defn properties
|
||||
"Returns a set of all properties associated with `idea`."
|
||||
[idea]
|
||||
(prtc/val-fn #(get-in % [:_meta :_properties]) idea))
|
||||
|
||||
(defn with-property
|
||||
"Returns a set of all ideas with `property`."
|
||||
[property]
|
||||
(get-in @state/state (concat [:_properties] property [:_node])))
|
||||
|
||||
;; XXX should error if multiple are found
|
||||
(defn just-property
|
||||
"Returns the single idea with `property`."
|
||||
[property]
|
||||
(first (with-property property)))
|
||||
|
||||
(defn has-property?
|
||||
"Returns true if and only if `property` is associated with `idea`."
|
||||
[idea property]
|
||||
(contains? (properties idea) property))
|
||||
|
||||
(defn register-property!
|
||||
"Associate one or more `properties` to an `idea`."
|
||||
[idea & properties]
|
||||
(let [property (first properties)
|
||||
tail (rest properties)
|
||||
iref (prtc/reference idea)]
|
||||
(when property
|
||||
(reduce
|
||||
(fn [acc property]
|
||||
(swap! state/state update-in (conj acc property :_node) (fnil conj #{}) iref)
|
||||
(swap! state/state update-in [iref :_meta :_properties] (fnil conj #{}) (conj (vec (rest acc)) property))
|
||||
(conj acc property))
|
||||
[:_properties] property))
|
||||
(when tail
|
||||
(run! #(register-property! idea %) tail))
|
||||
iref))
|
||||
|
||||
(defn- remove-property-node! [idea property]
|
||||
(swap! state/state update-in
|
||||
(concat [:_properties] property [:_node])
|
||||
disj (prtc/reference idea))
|
||||
(prtc/reference idea))
|
||||
|
||||
(defn- child-properties [property]
|
||||
(map #(conj property %)
|
||||
(-> @state/state :_properties (get-in property)
|
||||
keys set (disj :_node))))
|
||||
|
||||
(defn- rm-prop! [idea property]
|
||||
(swap! state/state update-in [idea :_meta :_properties] disj property)
|
||||
(remove-property-node! idea property)
|
||||
(let [children (stdset/intersection
|
||||
(set (child-properties property))
|
||||
(properties idea))]
|
||||
(run! #(rm-prop! idea %) children)))
|
||||
|
||||
(defn remove-property!
|
||||
"Dissociate one or more `properties` from `idea`."
|
||||
[idea & properties]
|
||||
(run! #(prtc/ref-fn rm-prop! idea %) properties)
|
||||
(prtc/reference idea))
|
78
src/cljs/emptyhead/idea/protocol.cljs
Normal file
78
src/cljs/emptyhead/idea/protocol.cljs
Normal file
@@ -0,0 +1,78 @@
|
||||
(ns emptyhead.idea.protocol
|
||||
"Implements transparent conversions between the _value_ of an idea, i.e. a map containing its data,
|
||||
and the _reference_ of an idea, i.e. a symbol that identifies it in the state;
|
||||
i.e., state looks like {reference_1 value_1 ...}"
|
||||
(:require [emptyhead.util.logging :as log]
|
||||
[emptyhead.idea.state :refer [state]]))
|
||||
|
||||
(defn- to-reference [val]
|
||||
(let [ref (get-in val [:_meta :_reference])]
|
||||
(cond
|
||||
(get-in val [:_meta :_stale-reference])
|
||||
(log/error (str "Attempt to find stale reference `" ref "` -- this is a copy.")
|
||||
{:value val :type :stale-reference})
|
||||
|
||||
(not (symbol? ref))
|
||||
(log/error (str "Attempt to find invalid reference `" ref "` -- invalid idea?")
|
||||
{:value val :type :invalid-reference})
|
||||
|
||||
:else ref)))
|
||||
|
||||
(defprotocol Idea
|
||||
(reference [idea] "Reference, i.e. symbol, for `idea`.")
|
||||
(value [idea] "Value, i.e. map, of `idea`."))
|
||||
|
||||
(extend-protocol Idea
|
||||
cljs.core/PersistentHashMap
|
||||
(reference [idea] (to-reference idea))
|
||||
(value [idea] idea)
|
||||
|
||||
cljs.core/PersistentArrayMap
|
||||
(reference [idea] (to-reference idea))
|
||||
(value [idea] idea)
|
||||
|
||||
Symbol
|
||||
(reference [idea] idea)
|
||||
(value [idea] (get @state idea)))
|
||||
|
||||
(defn copy
|
||||
"Make a copy of `idea`.
|
||||
Returns an object that is identical to the value of `idea`,
|
||||
but marked as not containing a reference to anything in the state."
|
||||
[idea]
|
||||
(assoc-in (value idea) [:_meta :_stale-reference] true))
|
||||
|
||||
(defn- non-copy [idea] (assoc-in (value idea) [:_meta :_stale-reference] false))
|
||||
|
||||
(defn uncopy!
|
||||
"Takes a copied idea and 'uncopies' it, making its reference active again
|
||||
and updating what is in the game state."
|
||||
[copy-obj]
|
||||
(let [idea (non-copy copy-obj)]
|
||||
(swap! state assoc (to-reference idea) idea)))
|
||||
|
||||
(defn force-reference
|
||||
"Get the (now stale!) reference of a copied idea."
|
||||
[copy-obj]
|
||||
(to-reference (non-copy copy-obj)))
|
||||
|
||||
(defn copy-fn
|
||||
"Execute `fun` on a copy of `idea` with optional additional `args`."
|
||||
[fun idea & args]
|
||||
(apply fun (copy idea) args))
|
||||
|
||||
(defn val-fn
|
||||
"Execute `fun` on the value of `idea` with optional additional `args`."
|
||||
[fun idea & args]
|
||||
(apply fun (value idea) args))
|
||||
|
||||
(defn ref-fn
|
||||
"Execute `fun` on a reference to `idea` with optional additional `args`."
|
||||
[fun idea & args]
|
||||
(apply fun (reference idea) args))
|
||||
|
||||
(defn force-reference-fn
|
||||
"Execute `fun` on a reference to `idea` with optional additional `args`.
|
||||
Unlike [[reference-fn]], this will work on a copy."
|
||||
[fun idea & args]
|
||||
(apply fun (force-reference idea) args))
|
16
src/cljs/emptyhead/idea/state.cljs
Normal file
16
src/cljs/emptyhead/idea/state.cljs
Normal file
@@ -0,0 +1,16 @@
|
||||
(ns emptyhead.idea.state
|
||||
"Implements the state object.")
|
||||
|
||||
(def empty-state
|
||||
{:_properties {}
|
||||
|
||||
:_descriptions
|
||||
{:_properties "Property-to-idea mapping."
|
||||
:property {}
|
||||
:property_fns {}}})
|
||||
|
||||
(defonce state
|
||||
(atom empty-state))
|
||||
|
||||
(defn reset-state! []
|
||||
(reset! state empty-state))
|
6
src/cljs/emptyhead/lib/core.cljs
Normal file
6
src/cljs/emptyhead/lib/core.cljs
Normal file
@@ -0,0 +1,6 @@
|
||||
(ns emptyhead.lib.core
|
||||
"Core components of the stdlib."
|
||||
(:require [emptyhead.thought.crud :as thought]))
|
||||
|
||||
(defn data [data]
|
||||
(thought/make-thought :emptyhead.core.data :data data))
|
1
src/cljs/emptyhead/lib/idea.cljs
Normal file
1
src/cljs/emptyhead/lib/idea.cljs
Normal file
@@ -0,0 +1 @@
|
||||
(ns emptyhead.lib.idea)
|
13
src/cljs/emptyhead/test/fixtures.cljs
Normal file
13
src/cljs/emptyhead/test/fixtures.cljs
Normal file
@@ -0,0 +1,13 @@
|
||||
(ns emptyhead.test.fixtures
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
[emptyhead.idea.state :as s]))
|
||||
|
||||
(defn temporary-state [tests]
|
||||
(let [orig-state (swap! s/state identity)]
|
||||
(s/reset-state!)
|
||||
(tests)
|
||||
(reset! s/state orig-state)))
|
||||
|
||||
(defn pre-reset [tests]
|
||||
(s/reset-state!)
|
||||
(tests))
|
30
src/cljs/emptyhead/test/idea/crud.cljs
Normal file
30
src/cljs/emptyhead/test/idea/crud.cljs
Normal file
@@ -0,0 +1,30 @@
|
||||
(ns emptyhead.test.idea.crud
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
[emptyhead.idea.crud :as crud]
|
||||
[emptyhead.idea.state :as s :refer [state]]
|
||||
[emptyhead.idea.protocol :as prtc]
|
||||
[emptyhead.test.fixtures :as fx]))
|
||||
|
||||
(t/use-fixtures :once fx/temporary-state)
|
||||
(t/use-fixtures :each fx/pre-reset)
|
||||
|
||||
(t/deftest have-idea!
|
||||
(t/testing "Idea creation"
|
||||
(let [idea (crud/have-idea!)]
|
||||
(t/is (get @state idea)))))
|
||||
|
||||
(t/deftest idea-updating
|
||||
(t/testing "Idea updating"
|
||||
(let [idea (crud/have-idea!)]
|
||||
(crud/extend-idea! idea {:foo :bar})
|
||||
(t/is (= :bar (prtc/val-fn :foo idea)))
|
||||
(crud/swap-idea! idea {:baz :quux})
|
||||
(t/is (not (prtc/val-fn :foo idea)))
|
||||
(t/is (= :quux (prtc/val-fn :baz idea)))
|
||||
)))
|
||||
|
||||
(t/deftest forget!
|
||||
(t/testing "Idea deletion"
|
||||
(let [idea (crud/have-idea!)]
|
||||
(crud/forget-idea! idea)
|
||||
(t/is (= nil (get @state idea))))))
|
25
src/cljs/emptyhead/test/idea/property.cljs
Normal file
25
src/cljs/emptyhead/test/idea/property.cljs
Normal file
@@ -0,0 +1,25 @@
|
||||
(ns emptyhead.test.idea.property
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
[emptyhead.idea.crud :as crud]
|
||||
[emptyhead.idea.property :as prop]
|
||||
[emptyhead.test.fixtures :as fx]))
|
||||
|
||||
(t/use-fixtures :once fx/temporary-state)
|
||||
(t/use-fixtures :each fx/pre-reset)
|
||||
|
||||
(t/deftest properties
|
||||
(t/testing "Property addition and removal"
|
||||
(let [idea (crud/have-idea!)]
|
||||
(prop/register-property! idea [:a :b :c] [:a :d :e])
|
||||
|
||||
(t/is (= #{[:a :b :c] [:a :d :e] [:a] [:a :b] [:a :d]}
|
||||
(prop/properties idea)))
|
||||
|
||||
(t/is (prop/has-property? idea [:a :b]))
|
||||
(t/is (not (prop/has-property? idea [:invalid])))
|
||||
|
||||
(t/is (contains? (prop/with-property [:a :d]) idea))
|
||||
(t/is (not (contains? (prop/with-property [:invalid]) idea)))
|
||||
|
||||
(prop/remove-property! idea [:a :b])
|
||||
(t/is (not (prop/has-property? idea [:a :b :c]))))))
|
22
src/cljs/emptyhead/test/idea/protocol.cljs
Normal file
22
src/cljs/emptyhead/test/idea/protocol.cljs
Normal file
@@ -0,0 +1,22 @@
|
||||
(ns emptyhead.test.idea.protocol
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
[emptyhead.idea.crud :as crud]
|
||||
[emptyhead.idea.protocol :as prtc]
|
||||
[emptyhead.test.utils :refer [expect-error]]
|
||||
[emptyhead.test.fixtures :as fx]))
|
||||
|
||||
(t/use-fixtures :once fx/temporary-state)
|
||||
(t/use-fixtures :each fx/pre-reset)
|
||||
|
||||
(t/deftest protocol
|
||||
(t/testing "Value/reference semantics"
|
||||
(let [idea (crud/have-idea!)]
|
||||
;; Repeatedly going between value and reference shouldn't mangle idea
|
||||
(t/is (= idea (-> idea prtc/value prtc/reference)))
|
||||
(t/is (= (prtc/value idea) (-> idea prtc/reference prtc/value)))
|
||||
|
||||
;; Attempting to get a reference to a copy should be an error
|
||||
(expect-error :stale-reference #(prtc/reference (prtc/copy idea)))
|
||||
|
||||
;; Attempting to get a reference to an invalid idea should be an error
|
||||
(expect-error :invalid-reference #(prtc/reference {})))))
|
8
src/cljs/emptyhead/test/main.cljs
Normal file
8
src/cljs/emptyhead/test/main.cljs
Normal file
@@ -0,0 +1,8 @@
|
||||
(ns emptyhead.test.main
|
||||
(:require [cljs.test :as t :include-macros true]
|
||||
[emptyhead.test.idea.crud]
|
||||
[emptyhead.test.idea.property]
|
||||
[emptyhead.test.idea.protocol]))
|
||||
|
||||
(defn run []
|
||||
(t/run-all-tests #"emptyhead\.test\..*"))
|
10
src/cljs/emptyhead/test/utils.cljs
Normal file
10
src/cljs/emptyhead/test/utils.cljs
Normal file
@@ -0,0 +1,10 @@
|
||||
(ns emptyhead.test.utils
|
||||
(:require [cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn expect-error [type fun]
|
||||
(t/is
|
||||
(= type
|
||||
(try (fun)
|
||||
nil
|
||||
(catch :default e
|
||||
(-> e ex-data :data first :type))))))
|
49
src/cljs/emptyhead/thought/crud.cljs
Normal file
49
src/cljs/emptyhead/thought/crud.cljs
Normal 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))
|
31
src/cljs/emptyhead/thought/define.cljs
Normal file
31
src/cljs/emptyhead/thought/define.cljs
Normal 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))
|
45
src/cljs/emptyhead/thought/eval.cljs
Normal file
45
src/cljs/emptyhead/thought/eval.cljs
Normal 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))))
|
60
src/cljs/emptyhead/thought/extend.cljs
Normal file
60
src/cljs/emptyhead/thought/extend.cljs
Normal 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)))
|
16
src/cljs/emptyhead/thought/return.cljs
Normal file
16
src/cljs/emptyhead/thought/return.cljs
Normal 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))
|
10
src/cljs/emptyhead/util/logging.cljs
Normal file
10
src/cljs/emptyhead/util/logging.cljs
Normal file
@@ -0,0 +1,10 @@
|
||||
(ns emptyhead.util.logging)
|
||||
|
||||
(defn error [message & data]
|
||||
(throw (ex-info message {:data data})))
|
||||
|
||||
(defn warn [message & data]
|
||||
(error message data))
|
||||
|
||||
(defn message [message & data]
|
||||
(println message {:data data}))
|
2
src/cljs/nothoughts/core/init.cljs
Normal file
2
src/cljs/nothoughts/core/init.cljs
Normal file
@@ -0,0 +1,2 @@
|
||||
(ns nothoughts.core.init)
|
||||
(defn init [] (print "hello"))
|
Reference in New Issue
Block a user