First stab at 'actors': using threads.
This commit is contained in:
parent
09696c212a
commit
1e28556baa
|
@ -1,15 +1,17 @@
|
||||||
(ns ludus.interpreter
|
(ns ludus.interpreter
|
||||||
(:require
|
(:require
|
||||||
[ludus.parser :as parser]
|
[ludus.parser :as parser]
|
||||||
[ludus.scanner :as scanner]
|
[ludus.scanner :as scanner]
|
||||||
[ludus.ast :as ast]
|
[ludus.ast :as ast]
|
||||||
[ludus.prelude :as prelude]
|
[ludus.prelude :as prelude]
|
||||||
[ludus.data :as data]
|
[ludus.data :as data]
|
||||||
[ludus.show :as show]
|
[ludus.show :as show]
|
||||||
[ludus.loader :as loader]
|
[ludus.loader :as loader]
|
||||||
[ludus.token :as token]
|
[ludus.token :as token]
|
||||||
[clojure.pprint :as pp]
|
[clojure.pprint :as pp]
|
||||||
[clojure.set]))
|
[clojure.set]))
|
||||||
|
|
||||||
|
(def ^:dynamic self 1001)
|
||||||
|
|
||||||
;; right now this is not very efficient:
|
;; right now this is not very efficient:
|
||||||
;; it's got runtime checking
|
;; it's got runtime checking
|
||||||
|
@ -25,6 +27,62 @@
|
||||||
|
|
||||||
(declare interpret-ast match interpret)
|
(declare interpret-ast match interpret)
|
||||||
|
|
||||||
|
(def processes (atom {}))
|
||||||
|
|
||||||
|
(def current-pid (atom 1001))
|
||||||
|
|
||||||
|
(defn new-process []
|
||||||
|
(let [pid @current-pid
|
||||||
|
process (atom {:pid pid
|
||||||
|
:queue clojure.lang.PersistentQueue/EMPTY
|
||||||
|
:in nil
|
||||||
|
:status :occupied
|
||||||
|
})]
|
||||||
|
(swap! processes #(assoc % pid process))
|
||||||
|
(swap! current-pid inc)
|
||||||
|
process))
|
||||||
|
|
||||||
|
(def vm-state (atom :stopped))
|
||||||
|
|
||||||
|
(defn- values [m] (into [] (map (fn [[_ v]] v)) m))
|
||||||
|
|
||||||
|
(defn- process-msg [process]
|
||||||
|
;;(println "processing message" self)
|
||||||
|
(let [q (:queue process)
|
||||||
|
in (:in process)]
|
||||||
|
(when (not (realized? in))
|
||||||
|
;;(println "delivering message in" self)
|
||||||
|
(deliver in (peek q))
|
||||||
|
(assoc process :queue (pop q) :in nil))))
|
||||||
|
|
||||||
|
(defn- run-process [process-atom]
|
||||||
|
(let [process @process-atom
|
||||||
|
status (:status process)
|
||||||
|
q (:queue process)
|
||||||
|
in (:in process)]
|
||||||
|
;;(println "running process" self ":" (into [] q))
|
||||||
|
(when (and (= status :idle) (not-empty q) in)
|
||||||
|
(swap! process-atom process-msg))))
|
||||||
|
|
||||||
|
(defn- start-vm []
|
||||||
|
;; (println "Starting Ludus VM")
|
||||||
|
(when (= @vm-state :stopped)
|
||||||
|
(future
|
||||||
|
(reset! vm-state :running)
|
||||||
|
(loop []
|
||||||
|
(if (= @vm-state :running)
|
||||||
|
(do
|
||||||
|
(run! run-process (values @processes))
|
||||||
|
(recur))
|
||||||
|
;; (println "Ludus VM shutting down")
|
||||||
|
)))))
|
||||||
|
|
||||||
|
(defn- stop-vm []
|
||||||
|
(reset! vm-state :stopped)
|
||||||
|
(reset! processes {})
|
||||||
|
(reset! current-pid 1001)
|
||||||
|
nil)
|
||||||
|
|
||||||
(defn- match-tuple [pattern value ctx-vol]
|
(defn- match-tuple [pattern value ctx-vol]
|
||||||
(cond
|
(cond
|
||||||
(not (vector? value)) {:success false :reason "Could not match non-tuple value to tuple"}
|
(not (vector? value)) {:success false :reason "Could not match non-tuple value to tuple"}
|
||||||
|
@ -219,9 +277,9 @@
|
||||||
:name (str (:name lfn) "{partial}")
|
:name (str (:name lfn) "{partial}")
|
||||||
:body (fn [arg]
|
:body (fn [arg]
|
||||||
(call-fn
|
(call-fn
|
||||||
lfn
|
lfn
|
||||||
(concat [::data/tuple] (replace {::data/placeholder arg} (rest tuple)))
|
(concat [::data/tuple] (replace {::data/placeholder arg} (rest tuple)))
|
||||||
ctx))}
|
ctx))}
|
||||||
|
|
||||||
(= (::data/type lfn) ::data/clj) (apply (:body lfn) (next tuple))
|
(= (::data/type lfn) ::data/clj) (apply (:body lfn) (next tuple))
|
||||||
|
|
||||||
|
@ -319,9 +377,9 @@
|
||||||
(if (contains? @ctx name)
|
(if (contains? @ctx name)
|
||||||
(throw (ex-info (str "ns name " name " is already bound") {:ast ast}))
|
(throw (ex-info (str "ns name " name " is already bound") {:ast ast}))
|
||||||
(let [ns (into
|
(let [ns (into
|
||||||
{::data/struct true ::data/type ::data/ns ::data/name name}
|
{::data/struct true ::data/type ::data/ns ::data/name name}
|
||||||
(map-values #(interpret-ast % ctx))
|
(map-values #(interpret-ast % ctx))
|
||||||
members)]
|
members)]
|
||||||
(vswap! ctx update-ctx {name ns})
|
(vswap! ctx update-ctx {name ns})
|
||||||
ns))))
|
ns))))
|
||||||
|
|
||||||
|
@ -383,8 +441,8 @@
|
||||||
(if (= (::ast/type member) ::ast/splat)
|
(if (= (::ast/type member) ::ast/splat)
|
||||||
(let [splatted (interpret-ast (:expr member) ctx)
|
(let [splatted (interpret-ast (:expr member) ctx)
|
||||||
splat-list? (and
|
splat-list? (and
|
||||||
(vector? splatted)
|
(vector? splatted)
|
||||||
(not (= (first splatted) ::data/tuple)))]
|
(not (= (first splatted) ::data/tuple)))]
|
||||||
(if splat-list?
|
(if splat-list?
|
||||||
(concat list splatted)
|
(concat list splatted)
|
||||||
(throw (ex-info "Cannot splat non-list into list" {:ast member}))))
|
(throw (ex-info "Cannot splat non-list into list" {:ast member}))))
|
||||||
|
@ -413,8 +471,8 @@
|
||||||
(if (= (::ast/type member) ::ast/splat)
|
(if (= (::ast/type member) ::ast/splat)
|
||||||
(let [splatted (interpret-ast (:expr member) ctx)
|
(let [splatted (interpret-ast (:expr member) ctx)
|
||||||
splat-map? (and
|
splat-map? (and
|
||||||
(map? splatted)
|
(map? splatted)
|
||||||
(::data/dict splatted))]
|
(::data/dict splatted))]
|
||||||
(if splat-map?
|
(if splat-map?
|
||||||
(merge dict splatted)
|
(merge dict splatted)
|
||||||
(throw (ex-info "Cannot splat non-dict into dict" {:ast member}))))
|
(throw (ex-info "Cannot splat non-dict into dict" {:ast member}))))
|
||||||
|
@ -425,8 +483,61 @@
|
||||||
(let [members (:members ast)]
|
(let [members (:members ast)]
|
||||||
(assoc (reduce (dict-term ctx) {} members) ::data/dict true)))
|
(assoc (reduce (dict-term ctx) {} members) ::data/dict true)))
|
||||||
|
|
||||||
|
(defn- interpret-receive [ast ctx]
|
||||||
|
(let [process-atom (get @processes self)
|
||||||
|
in (promise)
|
||||||
|
clauses (:clauses ast)]
|
||||||
|
;; (println "receiving in" self)
|
||||||
|
(swap! process-atom #(assoc % :in in :status :idle))
|
||||||
|
(let [msg @in]
|
||||||
|
(swap! process-atom #(assoc % :status :occupied))
|
||||||
|
;; (println "message received by" self ":" msg)
|
||||||
|
(loop [clause (first clauses)
|
||||||
|
clauses (rest clauses)]
|
||||||
|
(if clause
|
||||||
|
(let [pattern (:pattern clause)
|
||||||
|
body (:body clause)
|
||||||
|
new-ctx (volatile! {::parent ctx})
|
||||||
|
match? (match pattern msg new-ctx)
|
||||||
|
success (:success match?)
|
||||||
|
clause-ctx (:ctx match?)]
|
||||||
|
(if success
|
||||||
|
(do
|
||||||
|
(vswap! new-ctx #(merge % clause-ctx))
|
||||||
|
(let [result (interpret-ast body new-ctx)]
|
||||||
|
(swap! process-atom #(assoc % :status :idle))
|
||||||
|
result))
|
||||||
|
(recur (first clauses) (rest clauses))))
|
||||||
|
(throw (ex-info "Match Error: No match found" {:ast ast})))))))
|
||||||
|
|
||||||
|
(defn- interpret-send [ast ctx]
|
||||||
|
(let [msg (interpret-ast (:msg ast) ctx)
|
||||||
|
pid (interpret-ast (:pid ast) ctx)
|
||||||
|
process-atom (get @processes pid)
|
||||||
|
process @process-atom
|
||||||
|
q (:queue process)
|
||||||
|
status (:status process)]
|
||||||
|
(when (not (= :dead status))
|
||||||
|
(swap! process-atom #(assoc % :queue (conj q msg)))
|
||||||
|
;;(println "sent" msg "to" (:pid process))
|
||||||
|
)
|
||||||
|
msg))
|
||||||
|
|
||||||
|
(defn- interpret-spawn [ast ctx]
|
||||||
|
(let [expr (:expr ast)
|
||||||
|
process (new-process)
|
||||||
|
pid (:pid @process)]
|
||||||
|
(with-bindings {#'self pid}
|
||||||
|
(future
|
||||||
|
(try (interpret-ast expr ctx)
|
||||||
|
(catch Exception e
|
||||||
|
(println "Panic in Ludus process" (str self ":") (ex-message e))))
|
||||||
|
(swap! @process #(assoc % :status :dead))))
|
||||||
|
pid))
|
||||||
|
|
||||||
(defn interpret-ast [ast ctx]
|
(defn interpret-ast [ast ctx]
|
||||||
(case (::ast/type ast)
|
(case (::ast/type ast)
|
||||||
|
::ast/self self
|
||||||
|
|
||||||
::ast/atom (:value ast)
|
::ast/atom (:value ast)
|
||||||
|
|
||||||
|
@ -456,6 +567,12 @@
|
||||||
|
|
||||||
::ast/panic (panic ast ctx)
|
::ast/panic (panic ast ctx)
|
||||||
|
|
||||||
|
::ast/spawn (interpret-spawn ast ctx)
|
||||||
|
|
||||||
|
::ast/send (interpret-send ast ctx)
|
||||||
|
|
||||||
|
::ast/receive (interpret-receive ast ctx)
|
||||||
|
|
||||||
::ast/recur
|
::ast/recur
|
||||||
{::data/recur true :tuple (interpret-ast (:tuple ast) ctx)}
|
{::data/recur true :tuple (interpret-ast (:tuple ast) ctx)}
|
||||||
|
|
||||||
|
@ -482,8 +599,8 @@
|
||||||
::ast/tuple
|
::ast/tuple
|
||||||
(let [members (:members ast)]
|
(let [members (:members ast)]
|
||||||
(into
|
(into
|
||||||
[(if (:partial ast) ::data/partial ::data/tuple)]
|
[(if (:partial ast) ::data/partial ::data/tuple)]
|
||||||
(map #(interpret-ast % ctx)) members))
|
(map #(interpret-ast % ctx)) members))
|
||||||
|
|
||||||
::ast/list (interpret-list ast ctx)
|
::ast/list (interpret-list ast ctx)
|
||||||
|
|
||||||
|
@ -500,20 +617,32 @@
|
||||||
|
|
||||||
(defn interpret [parsed file]
|
(defn interpret [parsed file]
|
||||||
(try
|
(try
|
||||||
(let [base-ctx (volatile! (merge {:file file} prelude/prelude))]
|
(let [base-ctx (volatile! (merge {:file file} prelude/prelude))
|
||||||
(interpret-ast (::parser/ast parsed) base-ctx))
|
process (new-process)]
|
||||||
|
(start-vm)
|
||||||
|
(with-bindings {#'self (:pid @process)}
|
||||||
|
(let [result (interpret-ast (::parser/ast parsed) base-ctx)]
|
||||||
|
(swap! process #(assoc % :status :dead))
|
||||||
|
(stop-vm)
|
||||||
|
result)))
|
||||||
(catch clojure.lang.ExceptionInfo e
|
(catch clojure.lang.ExceptionInfo e
|
||||||
(println "Ludus panicked in" file)
|
(println "Ludus panicked in" file)
|
||||||
(println "On line" (get-in (ex-data e) [:ast :token ::token/line]))
|
(println "On line" (get-in (ex-data e) [:ast :token ::token/line]))
|
||||||
(println (ex-message e))
|
(println (ex-message e))
|
||||||
;;(pp/pprint (ex-data e))
|
|
||||||
(System/exit 67))))
|
(System/exit 67))))
|
||||||
|
|
||||||
(defn interpret-safe [parsed]
|
(defn interpret-safe [parsed]
|
||||||
(try
|
(try
|
||||||
(let [base-ctx (volatile! (merge {} prelude/prelude))]
|
(let [base-ctx (volatile! (merge {} prelude/prelude))
|
||||||
(interpret-ast (::parser/ast parsed) base-ctx))
|
process (new-process)]
|
||||||
|
(start-vm)
|
||||||
|
(with-bindings {#'self (:pid @process)}
|
||||||
|
(let [result (interpret-ast (::parser/ast parsed) base-ctx)]
|
||||||
|
(swap! process #(assoc % :status :dead))
|
||||||
|
result))
|
||||||
|
(stop-vm))
|
||||||
(catch clojure.lang.ExceptionInfo e
|
(catch clojure.lang.ExceptionInfo e
|
||||||
|
(stop-vm)
|
||||||
(println "Ludus panicked!")
|
(println "Ludus panicked!")
|
||||||
(println "On line" (get-in (ex-data e) [:ast :token ::token/line]))
|
(println "On line" (get-in (ex-data e) [:ast :token ::token/line]))
|
||||||
(println (ex-message e))
|
(println (ex-message e))
|
||||||
|
@ -539,9 +668,31 @@
|
||||||
(println (ex-message e))
|
(println (ex-message e))
|
||||||
{:result ::error :ctx (volatile! orig-ctx)})))))
|
{:result ::error :ctx (volatile! orig-ctx)})))))
|
||||||
|
|
||||||
(comment
|
|
||||||
|
|
||||||
(def source "panic! :oops
|
(comment
|
||||||
|
(start-vm)
|
||||||
|
(def source "
|
||||||
|
fn echo () -> {
|
||||||
|
& print (self, :echoing)
|
||||||
|
loop () with () -> {
|
||||||
|
& print (self, :looping)
|
||||||
|
receive {
|
||||||
|
msg -> {
|
||||||
|
print (\"from \", self, \": \", msg)
|
||||||
|
recur ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let echoer = spawn echo ()
|
||||||
|
|
||||||
|
send :hello to echoer
|
||||||
|
send :foo to echoer
|
||||||
|
send :bar to echoer
|
||||||
|
send :baz to echoer
|
||||||
|
|
||||||
|
sleep (1000)
|
||||||
|
|
||||||
")
|
")
|
||||||
|
|
||||||
|
@ -550,13 +701,13 @@
|
||||||
(println "*** *** NEW INTERPRETATION *** ***")
|
(println "*** *** NEW INTERPRETATION *** ***")
|
||||||
(println "")
|
(println "")
|
||||||
|
|
||||||
(-> source
|
(let [result (-> source
|
||||||
(scanner/scan)
|
(scanner/scan)
|
||||||
(parser/parse)
|
(parser/parse)
|
||||||
(interpret-safe)
|
(interpret-safe)
|
||||||
(show/show)
|
(show/show)
|
||||||
;;(println)
|
)]
|
||||||
))
|
result))
|
||||||
|
|
||||||
(comment "
|
(comment "
|
||||||
|
|
||||||
|
|
|
@ -953,11 +953,7 @@
|
||||||
|
|
||||||
(defn- parse-spawn [parser]
|
(defn- parse-spawn [parser]
|
||||||
(let [expr (parse-expr (advance parser))]
|
(let [expr (parse-expr (advance parser))]
|
||||||
(case (node-type expr)
|
(assoc expr ::ast {::ast/type ::ast/spawn :expr (::ast expr) :token (current parser)})))
|
||||||
(::ast/word ::ast/fn ::ast/synthetic)
|
|
||||||
(assoc expr ::ast {::ast/type ::ast/spawn :token (current parser) :expr (::ast expr)})
|
|
||||||
|
|
||||||
(panic parser "Expected function literal, word, or synthetic expression after spawn."))))
|
|
||||||
|
|
||||||
(defn- parse-send [parser]
|
(defn- parse-send [parser]
|
||||||
(let [msg (parse-expr (advance parser))
|
(let [msg (parse-expr (advance parser))
|
||||||
|
@ -1000,9 +996,12 @@
|
||||||
(::token/lparen ::token/keyword) (parse-synthetic parser)
|
(::token/lparen ::token/keyword) (parse-synthetic parser)
|
||||||
(parse-word parser)))
|
(parse-word parser)))
|
||||||
|
|
||||||
(::token/nil ::token/true ::token/false ::token/self)
|
(::token/nil ::token/true ::token/false)
|
||||||
(parse-atomic-word parser)
|
(parse-atomic-word parser)
|
||||||
|
|
||||||
|
(::token/self)
|
||||||
|
(assoc (advance parser) ::ast {::ast/type ::ast/self :token token})
|
||||||
|
|
||||||
::token/lparen (parse-tuple parser)
|
::token/lparen (parse-tuple parser)
|
||||||
|
|
||||||
::token/lbracket (parse-list parser)
|
::token/lbracket (parse-list parser)
|
||||||
|
@ -1067,24 +1066,54 @@
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(def pp pp/pprint)
|
(def pp pp/pprint)
|
||||||
(def source "
|
(def source1 "
|
||||||
cond { x -> x
|
fn echo () -> {
|
||||||
y -> y }
|
loop () with () -> {
|
||||||
|
receive {
|
||||||
|
msg -> {
|
||||||
|
print (msg)
|
||||||
|
recur ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& let my = spawn echo ()
|
||||||
")
|
")
|
||||||
(def lexed (scanner/scan source))
|
(def source2 "
|
||||||
(def tokens (:tokens lexed))
|
fn echo () -> {
|
||||||
(def p (parser tokens))
|
loop () with () -> {
|
||||||
|
receive {
|
||||||
|
msg -> {
|
||||||
|
print (msg)
|
||||||
|
recur ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
")
|
||||||
|
|
||||||
|
(time (do (def lexed2 (scanner/scan source2))
|
||||||
|
(def tokens2 (:tokens lexed2))
|
||||||
|
(def p2 (parser tokens2))
|
||||||
|
(def ast2 (::ast (parse-script p2)))))
|
||||||
|
|
||||||
|
(time (do (def lexed1 (scanner/scan source1))
|
||||||
|
(def tokens1 (:tokens lexed1))
|
||||||
|
(def p1 (parser tokens1))
|
||||||
|
(def ast1 (::ast (parse-script p1)))))
|
||||||
|
|
||||||
(println "")
|
(println "")
|
||||||
(println "")
|
(println "")
|
||||||
(println "******************************************************")
|
(println "******************************************************")
|
||||||
(println "")
|
(println "")
|
||||||
(println "*** *** NEW PARSE *** ***")
|
(println "*** *** TEST PARSE *** ***")
|
||||||
|
|
||||||
(-> p
|
(println "asts are the same?" (= ast1 ast2))
|
||||||
(parse-script)
|
(pp ast1)
|
||||||
(::ast)
|
(pp ast2))
|
||||||
(pp)))
|
|
||||||
|
|
||||||
(comment "
|
(comment "
|
||||||
Further thoughts/still to do:
|
Further thoughts/still to do:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
(ns ludus.prelude
|
(ns ludus.prelude
|
||||||
(:require
|
(:require
|
||||||
[ludus.data :as data]
|
[ludus.data :as data]
|
||||||
[ludus.show]))
|
[ludus.show]))
|
||||||
|
|
||||||
;; TODO: make eq, and, or special forms that short-circuit
|
;; TODO: make eq, and, or special forms that short-circuit
|
||||||
;; Right now, they evaluate all their args
|
;; Right now, they evaluate all their args
|
||||||
|
@ -75,6 +75,10 @@
|
||||||
::data/type ::data/clj
|
::data/type ::data/clj
|
||||||
:body ludus.show/show})
|
:body ludus.show/show})
|
||||||
|
|
||||||
|
(def sleep- {:name "sleep"
|
||||||
|
::data/type ::data/clj
|
||||||
|
:body (fn [ms] (Thread/sleep ms))})
|
||||||
|
|
||||||
(def prelude {"eq" eq
|
(def prelude {"eq" eq
|
||||||
"add" add
|
"add" add
|
||||||
;;"panic!" panic!
|
;;"panic!" panic!
|
||||||
|
@ -89,4 +93,6 @@
|
||||||
"deref" deref-
|
"deref" deref-
|
||||||
"set!" set!-
|
"set!" set!-
|
||||||
"and" and-
|
"and" and-
|
||||||
"or" or-})
|
"or" or-
|
||||||
|
"sleep" sleep-
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user