Skip to content

Commit

Permalink
Beginnings of a Node REPL (squint-cljs#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
borkdude authored Sep 19, 2022
1 parent a0f7431 commit 91bbafc
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 362 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ jobs:
run: |
npm install
bb test
bb bb/node_repl_test.clj
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ scratch.cljs
.vscode
.nrepl-port
examples/quickjs/hello
.repl
31 changes: 31 additions & 0 deletions bb/node_repl_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(ns node-repl-test
(:require
[babashka.fs :as fs]
[babashka.process :as p :refer [process]]
[clojure.string :as str]
[clojure.test :as t :refer [deftest is]]))

(defn repl-process
[input dir opts]
(process (into ["node" (str (fs/absolutize "node_cli.js")) "repl"] (:cmd opts))
(merge {:dir (or dir ".")
:out :string
:in input
:err :inherit}
opts)))

(defn repl
([input] (repl input nil))
([input dir] (repl input dir nil))
([input dir opts]
(-> (repl-process input dir opts)
p/check)))

(deftest repl-test
(is (str/includes? (:out (repl "(+ 1 2 3)")) "6\n")))

(when (= *file* (System/getProperty "babashka.file"))
(let [{:keys [fail error]}
(t/run-tests 'node-repl-test)]
(when (pos? (+ fail error))
(System/exit 1))))
213 changes: 124 additions & 89 deletions src/squint/compiler.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@
s))

(defn expr-env [env]
(assoc env :context :expr))
(assoc env :context :expr :top-level false))

(defmethod emit-special 'throw [_ env [_ expr]]
(str "throw " (emit expr (expr-env env))))

(def statement-separator ";\n")

;; TODO: move to context argument
(def ^:dynamic *aliases* (atom {}))
(def ^:dynamic *async* false)
(def ^:dynamic *imported-vars* (atom {}))
(def ^:dynamic *excluded-core-vars* (atom #{}))
(def ^:dynamic *public-vars* (atom #{}))
(def ^:dynamic *repl* false)

(defn statement [expr]
(if (not (= statement-separator (rstr/tail (count statement-separator) expr)))
Expand Down Expand Up @@ -99,17 +99,22 @@

(defn maybe-core-var [sym]
(let [m (munge sym)]
(if (and (contains? core-vars m)
(not (contains? @*excluded-core-vars* m)))
(do (swap! *imported-vars* update "squint-cljs/core.js" (fnil conj #{}) m)
m)
sym)))
(when (and (contains? core-vars m)
(not (contains? @*excluded-core-vars* m)))
(swap! *imported-vars* update "squint-cljs/core.js" (fnil conj #{}) m)
m)))

(defn escape-jsx [env expr]
(if (:jsx env)
(format "{%s}" expr)
expr))

(defn emit-repl [s env]
(if (and *repl*
(:top-level env))
(str "\nglobalThis._repl = " s)
s))

(defmethod emit #?(:clj clojure.lang.Symbol :cljs Symbol) [expr env]
(if (:quote env)
(emit-wrap env
Expand All @@ -128,7 +133,7 @@
(let [sn (symbol (name expr))]
(or (when (or (= "cljs.core" sym-ns)
(= "clojure.core" sym-ns))
(munge (maybe-core-var sn)))
(some-> (maybe-core-var sn) munge))
(when (= "js" sym-ns)
(munge* (name expr)))
(when-let [resolved-ns (get @*aliases* (symbol sym-ns))]
Expand All @@ -138,11 +143,14 @@
(if-let [renamed (get (:var->ident env) expr)]
(munge* (str renamed))
(or
(munge (maybe-core-var expr))
(munged-name expr))))]
(emit-wrap env
(escape-jsx env
(str expr)))))))
(some-> (maybe-core-var expr) munge)
(let [m (munged-name expr)]
(str (when *repl*
(str (munge *ns*) ".")) m)))))]
(-> (emit-wrap env
(escape-jsx env
(str expr)))
(emit-repl env))))))

#?(:clj (defmethod emit #?(:clj java.util.regex.Pattern) [expr _env]
(str \/ expr \/)))
Expand Down Expand Up @@ -246,15 +254,16 @@
(emit (list 'cljs.core/and
(list operator (first args) (second args))
(list* operator (rest args))))
(if (and (= '- operator)
(= 1 acount))
(str "-" (emit (first args) env))
(->> (let [substitutions {'= "===" == "===" '!= "!=="
'not= "!=="
'+ "+"}]
(str "(" (str/join (str " " (or (substitutions operator) operator) " ")
(emit-args env args)) ")"))
(emit-wrap enc-env))))))
(-> (if (and (= '- operator)
(= 1 acount))
(str "-" (emit (first args) env))
(->> (let [substitutions {'= "===" == "===" '!= "!=="
'not= "!=="
'+ "+"}]
(str "(" (str/join (str " " (or (substitutions operator) operator) " ")
(emit-args env args)) ")"))
(emit-wrap enc-env)))
(emit-repl env)))))

