Files
EMPTYHEAD/src/cljs/emptyhead/repl/core.cljs
2025-08-19 22:54:57 +02:00

183 lines
6.6 KiB
Clojure

(ns emptyhead.repl.core
(:require [clojure.string :as str]
[emptyhead.thought.extend :as extend]
[emptyhead.thought.eval :as teval]
[emptyhead.idea.property :as prop]
[emptyhead.util.magic :as magic]
[emptyhead.idea.protocol :as prtc]
[emptyhead.idea.memtag :as memtag]
[emptyhead.thought.define :as def]
[emptyhead.thought.crud :as thought]
[emptyhead.lib.core]
[emptyhead.lib.context :as ctx]))
(defonce context (ctx/new-context))
(defn clear-context [] (set! context (ctx/new-context)))
(defn undot-operator [dotted-sym]
(mapv keyword (str/split (name dotted-sym) #"\.")))
(defn eval! [thought & [parent]]
(set! context (teval/execute! thought parent)))
(defn parse-command [cmd]
(cond
(= cmd '.BEGIN) (thought/register-thought! [:emptyhead :repl :begin-block])
(= cmd '.END) (thought/register-thought! [:emptyhead :repl :end-block])
(symbol? cmd) (thought/register-thought!
[:emptyhead :compose :append]
:data (thought/register-thought! (undot-operator cmd)))
:else (thought/register-thought!
[:emptyhead :compose :append]
:data (thought/register-thought! [:emptyhead :core :return] :data cmd))))
(def/define! [:emptyhead :repl :begin-block]
(fn [thought parent]
(let [ctx-creator (thought/register-thought! [:emptyhead :core :context :construct])
;; Create a new context with `parent` as parent.
[parent ctx] (->> parent (teval/execute! ctx-creator) thought/pop-stack)
ctx (prop/just-property ctx)
;; Make `parent` put the new context on its stack during its execution flow
;; TODO refactor
parent (teval/execute! (thought/register-thought!
[:emptyhead :compose :append]
:data (thought/register-thought! [:emptyhead :core :return]
:data (memtag/uid-of ctx)))
parent)]
;; NOTE `ctx` becomes the new `parent`!
[ctx
ctx])))
(def/define! [:emptyhead :repl :end-block]
;; Exiting context, so its parent becomes the new parent.
(fn [thought parent]
(let [parent-ctx (-> parent prtc/value :data :parent prop/just-property)]
[parent-ctx nil])))
(defn read [& input]
(map parse-command input))
(defn pprint-tos [thought]
(let [printer #(println (str "EMPTYHEAD> " %))
tos (-> thought prtc/value :return last)]
(-> tos printer)
tos))
(defn run [& input]
(clear-context)
(run! #(eval! % context) (apply read input))
(pprint-tos context)
(prtc/reference (teval/execute! context context)))
;; XXX an annoying class of bug is eg forgetting the return value here, which yields just
;; >No protocol method Idea.value defined for type null:
;; In this case originationg from [[emptyhead.thought.eval/execute!]]
;; Widely catching these errors earlier would be wise since we don't have
;; a good stack trace for this stuff
(def/define! [:emptyhead :debug :track]
(fn [thought parent]
(let [ctx (:_parent (prtc/value parent))]
(print "RUNNING " (thought/operator parent) " ON ")
(print "CONTEXT " ctx " WITH \n")
(print "STACK: ")
(print (:return ctx))
(print "\n\n")
[parent nil])))
(def sample (thought/register-thought! [:foo]))
;; XXX This has to have no :PRE-EXECUTE stage otherwise it will loop on itself ww
(defonce tracker (thought/register-thought! [:emptyhead :debug :track] :ext-stages [[:EXECUTE]]))
(defn enable-tracking []
(extend/register-extension! tracker [:PRE-EXECUTE]))
;; XXX accidentally registering one extension multiple times is a known footgun right now
;; and probably needs some feature to prevent this
;; (but mind that it shouldn't be *illegal*)
;; Another issue is just general... garbage collection, that's a big thing
(defn disable-tracking []
(map #(extend/remove-extension! % [:PRE-EXECUTE]) (extend/extensions-of-stage [:PRE-EXECUTE])))
(defn name-idea
[idea context name]
(prop/register-property! idea [:emptyhead :name name (prtc/reference context)]))
(defn resolve-name
[context name]
(prop/just-property [:emptyhead :name name (prtc/reference context)]))
(defn deref-name
[self name]
(or (and (= (thought/operator self) [:emptyhead :core :context])
(resolve-name self name))
(and (prtc/val-fn :_parent self)
(deref-name (prtc/val-fn :_parent self) name))))
(def/define! [:GRAB]
(fn [thought parent]
(let [arg-num (inc (or (get-in parent [:data :last-arg]) -1))]
[(assoc-in parent [:data :last-arg] arg-num)
(-> parent prtc/value :_parent prtc/value :return (#(nth % (- (count %) arg-num 1))))])))
(def/define! [:RETURN]
(fn [thought parent]
(let [[parent val] (thought/pop-stack parent)]
[(update-in parent [:data :proc-ret] (fnil conj []) val) nil])))
;; FIXME this wil bind two things to the same name if you name severa things the same
;; FIXME this needs to wrap data in an [:emptyhead :core :return] and then evaluate that on deref
(def/define! [:emptyhead :core :assign-name]
(fn [_ parent]
(let [[parent name] (thought/pop-stack parent)
[_ op] (thought/pop-stack parent)
exe (prop/just-property op)]
(extend/register-extension! exe (into [:_name] name))
[parent nil])))
;; FIXME this is a weird use of the extension system -- figure out how names should actually work!
;; (in light of the way extensions usually work, see e.g. [emptyhead.thought.extend/get-extensions]) comment
;; FIXME this needs to wrap data in an [:emptyhead :core :return] and then evaluate that on deref
(def/define! [:emptyhead :core :deref-name]
(fn [_ parent]
(let [[parent name] (thought/pop-stack parent)
deref (memtag/uid-of
(prop/just-property (magic/extension-prop (into [:_name] name))))]
[parent deref])))
(def tst
'(.BEGIN
"<> -- COMMENCE PROCEDURE -- <>"
emptyhead.io.print
GRAB emptyhead.io.print
GRAB emptyhead.io.print
"<> -- PROCEDURE COMPLETE -- <>"
42 RETURN
69 RETURN
.END
emptyhead.io.print
[:meme]
emptyhead.core.assign-name
"hello"
"goodbye"
[:meme]
emptyhead.core.deref-name
emptyhead.core.execute
emptyhead.core.pop ;; pop deref'd name
"\nReturn values of previous:\n"
emptyhead.io.print
emptyhead.core.pop
[:meme]
emptyhead.core.deref-name
emptyhead.core.execute))