Clean it up, wire it up.
This commit is contained in:
parent
e276298f4e
commit
7ec258ee24
10
deps.edn
10
deps.edn
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
:deps {org.clojure/clojurescript {:mvn/version "1.11.121"}}
|
:deps {org.clojure/clojurescript {:mvn/version "1.11.121"}}
|
||||||
|
|
||||||
:aliases {:nREPL
|
:aliases
|
||||||
{:extra-deps
|
{:repl
|
||||||
{nrepl/nrepl {:mvn/version "0.9.0"}}}}
|
{:exec-fn clojure.core.server/start-server
|
||||||
|
:exec-args {:name "repl"
|
||||||
|
:port 5555
|
||||||
|
:accept clojure.core.server/repl
|
||||||
|
:server-daemon false}}}
|
||||||
}
|
}
|
5
justfile
5
justfile
|
@ -1,2 +1,3 @@
|
||||||
repl: # start a repl
|
# start a repl
|
||||||
clj -X clojure.core.server/start-server :name repl :port 5555 :accept clojure.core.server/repl :server-daemon false
|
repl:
|
||||||
|
clj -X:repl
|
|
@ -1,22 +0,0 @@
|
||||||
(ns ludus.analyzer
|
|
||||||
(:require
|
|
||||||
[ludus.ast :as ast]
|
|
||||||
[ludus.token :as token]))
|
|
||||||
|
|
||||||
(defn analyze [ast] ast)
|
|
||||||
|
|
||||||
(comment "
|
|
||||||
Here's where we do a bunch of static analysis.
|
|
||||||
Some things we might wish for:
|
|
||||||
* No unused bindings
|
|
||||||
* No unbound names
|
|
||||||
* Compound `loop` and `gen` forms must have LHS's (tuple patterns) of the same length
|
|
||||||
* Recur must be in tail position in `loop`s
|
|
||||||
* Tail call optimization for simple recursion (rewrite it as a loop?)
|
|
||||||
* Check arities for statically known functions
|
|
||||||
* Enforce single-member tuple after called keywords
|
|
||||||
* Placeholders may only appear in tuples in synthetic expressions
|
|
||||||
* Each of these may have zero or one placeholders
|
|
||||||
* Function arities are correct
|
|
||||||
* Arity of called keywords must be 1
|
|
||||||
")
|
|
|
@ -1,35 +0,0 @@
|
||||||
(ns ludus.compile
|
|
||||||
(:require
|
|
||||||
[ludus.grammar :as g]
|
|
||||||
[ludus.parser-new :as p]
|
|
||||||
[ludus.scanner :as s]))
|
|
||||||
|
|
||||||
(def source
|
|
||||||
"1"
|
|
||||||
)
|
|
||||||
|
|
||||||
(def result (->> source s/scan :tokens (p/apply-parser g/script)))
|
|
||||||
|
|
||||||
(println result)
|
|
||||||
|
|
||||||
(comment "
|
|
||||||
What sorts of compiling and validation do we want to do? Be specific.
|
|
||||||
|
|
||||||
- check used names are bound (validation)
|
|
||||||
- check bound names are free (validation)
|
|
||||||
- check `recur` is only ever in `loop` (and in `fn` bodies?), in tail position (validation)
|
|
||||||
- separate function arities into different functions (optimization)
|
|
||||||
- desugar partially applied functions (?) (simplification)
|
|
||||||
- desugar keyword entry shorthand (?) (simplification)
|
|
||||||
- flag tail calls for optimization (optimization)
|
|
||||||
- direct tail calls
|
|
||||||
- through different expressions
|
|
||||||
- block
|
|
||||||
- if
|
|
||||||
- cond
|
|
||||||
- match
|
|
||||||
- let
|
|
||||||
- check ns access (validation)
|
|
||||||
- check constraints: only use specific fns (checked against a constraint-specific ctx) (validation)
|
|
||||||
|
|
||||||
")
|
|
|
@ -1,39 +0,0 @@
|
||||||
(ns ludus.core
|
|
||||||
"A tree-walk interpreter for the Ludus language."
|
|
||||||
(:require
|
|
||||||
[ludus.scanner :as scanner]
|
|
||||||
;[ludus.parser :as parser]
|
|
||||||
[ludus.parser-new :as p]
|
|
||||||
[ludus.grammar :as g]
|
|
||||||
[ludus.interpreter :as interpreter]
|
|
||||||
[ludus.show :as show]
|
|
||||||
[clojure.pprint :as pp]
|
|
||||||
[ludus.loader :as loader]
|
|
||||||
[ludus.repl :as repl])
|
|
||||||
(:gen-class))
|
|
||||||
|
|
||||||
(defn- run [file source]
|
|
||||||
(let [scanned (scanner/scan source)]
|
|
||||||
(if (not-empty (:errors scanned))
|
|
||||||
(do
|
|
||||||
(println "I found some scanning errors!")
|
|
||||||
(pp/pprint (:errors scanned))
|
|
||||||
(System/exit 65))
|
|
||||||
(let [parsed (p/apply-parser g/script (:tokens scanned))]
|
|
||||||
(if (p/fail? parsed)
|
|
||||||
(do
|
|
||||||
(println "I found some parsing errors!")
|
|
||||||
(println (p/err-msg parsed))
|
|
||||||
(System/exit 66))
|
|
||||||
(let [interpreted (interpreter/interpret source file parsed)]
|
|
||||||
(println (show/show interpreted))
|
|
||||||
(System/exit 0)))))))
|
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(cond
|
|
||||||
(= (count args) 1)
|
|
||||||
(let [file (first args)
|
|
||||||
source (loader/load-import file)]
|
|
||||||
(run file source))
|
|
||||||
|
|
||||||
:else (repl/launch)))
|
|
39
src/ludus/core.cljc
Normal file
39
src/ludus/core.cljc
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
(ns ludus.core
|
||||||
|
(:require
|
||||||
|
[ludus.scanner :as scanner]
|
||||||
|
[ludus.parser :as parser]
|
||||||
|
[ludus.grammar :as grammar]
|
||||||
|
[ludus.interpreter :as interpreter]
|
||||||
|
[ludus.show :as show]
|
||||||
|
[clojure.pprint :as pp]
|
||||||
|
))
|
||||||
|
|
||||||
|
(println "Hi, there.")
|
||||||
|
|
||||||
|
(defn run [source]
|
||||||
|
(println (str "Running some ludus source: " source))
|
||||||
|
(let [scanned (scanner/scan source)]
|
||||||
|
(if (not-empty (:errors scanned))
|
||||||
|
(do
|
||||||
|
(println "I found some scanning errors!")
|
||||||
|
(pp/pprint (:errors scanned))
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
(let [parsed (parser/apply-parser grammar/script (:tokens scanned))]
|
||||||
|
(if (parser/fail? parsed)
|
||||||
|
(do
|
||||||
|
(println "I found some parsing errors!")
|
||||||
|
(println (parser/err-msg parsed))
|
||||||
|
nil
|
||||||
|
)
|
||||||
|
(let [interpreted (interpreter/interpret source parsed)]
|
||||||
|
(println (show/show interpreted))
|
||||||
|
interpreted
|
||||||
|
))))))
|
||||||
|
|
||||||
|
(run "
|
||||||
|
|
||||||
|
fn foo () -> :bar
|
||||||
|
|
||||||
|
foo ()
|
||||||
|
")
|
|
@ -1,3 +0,0 @@
|
||||||
(ns ludus.core)
|
|
||||||
|
|
||||||
(println "Hello, world!")
|
|
|
@ -1,32 +0,0 @@
|
||||||
(ns ludus.draw
|
|
||||||
(:require [quil.core :as q]
|
|
||||||
[quil.middleware :as m]))
|
|
||||||
|
|
||||||
(defn setup []
|
|
||||||
(q/frame-rate 60)
|
|
||||||
(q/color-mode :hsb)
|
|
||||||
{:color 0 :angle 0})
|
|
||||||
|
|
||||||
(defn update-state [state]
|
|
||||||
{:color (mod (+ (:color state) 0.7) 255)
|
|
||||||
:angle (+ (:angle state) 0.1)})
|
|
||||||
|
|
||||||
(defn draw-state [state]
|
|
||||||
(q/background 240)
|
|
||||||
(q/fill (:color state) 255 255)
|
|
||||||
(let [angle (:angle state)
|
|
||||||
x (* 150 (q/cos angle))
|
|
||||||
y (* 150 (q/sin angle))]
|
|
||||||
(q/with-translation [(/ (q/width) 2)
|
|
||||||
(/ (q/height) 2)]
|
|
||||||
(q/ellipse x y 100 100))))
|
|
||||||
|
|
||||||
(defn ludus-draw []
|
|
||||||
(q/defsketch sketch
|
|
||||||
:title "Hello Ludus"
|
|
||||||
:size [500 500]
|
|
||||||
:setup setup
|
|
||||||
:update update-state
|
|
||||||
:draw draw-state
|
|
||||||
:features []
|
|
||||||
:middleware [m/fun-mode]))
|
|
|
@ -1,6 +1,13 @@
|
||||||
(ns ludus.grammar
|
(ns ludus.grammar
|
||||||
(:require [ludus.parser-new :refer :all]
|
(:require
|
||||||
[ludus.scanner :as scan]))
|
#?(
|
||||||
|
:clj [ludus.parser :refer :all]
|
||||||
|
:cljs [ludus.parser
|
||||||
|
:refer [choice quiet one+ zero+ group order-0 order-1 flat maybe weak-order]
|
||||||
|
:refer-macros [defp]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
(declare expression pattern)
|
(declare expression pattern)
|
||||||
|
|
||||||
|
@ -276,43 +283,3 @@
|
||||||
(defp script order-0 [nls?
|
(defp script order-0 [nls?
|
||||||
(one+ script-line)
|
(one+ script-line)
|
||||||
(quiet :eof)])
|
(quiet :eof)])
|
||||||
|
|
||||||
|
|
||||||
;;; REPL
|
|
||||||
|
|
||||||
(comment
|
|
||||||
|
|
||||||
(def source
|
|
||||||
"if 1 then 2 else 3"
|
|
||||||
)
|
|
||||||
|
|
||||||
(def rule (literal))
|
|
||||||
|
|
||||||
(def tokens (-> source scan/scan :tokens))
|
|
||||||
|
|
||||||
(def result (apply-parser script tokens))
|
|
||||||
|
|
||||||
|
|
||||||
(defn report [node]
|
|
||||||
(when (fail? node) (err-msg node))
|
|
||||||
node)
|
|
||||||
|
|
||||||
(defn clean [node]
|
|
||||||
(if (map? node)
|
|
||||||
(-> node
|
|
||||||
(report)
|
|
||||||
(dissoc
|
|
||||||
;:status
|
|
||||||
:remaining
|
|
||||||
:token)
|
|
||||||
(update :data #(into [] (map clean) %)))
|
|
||||||
node))
|
|
||||||
|
|
||||||
(defn tap [x] (println "\n\n\n\n******NEW RUN\n\n:::=> " x "\n\n") x)
|
|
||||||
|
|
||||||
(def my-data (-> result
|
|
||||||
clean
|
|
||||||
tap
|
|
||||||
))
|
|
||||||
|
|
||||||
(println my-data))
|
|
|
@ -1,21 +1,16 @@
|
||||||
(ns ludus.interpreter
|
(ns ludus.interpreter
|
||||||
(:require
|
(:require
|
||||||
[ludus.parser :as parser]
|
[ludus.parser :as p]
|
||||||
[ludus.parser-new :as p]
|
|
||||||
[ludus.grammar :as g]
|
[ludus.grammar :as g]
|
||||||
[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.loader :as loader]
|
||||||
[ludus.loader :as loader]
|
[clojure.pprint :as pp]
|
||||||
[ludus.token :as token]
|
|
||||||
[ludus.process :as process]
|
|
||||||
[clojure.set]
|
[clojure.set]
|
||||||
[clojure.string]))
|
[clojure.string]))
|
||||||
|
|
||||||
(def ^:dynamic self @process/current-pid)
|
|
||||||
|
|
||||||
;; right now this is not very efficient:
|
;; right now this is not very efficient:
|
||||||
;; it's got runtime checking
|
;; it's got runtime checking
|
||||||
;; we should be able to do these checks statically
|
;; we should be able to do these checks statically
|
||||||
|
@ -593,30 +588,30 @@
|
||||||
(let [[k v] kv]
|
(let [[k v] kv]
|
||||||
[k (f v)]))))
|
[k (f v)]))))
|
||||||
|
|
||||||
(defn- interpret-import [ast ctx]
|
; (defn- interpret-import [ast ctx]
|
||||||
(let [data (:data ast)
|
; (let [data (:data ast)
|
||||||
path (-> data first :data first)
|
; path (-> data first :data first)
|
||||||
name (-> data second :data first)
|
; name (-> data second :data first)
|
||||||
file (ludus-resolve :file ctx)
|
; file (ludus-resolve :file ctx)
|
||||||
from (if (= ::not-found file) :cwd file)]
|
; from (if (= ::not-found file) :cwd file)]
|
||||||
(if (contains? @ctx name)
|
; (if (contains? @ctx name)
|
||||||
(throw (ex-info (str "Name " name " is alrady bound") {:ast ast}))
|
; (throw (ex-info (str "Name " name " is alrady bound") {:ast ast}))
|
||||||
(let [source (try
|
; (let [source (try
|
||||||
(loader/load-import path from)
|
; (loader/load-import path from)
|
||||||
(catch Exception e
|
; (catch Exception e
|
||||||
(if (::loader/error (ex-data e))
|
; (if (::loader/error (ex-data e))
|
||||||
(throw (ex-info (ex-message e) {:ast ast}))
|
; (throw (ex-info (ex-message e) {:ast ast}))
|
||||||
(throw e))))
|
; (throw e))))
|
||||||
parsed (->> source (scanner/scan) :tokens (p/apply-parser g/script))]
|
; parsed (->> source (scanner/scan) :tokens (p/apply-parser g/script))]
|
||||||
(if (p/fail? parsed)
|
; (if (p/fail? parsed)
|
||||||
(throw (ex-info
|
; (throw (ex-info
|
||||||
(str "Parse error in file " path "\n"
|
; (str "Parse error in file " path "\n"
|
||||||
(p/err-msg parsed))
|
; (p/err-msg parsed))
|
||||||
{:ast ast}))
|
; {:ast ast}))
|
||||||
(let [interpret-result (interpret-file source path parsed)]
|
; (let [interpret-result (interpret-file source path parsed)]
|
||||||
(vswap! ctx update-ctx {name interpret-result})
|
; (vswap! ctx update-ctx {name interpret-result})
|
||||||
interpret-result))
|
; interpret-result))
|
||||||
))))
|
; ))))
|
||||||
|
|
||||||
(defn- interpret-ref [ast ctx]
|
(defn- interpret-ref [ast ctx]
|
||||||
(let [data (:data ast)
|
(let [data (:data ast)
|
||||||
|
@ -757,70 +752,9 @@
|
||||||
(vswap! ctx update-ctx {name ns})
|
(vswap! ctx update-ctx {name ns})
|
||||||
ns))))
|
ns))))
|
||||||
|
|
||||||
;; TODO: update this to use new AST representation
|
|
||||||
(defn- interpret-receive [ast ctx]
|
|
||||||
(let [process-atom (get @process/processes self)
|
|
||||||
inbox (promise)
|
|
||||||
clauses (:clauses ast)]
|
|
||||||
;; (println "receiving in" self)
|
|
||||||
(swap! process-atom #(assoc % :inbox inbox :status :idle))
|
|
||||||
;; (println "awaiting message in" self)
|
|
||||||
(let [msg @inbox]
|
|
||||||
(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})))))))
|
|
||||||
|
|
||||||
;; TODO: update send to be a function (here or in prelude)
|
|
||||||
(defn- interpret-send [ast ctx]
|
|
||||||
(let [msg (interpret-ast (:msg ast) ctx)
|
|
||||||
pid (interpret-ast (:pid ast) ctx)
|
|
||||||
process-atom (get @process/processes pid)
|
|
||||||
process @process-atom
|
|
||||||
q (:queue process)
|
|
||||||
status (:status process)]
|
|
||||||
(when (not (= :dead status))
|
|
||||||
(swap! process-atom #(assoc % :queue (conj q msg)))
|
|
||||||
(Thread/sleep 1) ;; this is terrible--but it avoids deadlock
|
|
||||||
;;TODO: actually debug this?
|
|
||||||
;;THOUGHT: is swap! returning before the value is actually changed? Clojure docs say atoms are synchronous
|
|
||||||
)
|
|
||||||
msg))
|
|
||||||
|
|
||||||
(defn- interpret-spawn [ast ctx]
|
|
||||||
(let [expr (-> ast :data first)
|
|
||||||
process (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))
|
|
||||||
;; (pp/pprint (ex-data e))
|
|
||||||
(println "On line" (get-in (ex-data e) [:ast :token ::token/line]) "in" (ludus-resolve :file ctx))))
|
|
||||||
(swap! process #(assoc % :status :dead))))
|
|
||||||
pid))
|
|
||||||
|
|
||||||
(defn- interpret-literal [ast] (-> ast :data first))
|
(defn- interpret-literal [ast] (-> ast :data first))
|
||||||
|
|
||||||
(defn interpret-ast [ast ctx]
|
(defn interpret-ast [ast ctx]
|
||||||
;(println "interpreting ast type" (:type ast))
|
|
||||||
;(println "AST: " ast)
|
|
||||||
(case (:type ast)
|
(case (:type ast)
|
||||||
|
|
||||||
(:nil :true :false :number :string :keyword) (interpret-literal ast)
|
(:nil :true :false :number :string :keyword) (interpret-literal ast)
|
||||||
|
@ -845,16 +779,12 @@
|
||||||
|
|
||||||
:ns-expr (interpret-ns ast ctx)
|
:ns-expr (interpret-ns ast ctx)
|
||||||
|
|
||||||
:import-expr (interpret-import ast ctx)
|
;; :import-expr (interpret-import ast ctx)
|
||||||
|
|
||||||
:ref-expr (interpret-ref ast ctx)
|
:ref-expr (interpret-ref ast ctx)
|
||||||
|
|
||||||
:when-expr (interpret-ast (-> ast :data first) ctx)
|
:when-expr (interpret-ast (-> ast :data first) ctx)
|
||||||
|
|
||||||
; ::ast/spawn (interpret-spawn ast ctx)
|
|
||||||
|
|
||||||
; ::ast/receive (interpret-receive ast ctx)
|
|
||||||
|
|
||||||
:recur-call
|
:recur-call
|
||||||
{::data/recur true :args (interpret-ast (-> ast :data first) ctx)}
|
{::data/recur true :args (interpret-ast (-> ast :data first) ctx)}
|
||||||
|
|
||||||
|
@ -899,99 +829,29 @@
|
||||||
(clojure.string/trim (nth lines (dec line))))))
|
(clojure.string/trim (nth lines (dec line))))))
|
||||||
|
|
||||||
;; TODO: update this to use new parser pipeline & new AST representation
|
;; TODO: update this to use new parser pipeline & new AST representation
|
||||||
(defn interpret-file [source path parsed]
|
; (defn interpret-file [source path parsed]
|
||||||
|
; (try
|
||||||
|
; (let [base-ctx (volatile! {::parent (volatile! prelude/prelude) :file path})]
|
||||||
|
; (interpret-ast parsed base-ctx))
|
||||||
|
; (catch clojure.lang.ExceptionInfo e
|
||||||
|
; (println "Ludus panicked in" path)
|
||||||
|
; (println "On line" (get-in (ex-data e) [:ast :token :line]))
|
||||||
|
; (println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
||||||
|
; (println (ex-message e))
|
||||||
|
; (System/exit 67))))
|
||||||
|
|
||||||
|
;; TODO: update this to use new parser pipeline & new AST representation
|
||||||
|
(defn interpret [source parsed]
|
||||||
(try
|
(try
|
||||||
(let [base-ctx (volatile! {::parent (volatile! prelude/prelude) :file path})]
|
(let [base-ctx (volatile! {::parent (volatile! prelude/prelude)})]
|
||||||
(interpret-ast parsed base-ctx))
|
(interpret-ast parsed base-ctx))
|
||||||
(catch clojure.lang.ExceptionInfo e
|
(catch clojure.lang.ExceptionInfo e
|
||||||
(println "Ludus panicked in" path)
|
(println "Ludus panicked!")
|
||||||
(println "On line" (get-in (ex-data e) [:ast :token :line]))
|
(println "On line" (get-in (ex-data e) [:ast :token :line]))
|
||||||
(println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
(println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
||||||
(println (ex-message e))
|
(println (ex-message e))
|
||||||
(System/exit 67))))
|
(pp/pprint (ex-data e)
|
||||||
|
;;(System/exit 67)
|
||||||
;; TODO: update this to use new parser pipeline & new AST representation
|
))))
|
||||||
(defn interpret [source path parsed]
|
|
||||||
(try
|
|
||||||
(let [base-ctx (volatile! {::parent (volatile! prelude/prelude) :file path})
|
|
||||||
process (process/new-process)]
|
|
||||||
(process/start-vm)
|
|
||||||
(with-bindings {#'self (:pid @process)}
|
|
||||||
(let [result (interpret-ast parsed base-ctx)]
|
|
||||||
(swap! process #(assoc % :status :dead))
|
|
||||||
(process/stop-vm)
|
|
||||||
result)))
|
|
||||||
(catch clojure.lang.ExceptionInfo e
|
|
||||||
(println "Ludus panicked in" path)
|
|
||||||
(println "On line" (get-in (ex-data e) [:ast :token :line]))
|
|
||||||
(println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
|
||||||
(println (ex-message e))
|
|
||||||
(System/exit 67))))
|
|
||||||
|
|
||||||
(defn interpret-safe [parsed]
|
|
||||||
(try
|
|
||||||
(let [base-ctx (volatile! {::parent (volatile! prelude/prelude)})
|
|
||||||
process (process/new-process)]
|
|
||||||
(process/start-vm)
|
|
||||||
(with-bindings {#'self (:pid @process)}
|
|
||||||
(let [result (interpret-ast parsed base-ctx)]
|
|
||||||
(swap! process #(assoc % :status :dead))
|
|
||||||
(process/stop-vm)
|
|
||||||
result)))
|
|
||||||
(catch clojure.lang.ExceptionInfo e
|
|
||||||
(process/stop-vm)
|
|
||||||
(println "Ludus panicked on line " (get-in (ex-data e) [:ast :token :line]))
|
|
||||||
(println "> " (get-in (ex-data e) [:ast :token]))
|
|
||||||
(println (ex-message e))
|
|
||||||
;(pp/pprint (ex-data e))
|
|
||||||
)))
|
|
||||||
|
|
||||||
;; TODO: update this to use new parser pipeline & new AST representation
|
|
||||||
(defn interpret-repl
|
|
||||||
([parsed ctx]
|
|
||||||
(let [orig-ctx @ctx
|
|
||||||
process (process/new-process)
|
|
||||||
pid (:pid @process)]
|
|
||||||
(try
|
|
||||||
(process/start-vm)
|
|
||||||
(with-bindings {#'self pid}
|
|
||||||
(let [result (interpret-ast parsed ctx)]
|
|
||||||
{:result result :ctx ctx :pid pid}))
|
|
||||||
(catch clojure.lang.ExceptionInfo e
|
|
||||||
(println "Ludus panicked!")
|
|
||||||
(println (ex-message e))
|
|
||||||
{:result :error :ctx (volatile! orig-ctx) :pid pid}))))
|
|
||||||
([parsed ctx pid]
|
|
||||||
(let [orig-ctx @ctx]
|
|
||||||
(try
|
|
||||||
(process/start-vm)
|
|
||||||
(with-bindings {#'self pid}
|
|
||||||
(let [result (interpret-ast parsed ctx)]
|
|
||||||
{:result result :ctx ctx :pid pid}))
|
|
||||||
(catch clojure.lang.ExceptionInfo e
|
|
||||||
(println "Ludus panicked!")
|
|
||||||
(println (ex-message e))
|
|
||||||
{:result :error :ctx (volatile! orig-ctx) :pid pid}
|
|
||||||
)))))
|
|
||||||
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(def source "
|
|
||||||
let 2 = 1
|
|
||||||
")
|
|
||||||
|
|
||||||
(println "")
|
|
||||||
(println "****************************************")
|
|
||||||
(println "*** *** NEW INTERPRETATION *** ***")
|
|
||||||
(println "")
|
|
||||||
|
|
||||||
(let [result (->> source
|
|
||||||
scanner/scan
|
|
||||||
:tokens
|
|
||||||
(p/apply-parser g/script)
|
|
||||||
interpret-safe
|
|
||||||
show/show
|
|
||||||
)]
|
|
||||||
(println result)
|
|
||||||
result))
|
|
||||||
|
|
||||||
|
(+ 1 2)
|
|
@ -1,38 +0,0 @@
|
||||||
(ns ludus.interpreter-new
|
|
||||||
(:require
|
|
||||||
[ludus.grammar :as g]
|
|
||||||
[ludus.parser-new :as p]
|
|
||||||
[ludus.scanner :as s]))
|
|
||||||
|
|
||||||
(def source
|
|
||||||
"(1, 2)
|
|
||||||
"
|
|
||||||
)
|
|
||||||
|
|
||||||
(def tokens (-> source s/scan :tokens))
|
|
||||||
|
|
||||||
(def result (p/apply-parser g/script tokens))
|
|
||||||
|
|
||||||
(-> result :data)
|
|
||||||
|
|
||||||
(defn report [node]
|
|
||||||
(when (p/fail? node) (p/err-msg node))
|
|
||||||
node)
|
|
||||||
|
|
||||||
(defn clean [node]
|
|
||||||
(if (map? node)
|
|
||||||
(-> node
|
|
||||||
(report)
|
|
||||||
(dissoc
|
|
||||||
;:status
|
|
||||||
:remaining
|
|
||||||
:token)
|
|
||||||
(update :data #(into [] (map clean) %)))
|
|
||||||
node))
|
|
||||||
|
|
||||||
(defn tap [x] (println "\n\n\n\n******NEW RUN\n\n:::=> " x "\n\n") x)
|
|
||||||
|
|
||||||
(def my-data (-> result
|
|
||||||
clean
|
|
||||||
tap
|
|
||||||
))
|
|
|
@ -1,16 +0,0 @@
|
||||||
(ns ludus.loader
|
|
||||||
(:require [babashka.fs :as fs]))
|
|
||||||
|
|
||||||
(defn cwd [] (fs/cwd))
|
|
||||||
|
|
||||||
(defn load-import
|
|
||||||
([file]
|
|
||||||
(let [path (-> file (fs/canonicalize) (fs/file))]
|
|
||||||
(try (slurp path)
|
|
||||||
(catch java.io.FileNotFoundException _
|
|
||||||
(throw (ex-info (str "File " path " not found") {:path path ::error true}))))))
|
|
||||||
([file from]
|
|
||||||
(load-import
|
|
||||||
(fs/path
|
|
||||||
(if (= from :cwd) (fs/cwd) (fs/parent (fs/canonicalize from)))
|
|
||||||
(fs/path file)))))
|
|
|
@ -1,135 +0,0 @@
|
||||||
(*
|
|
||||||
ludus.ebnf
|
|
||||||
|
|
||||||
An Instaparse-style EBNF grammer for Ludus.
|
|
||||||
*)
|
|
||||||
|
|
||||||
script = <wsnl?> toplevel <ws?> {<terminator> <ws?> toplevel <ws?>} <wsnl?>
|
|
||||||
|
|
||||||
terminator = (";" | <{comment}> "\n")+
|
|
||||||
|
|
||||||
ws = (" " | "\t" | "\r")+
|
|
||||||
|
|
||||||
wsnl = (ws | <{comment}> "\n")+
|
|
||||||
|
|
||||||
reserved = "cond" | "let" | "if" | "then" | "else" | "nil" | "true" | "false" | "as" | "match" | "with" | "NaN" | "recur"
|
|
||||||
|
|
||||||
comment = "&" not_nl*
|
|
||||||
|
|
||||||
not_nl = #"[^\n]"
|
|
||||||
|
|
||||||
toplevel = expression | import | test | ns
|
|
||||||
|
|
||||||
test = <"test" ws> string <ws> expression
|
|
||||||
|
|
||||||
import = <"import" ws> string <ws "as" ws> name
|
|
||||||
|
|
||||||
ns = <"ns" ws> name <ws? "{" wsnl?> entries <wsnl? "}">
|
|
||||||
|
|
||||||
entries = [(name | entry) {<separator> [(name | entry)]}]
|
|
||||||
|
|
||||||
expression = if | cond | let | tuple | atom | synthetic | block | match | fn | do | loop | dict | struct | list | ref | spawn | send | receive | repeat
|
|
||||||
|
|
||||||
(* TODO: is this right? *)
|
|
||||||
repeat = <"repeat" ws> (number | name) <ws> fn_clause
|
|
||||||
|
|
||||||
spawn = <"spawn" ws> expression
|
|
||||||
|
|
||||||
receive = <"receive" ws? "{" wsnl?> match_clause {terminator <ws?> [match_clause]} <wsnl? "}">
|
|
||||||
|
|
||||||
ref = <"ref" ws> name <ws? "=" ws?> expression
|
|
||||||
|
|
||||||
loop = <"loop" ws> tuple <ws "with" ws> (fn_clause
|
|
||||||
| (<"{" wsnl?> fn_clause {terminator <ws?> [fn_clause]} <wsnl? "}">))
|
|
||||||
|
|
||||||
do = <"do" ws> expression {<pipe> expression}
|
|
||||||
|
|
||||||
pipe = wsnl? "|>" wsnl?
|
|
||||||
|
|
||||||
fn = lambda | named | complex
|
|
||||||
|
|
||||||
lambda = <"fn" ws?> fn_clause
|
|
||||||
|
|
||||||
named = <"fn" ws?> name <ws> fn_clause
|
|
||||||
|
|
||||||
complex = <"fn" ws?> name <ws?> "{" <wsnl?> string? <wsnl> fn_clause {terminator <ws?> [fn_clause]} <wsnl? "}">
|
|
||||||
|
|
||||||
fn_clause = tuple_pattern <arrow> expression
|
|
||||||
|
|
||||||
match = <"match" ws> expression <ws "with" ws? "{" wsnl?> match_clause {terminator <ws?> [match_clause]} <wsnl? "}">
|
|
||||||
|
|
||||||
match_clause = pattern constraint? <arrow> expression
|
|
||||||
|
|
||||||
constraint = <"when" ws> expression
|
|
||||||
|
|
||||||
let = <"let" ws> pattern <ws "=" wsnl> expression
|
|
||||||
|
|
||||||
pattern = tuple_pattern | atom | placeholder | "else" | splattern
|
|
||||||
|
|
||||||
tuple_pattern = <"(" wsnl?> [pattern {<separator> [pattern]}] <{separator} ws? ")">
|
|
||||||
|
|
||||||
struct_pattern = <"@{" wsnl?> [(name | pattern_entry | splattern) {<separator> [(name | pattern_entry | splattern)]}] <{separator} ws? "}">
|
|
||||||
|
|
||||||
dict_pattern = <"#{" wsnl?> [(name | pattern_entry | splattern) {<separator> [(name | pattern_entry | splattern)]}] <{separator} ws? "}">
|
|
||||||
|
|
||||||
pattern_entry = keyword <ws> pattern
|
|
||||||
|
|
||||||
splattern = <"..."> name | ignored | placeholder
|
|
||||||
|
|
||||||
block = <"{" wsnl?> expression {<terminator ws?> expression <ws?>} <wsnl? "}">
|
|
||||||
|
|
||||||
cond = "cond" <ws> expression <ws? "{" wsnl?> cond_clause {terminator <ws?> [cond_clause]} <wsnl? "}">
|
|
||||||
|
|
||||||
cond_clause = expression <arrow> expression
|
|
||||||
|
|
||||||
arrow = <ws? "->" wsnl?>
|
|
||||||
|
|
||||||
if = <"if" ws> expression <wsnl "then" ws> expression <wsnl> <"else" ws> expression
|
|
||||||
|
|
||||||
synthetic = (name | keyword | recur) ((<ws?> (args | keyword))+)
|
|
||||||
|
|
||||||
recur = <"recur">
|
|
||||||
|
|
||||||
separator = <ws?> ("," | "\n") <ws?>
|
|
||||||
|
|
||||||
args = <"(" ws? {separator}> [arg_expr {<separator> [arg_expr]}] <{separator} ws? ")">
|
|
||||||
|
|
||||||
arg_expr = expression | placeholder
|
|
||||||
|
|
||||||
placeholder = <"_">
|
|
||||||
|
|
||||||
tuple = <"(" wsnl?> [expression {<separator> [expression]}] <{separator} ws? ")">
|
|
||||||
|
|
||||||
list = <"[" wsnl?> [(expression | splat) {<separator> [(expression | splat)]}] <{separator} ws? "]">
|
|
||||||
|
|
||||||
struct = <"@{" wsnl?> [(name | entry) {<separator> [(name | entry)]}] <{separator} ws? "}">
|
|
||||||
|
|
||||||
dict = <"#{" wsnl?> [(name | entry | splat) {<separator> [(name | entry | splat)]}] <{separator} ws? "}">
|
|
||||||
|
|
||||||
entry = keyword <ws> expression
|
|
||||||
|
|
||||||
splat = <"..."> name
|
|
||||||
|
|
||||||
atom = name | ignored | keyword | number | string | boolean | nil
|
|
||||||
|
|
||||||
boolean = true | false
|
|
||||||
|
|
||||||
true = <"true">
|
|
||||||
|
|
||||||
false = <"false">
|
|
||||||
|
|
||||||
nil = <"nil">
|
|
||||||
|
|
||||||
string = <'"'> {escaped_quote | nonquote} <'"'>
|
|
||||||
|
|
||||||
escaped_quote = "\\" '\"'
|
|
||||||
nonquote = #'[^"]'
|
|
||||||
|
|
||||||
keyword = #":[a-zA-Z][a-zA-Z0-9\/\-_!\*\?]*"
|
|
||||||
ignored = #"_[a-z][a-zA-Z0-9\/\-_!\*\?]*"
|
|
||||||
|
|
||||||
name = !reserved #"[a-z][a-zA-Z0-9\/\-_!\*\?]*"
|
|
||||||
|
|
||||||
(* TODO: Debug this to reject things starting with 0, eg 012. *)
|
|
||||||
number = #"\-?[1-9][0-9]*" | #"\-?(0|[1-9][0-9]*).[0-9]+" | ["-"] "0" | "NaN"
|
|
||||||
|
|
1279
src/ludus/parser.clj
1279
src/ludus/parser.clj
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
(ns ludus.parser-new)
|
(ns ludus.parser)
|
||||||
|
|
||||||
(defn ? [val default] (if (nil? val) default val))
|
(defn ? [val default] (if (nil? val) default val))
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
(keyword? parser) (apply-kw-parser parser tokens)
|
(keyword? parser) (apply-kw-parser parser tokens)
|
||||||
(:rule parser) (apply-fn-parser parser tokens)
|
(:rule parser) (apply-fn-parser parser tokens)
|
||||||
(fn? parser) (apply-fn-parser (parser) tokens)
|
(fn? parser) (apply-fn-parser (parser) tokens)
|
||||||
:else (throw (Exception. "`apply-parser` requires a parser")))]
|
:else (throw (ex-info "`apply-parser` requires a parser" {})))]
|
||||||
;(println "Parser result " (? (:name parser) parser) (:status result))
|
;(println "Parser result " (? (:name parser) parser) (:status result))
|
||||||
result
|
result
|
||||||
))
|
))
|
||||||
|
@ -314,21 +314,3 @@
|
||||||
(let [arg (last items)
|
(let [arg (last items)
|
||||||
fns (into [] (butlast items))]
|
fns (into [] (butlast items))]
|
||||||
`(defn ~name [] ((apply comp ~fns) (keyword '~name) ~arg))))
|
`(defn ~name [] ((apply comp ~fns) (keyword '~name) ~arg))))
|
||||||
|
|
||||||
(macroexpand '(defp foo group choice [:one :two]))
|
|
||||||
|
|
||||||
(comment (defp foo quiet choice [:one :two])
|
|
||||||
|
|
||||||
(def group-choice (apply comp '(group choice)))
|
|
||||||
|
|
||||||
(group-choice :thing [:a :b])
|
|
||||||
|
|
||||||
((apply comp [group choice]) :foo [:one :two])
|
|
||||||
|
|
||||||
(fn? foo)
|
|
||||||
|
|
||||||
foo
|
|
||||||
|
|
||||||
(keyword 'foo)
|
|
||||||
|
|
||||||
(foo))
|
|
|
@ -1,96 +0,0 @@
|
||||||
(ns ludus.process
|
|
||||||
(:require
|
|
||||||
[ludus.data :as data])
|
|
||||||
(:import (java.util.concurrent Executors)))
|
|
||||||
|
|
||||||
;; virtual thread patch from https://ales.rocks/notes-on-virtual-threads-and-clojure
|
|
||||||
(defn- thread-factory [name]
|
|
||||||
(-> (Thread/ofVirtual)
|
|
||||||
(.name name 0)
|
|
||||||
(.factory)))
|
|
||||||
|
|
||||||
(set-agent-send-off-executor!
|
|
||||||
(Executors/newThreadPerTaskExecutor
|
|
||||||
(thread-factory "ludus-vthread-")))
|
|
||||||
|
|
||||||
(def processes (atom {}))
|
|
||||||
|
|
||||||
(def current-pid (atom 1001))
|
|
||||||
|
|
||||||
(defn new-process []
|
|
||||||
(let [pid @current-pid
|
|
||||||
process (atom {:pid pid
|
|
||||||
:queue clojure.lang.PersistentQueue/EMPTY
|
|
||||||
:inbox 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- map-values [m f] (into {} (map (fn [[k v]] [k (f v)])) m))
|
|
||||||
|
|
||||||
(defn process-msg [process]
|
|
||||||
;;(println "processing message" self)
|
|
||||||
(let [q (:queue process)
|
|
||||||
inbox (:inbox process)]
|
|
||||||
(when (not (realized? inbox))
|
|
||||||
;;(println "delivering message in" self)
|
|
||||||
(deliver inbox (peek q))
|
|
||||||
(assoc process :queue (pop q) :inbox nil))))
|
|
||||||
|
|
||||||
(defn run-process [process-atom]
|
|
||||||
(let [process @process-atom
|
|
||||||
status (:status process)
|
|
||||||
q (:queue process)
|
|
||||||
inbox (:inbox process)]
|
|
||||||
;;(println "running process" self ":" (into [] q))
|
|
||||||
(when (and (= status :idle) (not-empty q) inbox)
|
|
||||||
(swap! process-atom process-msg))))
|
|
||||||
|
|
||||||
(defn start-vm []
|
|
||||||
;; (println "Starting Ludus VM")
|
|
||||||
(when (= @vm-state :stopped)
|
|
||||||
(future
|
|
||||||
(reset! vm-state :running)
|
|
||||||
(loop []
|
|
||||||
(when (= @vm-state :running)
|
|
||||||
(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)
|
|
||||||
|
|
||||||
(def process {"process" {
|
|
||||||
::data/struct true
|
|
||||||
::data/type ::data/ns
|
|
||||||
::data/name "process"
|
|
||||||
|
|
||||||
"list" {::data/type ::data/clj
|
|
||||||
:name "list"
|
|
||||||
:body (fn [] (into [] (keys @processes)))}
|
|
||||||
|
|
||||||
"info" {::data/type ::data/clj
|
|
||||||
:name "info"
|
|
||||||
:body (fn [pid]
|
|
||||||
(let [process @(get @processes pid)
|
|
||||||
queue (into [] (:queue process))]
|
|
||||||
(assoc process :queue queue ::data/dict true)))}
|
|
||||||
|
|
||||||
"flush" {::data/type ::data/clj
|
|
||||||
:name "flush"
|
|
||||||
:body (fn [pid]
|
|
||||||
(let [process (get @processes pid)
|
|
||||||
queue (into [] (:queue @process))]
|
|
||||||
(swap! process #(assoc % :queue clojure.lang.PersistentQueue/EMPTY))
|
|
||||||
queue))}
|
|
||||||
}})
|
|
|
@ -1,124 +0,0 @@
|
||||||
(ns ludus.repl
|
|
||||||
(:require
|
|
||||||
[ludus.scanner :as scanner]
|
|
||||||
;[ludus.parser :as parser]
|
|
||||||
[ludus.parser-new :as p]
|
|
||||||
[ludus.grammar :as g]
|
|
||||||
[ludus.interpreter :as interpreter]
|
|
||||||
[ludus.prelude :as prelude]
|
|
||||||
[ludus.show :as show]
|
|
||||||
[ludus.data :as data]
|
|
||||||
;[ludus.process :as process]
|
|
||||||
))
|
|
||||||
|
|
||||||
(declare repl-prelude new-session)
|
|
||||||
|
|
||||||
(def sessions (atom {}))
|
|
||||||
|
|
||||||
(def current-session (atom nil))
|
|
||||||
|
|
||||||
(def prompt "=> ")
|
|
||||||
|
|
||||||
(defn- exit []
|
|
||||||
(println "\nGoodbye!")
|
|
||||||
(System/exit 0))
|
|
||||||
|
|
||||||
(def base-ctx (merge prelude/prelude ;process/process
|
|
||||||
{::repl true
|
|
||||||
"repl"
|
|
||||||
{::data/struct true
|
|
||||||
::data/type ::data/ns
|
|
||||||
::data/name "repl"
|
|
||||||
|
|
||||||
:flush
|
|
||||||
{:name "flush"
|
|
||||||
::data/type ::data/clj
|
|
||||||
:body (fn
|
|
||||||
([]
|
|
||||||
(let [session @current-session]
|
|
||||||
(swap! session #(assoc % :ctx (volatile! base-ctx)))
|
|
||||||
:ok))
|
|
||||||
([name]
|
|
||||||
(if-let [session (get @sessions name)]
|
|
||||||
(do
|
|
||||||
(swap! session #(assoc % :ctx (volatile! base-ctx)))
|
|
||||||
:ok)
|
|
||||||
(do
|
|
||||||
(println "No session named" name)
|
|
||||||
:error))))}
|
|
||||||
|
|
||||||
:new
|
|
||||||
{:name "new"
|
|
||||||
::data/type ::data/clj
|
|
||||||
:body (fn [name]
|
|
||||||
(let [session (new-session name)]
|
|
||||||
(reset! current-session session)
|
|
||||||
:ok))}
|
|
||||||
|
|
||||||
:switch
|
|
||||||
{:name "switch"
|
|
||||||
::data/type ::data/clj
|
|
||||||
:body (fn [name]
|
|
||||||
(if-let [session (get @sessions name)]
|
|
||||||
(do
|
|
||||||
(reset! current-session session)
|
|
||||||
:ok)
|
|
||||||
(do
|
|
||||||
(println "No session named" name)
|
|
||||||
:error)))}
|
|
||||||
|
|
||||||
:quit
|
|
||||||
{:name "quit"
|
|
||||||
::data/type ::data/clj
|
|
||||||
:body (fn [] (exit))}
|
|
||||||
}}))
|
|
||||||
|
|
||||||
(defn- new-session [name]
|
|
||||||
(let [session (atom {:name name
|
|
||||||
:ctx (volatile! base-ctx)
|
|
||||||
:history []})]
|
|
||||||
(swap! sessions #(assoc % name session))
|
|
||||||
session))
|
|
||||||
|
|
||||||
(defn repl-loop []
|
|
||||||
(let [session-atom @current-session
|
|
||||||
session @session-atom
|
|
||||||
orig-ctx (:ctx session)
|
|
||||||
pid (:pid session)]
|
|
||||||
(print (str (:name session) prompt))
|
|
||||||
(flush)
|
|
||||||
(let [input (read-line)]
|
|
||||||
(cond
|
|
||||||
(= nil input) (exit)
|
|
||||||
|
|
||||||
(= "" input) (recur)
|
|
||||||
|
|
||||||
:else
|
|
||||||
(let [parsed (->> input
|
|
||||||
(scanner/scan)
|
|
||||||
:tokens
|
|
||||||
(p/apply-parser g/script))]
|
|
||||||
(if (= :err (:status parsed))
|
|
||||||
(do
|
|
||||||
(println (p/err-msg parsed))
|
|
||||||
(recur))
|
|
||||||
(let [{result :result ctx :ctx pid- :pid}
|
|
||||||
(if pid
|
|
||||||
(interpreter/interpret-repl parsed orig-ctx pid)
|
|
||||||
(interpreter/interpret-repl parsed orig-ctx))]
|
|
||||||
(if (= result :error)
|
|
||||||
(recur)
|
|
||||||
(do
|
|
||||||
(println (show/show result))
|
|
||||||
(when (not (= @ctx @orig-ctx))
|
|
||||||
(swap! session-atom #(assoc % :ctx ctx)))
|
|
||||||
(when (not (= pid pid-))
|
|
||||||
(swap! session-atom #(assoc % :pid pid-)))
|
|
||||||
(recur))))))))))
|
|
||||||
|
|
||||||
(defn launch []
|
|
||||||
(println "Welcome to Ludus (v. 0.1.0-alpha)")
|
|
||||||
(let [session (new-session :ludus)]
|
|
||||||
(reset! current-session session)
|
|
||||||
(repl-loop)))
|
|
||||||
|
|
|
@ -181,7 +181,7 @@
|
||||||
interpolate? false]
|
interpolate? false]
|
||||||
(let [char (current-char scanner)]
|
(let [char (current-char scanner)]
|
||||||
(case char
|
(case char
|
||||||
\{ (recur (update (advance scanner)) (str string char) true)
|
\{ (recur (advance scanner) (str string char) true)
|
||||||
; allow multiline strings
|
; allow multiline strings
|
||||||
\newline (recur (update (advance scanner) :line inc) (str string char) interpolate?)
|
\newline (recur (update (advance scanner) :line inc) (str string char) interpolate?)
|
||||||
\" (if interpolate?
|
\" (if interpolate?
|
Loading…
Reference in New Issue
Block a user