(def ^:dynamic *recur-targets* [])

Expand Down Expand Up @@ -285,39 +294,42 @@
(let [context (:context enc-env)
env (assoc enc-env :context :expr)
partitioned (partition 2 bindings)
iife? (= :expr context)
iife? (or (= :expr context)
(and *repl* (:top-level env)))
upper-var->ident (:var->ident enc-env)
[bindings var->ident]
(reduce (fn [[acc var->ident] [var-name rhs]]
(let [vm (meta var-name)
rename? (not (:squint.compiler/no-rename vm))
renamed (if rename? (munge (gensym var-name))
var-name)
lhs (str renamed)
rhs (emit rhs (assoc env :var->ident var->ident))
expr (format "let %s = %s;\n" lhs rhs)
var->ident (assoc var->ident var-name renamed)]
[(str acc expr) var->ident]))
["" upper-var->ident]
partitioned)
enc-env (assoc enc-env :var->ident var->ident)]
(cond->> (str
bindings
(when is-loop
(str "while(true){\n"))
;; TODO: move this to env arg?
(binding [*recur-targets*
(if is-loop (map var->ident (map first partitioned))
*recur-targets*)]
(emit-do (if iife?
(assoc enc-env :context :return)
enc-env) body))
(when is-loop
;; TODO: not sure why I had to insert the ; here, but else
;; (loop [x 1] (+ 1 2 x)) breaks
(str ";break;\n}\n")))
(= :expr context)
(wrap-iife))))
(let [env (dissoc env :top-level)]
(reduce (fn [[acc var->ident] [var-name rhs]]
(let [vm (meta var-name)
rename? (not (:squint.compiler/no-rename vm))
renamed (if rename? (munge (gensym var-name))
var-name)
lhs (str renamed)
rhs (emit rhs (assoc env :var->ident var->ident))
expr (format "let %s = %s;\n" lhs rhs)
var->ident (assoc var->ident var-name renamed)]
[(str acc expr) var->ident]))
["" upper-var->ident]
partitioned))
enc-env (assoc enc-env :var->ident var->ident :top-level false)]
(-> (cond->> (str
bindings
(when is-loop
(str "while(true){\n"))
;; TODO: move this to env arg?
(binding [*recur-targets*
(if is-loop (map var->ident (map first partitioned))
*recur-targets*)]
(emit-do (if iife?
(assoc enc-env :context :return)
enc-env) body))
(when is-loop
;; TODO: not sure why I had to insert the ; here, but else
;; (loop [x 1] (+ 1 2 x)) breaks
(str ";break;\n}\n")))
iife?
(wrap-iife))
(emit-repl env))))

(defmethod emit-special 'let* [_type enc-env [_let bindings & body]]
(emit-let enc-env bindings body false))
Expand Down Expand Up @@ -422,15 +434,31 @@
)
"continue;\n")))

(defn emit-var [more env]
(apply str
(interleave (map (fn [[name expr]]
(str "var " (emit name env) " = "
(emit expr (assoc env :context :expr))))
(partition 2 more))
(repeat statement-separator))))
(defn emit-repl-var [s name env]
(str s
(when (and *repl* (:top-level env))
(emit name env))))

(defn no-top-level [env]
(dissoc env :top-level))

(defn emit-var [[name expr] env]
(-> (let [env (no-top-level env)]
(str (if *repl*
(str "globalThis."
(when *ns*
(str (munge *ns*) ".") #_"var ")
(munge name))
(str "var " (munge name))) " = "
(emit expr (expr-env env)) "\n"
(when *repl*
(str "var " (munge name) " = " "globalThis."
(when *ns*
(str (munge *ns*) ".")))) (munge name)))
(emit-repl-var name env)))

(defmethod emit-special 'def [_type env [_const & more]]
;;(prn *ns*)
(let [name (first more)]
(swap! *public-vars* conj (munge* name))
(emit-var more env)))
Expand All @@ -441,13 +469,14 @@
(format "(%s)" (str "await " s)))

(defmethod emit-special 'js/await [_ env [_await more]]
(emit-wrap env (wrap-await (emit more (expr-env env)))))
(-> (emit-wrap env (wrap-await (emit more (expr-env env))))
(emit-repl env)))

(defn wrap-iife [s]
(cond-> (format "(%sfunction () {\n %s\n})()" (if *async* "async " "") s)
*async* (wrap-await)))

(defmethod emit-special 'let [type env [_let bindings & more]]
(defmethod emit-special 'let [_type env [_let bindings & more]]
(emit (core-let bindings more) env)
#_(prn (core-let bindings more)))

Expand All @@ -474,6 +503,7 @@
(statement (format "import { %s } from '%s'" (str/join ", " refer) libname))))))

(defmethod emit-special 'ns [_type _env [_ns name & clauses]]
(set! *ns* name)
(reset! *aliases*
(->> clauses
(some
Expand All @@ -487,29 +517,32 @@
(assoc aliases alias full)
aliases)))
{:current name})))
(reduce (fn [acc [k & exprs]]
(cond
(= :require k)
(str acc (str/join "" (map process-require-clause exprs)))
(= :refer-clojure k)
(let [{:keys [exclude]} exprs]
(swap! *excluded-core-vars* into exclude)
acc)
:else acc))
""
clauses))
(str
(when *repl* (str "globalThis." (munge name) " = {} " ))
(reduce (fn [acc [k & exprs]]
(cond
(= :require k)
(str acc (str/join "" (map process-require-clause exprs)))
(= :refer-clojure k)
(let [{:keys [exclude]} exprs]
(swap! *excluded-core-vars* into exclude)
acc)
:else acc))
""
clauses)))

(defmethod emit-special 'funcall [_type env [fname & args :as _expr]]
(emit-wrap env
(str
(emit fname (expr-env env))
;; this is needed when calling keywords, symbols, etc. We could
;; optimize this later by inferring that we're not directly
;; calling a `function`.
#_(when-not interop? ".call")
(comma-list (emit-args env
args #_(if interop? args
(cons nil args)))))))
(-> (emit-wrap env
(str
(emit fname (expr-env env))
;; this is needed when calling keywords, symbols, etc. We could
;; optimize this later by inferring that we're not directly
;; calling a `function`.
#_(when-not interop? ".call")
(comma-list (emit-args env
args #_(if interop? args
(cons nil args))))))
(emit-repl env)))

(defmethod emit-special 'str [_type env [_str & args]]
(apply clojure.core/str (interpose " + " (emit-args env args))))
Expand All @@ -530,9 +563,10 @@
[(first method) (rest method)]
[method args])
method-str (str method)]
(if (str/starts-with? method-str "-")
(emit-aget env obj [(subs method-str 1)])
(emit-method env obj (symbol method-str) args))) #_(emit-method env obj method args))
(-> (if (str/starts-with? method-str "-")
(emit-aget env obj [(subs method-str 1)])
(emit-method env obj (symbol method-str) args))
(emit-repl env))))

(defmethod emit-special 'if [_type env [_if test then else]]
(if (= :expr (:context env))
Expand Down Expand Up @@ -644,8 +678,8 @@
[env [] #{}]
sig))

(defn emit-function [env name sig body & [elide-function?]]
(assert (or (symbol? name) (nil? name)))
(defn emit-function [env _name sig body & [elide-function?]]
;; (assert (or (symbol? name) (nil? name)))
(assert (vector? sig))
(let [[env sig] (->sig env sig)]
(binding [*recur-targets* sig]
Expand Down Expand Up @@ -868,7 +902,8 @@ break;}" body)
(str/join ", " (emit-args (expr-env env) expr)))))

(defn transpile-form [f]
(emit f {:context :statement}))
(emit f {:context :statement
:top-level true}))

(def ^:dynamic *jsx* false)

Expand Down
5 changes: 4 additions & 1 deletion src/squint/internal/cli.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
[babashka.cli :as cli]
[shadow.esm :as esm]
[squint.compiler :as cc]
[squint.compiler.node :as compiler]))
[squint.compiler.node :as compiler]
[squint.repl.node :as repl]))

(defn compile-files
[files]
Expand All @@ -28,6 +29,7 @@ Usage:
run <file.cljs> Compile and run a file
compile <file.cljs> ... Compile file(s)
repl Start repl
help Print this help"))

(defn fallback [{:keys [rest-cmds opts]}]
Expand Down Expand Up @@ -67,6 +69,7 @@ help Print this help"))
[{:cmds ["run"] :fn run :cmds-opts [:file]}
{:cmds ["compile"] :fn (fn [{:keys [rest-cmds]}]
(compile-files rest-cmds))}
{:cmds ["repl"] :fn repl/repl}
{:cmds [] :fn fallback}])

(defn init []
Expand Down
Loading

0 comments on commit 91bbafc

Please sign in to comment.