[#154] Expose clojure.main/repl to babashka
This commit is contained in:
parent
1835d7d219
commit
acfc79660a
10 changed files with 135 additions and 128 deletions
|
|
@ -175,7 +175,9 @@ enumerated explicitly.
|
|||
- `clojure.java.shell` aliases as `shell`:
|
||||
- `sh`
|
||||
- `clojure.java.io` aliased as `io`:
|
||||
- `as-relative-path`, `copy`, `delete-file`, `file`
|
||||
- `as-relative-path`, `as-url`, `copy`, `delete-file`, `file`, `input-stream`,
|
||||
`make-parents`, `output-stream`, `reader`, `writer`
|
||||
- `clojure.main`: `repl`
|
||||
- [`clojure.core.async`](https://clojure.github.io/core.async/) aliased as
|
||||
`async`. The `alt` and `go` macros are not available but `alts!!` does work as
|
||||
it is a function.
|
||||
|
|
|
|||
2
sci
2
sci
|
|
@ -1 +1 @@
|
|||
Subproject commit 6b75e1071ee425c6f468c73b2cef3ceae9c24af5
|
||||
Subproject commit 79563a0c94daa11e56e0969c963a52af9f7ee00f
|
||||
|
|
@ -14,10 +14,11 @@
|
|||
:no-doc true}
|
||||
babashka.impl.clojure.core.server
|
||||
(:refer-clojure :exclude [locking])
|
||||
(:require [sci.core :as sci])
|
||||
(:import
|
||||
[clojure.lang LineNumberingPushbackReader]
|
||||
[java.net InetAddress Socket ServerSocket SocketException]
|
||||
[java.io Reader Writer PrintWriter BufferedWriter BufferedReader InputStreamReader OutputStreamWriter]))
|
||||
[java.io BufferedWriter InputStreamReader OutputStreamWriter]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
|
|
@ -41,9 +42,9 @@
|
|||
args - to pass to accept-fn"
|
||||
[^Socket conn client-id in out err accept args]
|
||||
(try
|
||||
(binding [*in* in
|
||||
*out* out
|
||||
*err* err]
|
||||
(sci/with-bindings {sci/in in
|
||||
sci/out out
|
||||
sci/err err}
|
||||
(swap! server assoc-in [:sessions client-id] {})
|
||||
(apply accept args))
|
||||
(catch SocketException _disconnect)
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@
|
|||
:author "Stephen C. Gilardi and Rich Hickey"
|
||||
:no-doc true}
|
||||
babashka.impl.clojure.main
|
||||
(:refer-clojure :exclude [with-bindings])
|
||||
(:import (java.io StringReader)
|
||||
(clojure.lang LineNumberingPushbackReader LispReader$ReaderException)))
|
||||
(:refer-clojure :exclude [with-bindings]))
|
||||
|
||||
(defmacro with-bindings
|
||||
"Executes body in the context of thread-local bindings for several vars
|
||||
|
|
@ -44,18 +42,6 @@
|
|||
*e nil]
|
||||
~@body))
|
||||
|
||||
(defn repl-prompt
|
||||
"Default :prompt hook for repl"
|
||||
[]
|
||||
(print "bb=> " ))
|
||||
|
||||
(defn repl-caught
|
||||
"Default :caught hook for repl"
|
||||
[e]
|
||||
(binding [*out* *err*]
|
||||
(println (.getMessage ^Exception e))
|
||||
(flush)))
|
||||
|
||||
(defn repl
|
||||
"Generic, reusable, read-eval-print loop. By default, reads from *in*,
|
||||
writes to *out*, and prints exception summaries to *err*. If you use the
|
||||
|
|
@ -92,14 +78,7 @@
|
|||
read, eval, or print throws an exception or error
|
||||
default: repl-caught"
|
||||
[& options]
|
||||
(let [{:keys [init need-prompt prompt flush read eval print caught]
|
||||
:or {need-prompt (if (instance? LineNumberingPushbackReader *in*)
|
||||
#(.atLineStart ^LineNumberingPushbackReader *in*)
|
||||
#(identity true))
|
||||
prompt repl-prompt
|
||||
flush flush
|
||||
print prn
|
||||
caught repl-caught}}
|
||||
(let [{:keys [init need-prompt prompt flush read eval print caught]}
|
||||
(apply hash-map options)
|
||||
request-prompt (Object.)
|
||||
request-exit (Object.)
|
||||
|
|
|
|||
|
|
@ -5,50 +5,72 @@
|
|||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.reader.reader-types :as r]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.interpreter :refer [eval-form]]
|
||||
[sci.impl.opts :refer [init]]
|
||||
[sci.impl.parser :as parser]))
|
||||
[sci.impl.parser :as parser]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.io :as sio]))
|
||||
|
||||
(defn repl-caught
|
||||
"Default :caught hook for repl"
|
||||
[e]
|
||||
(sci/with-bindings {sci/out *err* #_@sci/err}
|
||||
(sio/println (.getMessage ^Exception e))
|
||||
(sio/flush)))
|
||||
|
||||
(defn repl
|
||||
"REPL with predefined hooks for attachable socket server."
|
||||
[sci-ctx]
|
||||
(let [in (r/indexing-push-back-reader (r/push-back-reader *in*))]
|
||||
(m/repl
|
||||
:init #(do (println "Babashka"
|
||||
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
|
||||
"REPL.")
|
||||
(println "Use :repl/quit or :repl/exit to quit the REPL.")
|
||||
(println "Clojure rocks, Bash reaches.")
|
||||
(println))
|
||||
:read (fn [_request-prompt request-exit]
|
||||
(if (r/peek-char in) ;; if this is nil, we reached EOF
|
||||
(let [v (parser/parse-next sci-ctx in)]
|
||||
(if (or (identical? :repl/quit v)
|
||||
(identical? :repl/exit v)
|
||||
(identical? :edamame.impl.parser/eof v))
|
||||
request-exit
|
||||
v))
|
||||
request-exit))
|
||||
:eval (fn [expr]
|
||||
(let [ret (sci/with-bindings {sci/in *in*
|
||||
sci/out *out*
|
||||
sci/err *err*}
|
||||
(eval-form (update sci-ctx
|
||||
:env
|
||||
(fn [env]
|
||||
(swap! env update-in [:namespaces 'clojure.core]
|
||||
assoc
|
||||
'*1 *1
|
||||
'*2 *2
|
||||
'*3 *3
|
||||
'*e *e)
|
||||
env))
|
||||
expr))]
|
||||
ret))
|
||||
:need-prompt (fn [] true)
|
||||
:prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns)))))
|
||||
([sci-ctx] (repl sci-ctx nil))
|
||||
([sci-ctx {:keys [:init :read :eval :need-prompt :prompt :flush :print :caught]}]
|
||||
(let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))]
|
||||
(m/repl
|
||||
:init (or init
|
||||
#(do (sio/println "Babashka"
|
||||
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
|
||||
"REPL.")
|
||||
(sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
|
||||
(sio/println "Clojure rocks, Bash reaches.")
|
||||
(sio/println)))
|
||||
:read (or read
|
||||
(fn [_request-prompt request-exit]
|
||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in))
|
||||
;; (prn "PEEK" @sci/in (r/peek-char @sci/in)) this works fine
|
||||
(if (r/peek-char in) ;; if this is nil, we reached EOF
|
||||
(let [v (parser/parse-next sci-ctx in)]
|
||||
(if (or (identical? :repl/quit v)
|
||||
(identical? :repl/exit v)
|
||||
(identical? :edamame.impl.parser/eof v))
|
||||
request-exit
|
||||
v))
|
||||
request-exit)))
|
||||
:eval (or eval
|
||||
(fn [expr]
|
||||
(let [ret (eval-form (update sci-ctx
|
||||
:env
|
||||
(fn [env]
|
||||
(swap! env update-in [:namespaces 'clojure.core]
|
||||
assoc
|
||||
'*1 *1
|
||||
'*2 *2
|
||||
'*3 *3
|
||||
'*e *e)
|
||||
env))
|
||||
expr)]
|
||||
ret)))
|
||||
:need-prompt (or need-prompt (fn [] true))
|
||||
:prompt (or prompt #(sio/printf "%s=> " (-> sci-ctx :env deref :current-ns)))
|
||||
:flush (or flush sio/flush)
|
||||
:print (or print sio/prn)
|
||||
:caught (or caught repl-caught)))))
|
||||
|
||||
(defn start-repl! [ctx]
|
||||
(let [sci-ctx (init ctx)]
|
||||
(repl sci-ctx)))
|
||||
(defn start-repl!
|
||||
([sci-ctx] (start-repl! sci-ctx nil))
|
||||
([sci-ctx opts]
|
||||
(repl sci-ctx opts)))
|
||||
|
||||
;;;; Scratch
|
||||
|
||||
(comment
|
||||
(def in (-> (java.io.StringReader. "(+ 1 2 3)") clojure.lang.LineNumberingPushbackReader.))
|
||||
(r/peek-char in)
|
||||
(r/read-char in)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,19 +3,17 @@
|
|||
(:require
|
||||
[babashka.impl.clojure.core.server :as server]
|
||||
[babashka.impl.repl :as repl]
|
||||
[clojure.string :as str]
|
||||
[sci.impl.opts :refer [init]]))
|
||||
[clojure.string :as str]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn start-repl! [host+port sci-opts]
|
||||
(defn start-repl! [host+port sci-ctx]
|
||||
(let [parts (str/split host+port #":")
|
||||
[host port] (if (= 1 (count parts))
|
||||
[nil (Integer. ^String (first parts))]
|
||||
[(first parts) (Integer. ^String (second parts))])
|
||||
host+port (if-not host (str "localhost:" port)
|
||||
host+port)
|
||||
sci-ctx (init sci-opts)
|
||||
socket (server/start-server
|
||||
{:address host
|
||||
:port port
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@
|
|||
[clojure.string :as str]
|
||||
[sci.addons :as addons]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.vars :as vars])
|
||||
[sci.impl.opts :as sci-opts]
|
||||
[sci.impl.vars :as vars]
|
||||
[sci.impl.interpreter :refer [eval-string*]])
|
||||
(:gen-class))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
|
@ -179,21 +181,10 @@ Everything after that is bound to *command-line-args*."))
|
|||
(defn eval* [ctx form]
|
||||
(eval-string (pr-str form) ctx))
|
||||
|
||||
(defn start-repl! [ctx read-next]
|
||||
(let [ctx (update ctx :bindings assoc
|
||||
(with-meta '*input*
|
||||
{:sci.impl/deref! true})
|
||||
(sci/new-dynamic-var '*input* (read-next)))]
|
||||
(repl/start-repl! ctx)))
|
||||
|
||||
(defn start-socket-repl! [address ctx read-next]
|
||||
(let [ctx (update ctx :bindings assoc
|
||||
(with-meta '*input*
|
||||
{:sci.impl/deref! true})
|
||||
(sci/new-dynamic-var '*input* (read-next)))]
|
||||
(socket-repl/start-repl! address ctx)
|
||||
;; hang until SIGINT
|
||||
@(promise)))
|
||||
(defn start-socket-repl! [address ctx]
|
||||
(socket-repl/start-repl! address ctx)
|
||||
;; hang until SIGINT
|
||||
@(promise))
|
||||
|
||||
(defn exit [n]
|
||||
(throw (ex-info "" {:bb/exit-code n})))
|
||||
|
|
@ -283,12 +274,17 @@ Everything after that is bound to *command-line-args*."))
|
|||
ctx (update-in ctx [:namespaces 'clojure.core] assoc
|
||||
'eval #(eval* ctx %)
|
||||
'load-file #(load-file* ctx %))
|
||||
ctx (assoc-in ctx [:namespaces 'clojure.main 'repl]
|
||||
(fn [& opts]
|
||||
(let [opts (apply hash-map opts)]
|
||||
(repl/start-repl! ctx opts))))
|
||||
ctx (addons/future ctx)
|
||||
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx))
|
||||
expression (if main
|
||||
(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
|
||||
main)
|
||||
expression)
|
||||
sci-ctx (sci-opts/init ctx)
|
||||
exit-code
|
||||
(sci/with-bindings {reflection-var false}
|
||||
(or
|
||||
|
|
@ -299,20 +295,21 @@ Everything after that is bound to *command-line-args*."))
|
|||
[(print-version) 0]
|
||||
help?
|
||||
[(print-help) 0]
|
||||
repl [(start-repl! ctx #(read-next *in*)) 0]
|
||||
socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0]
|
||||
repl [(repl/start-repl! sci-ctx) 0]
|
||||
socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
|
||||
:else
|
||||
(try
|
||||
(let [expr (if file (read-file file) expression)]
|
||||
(let [ expr (if file (read-file file) expression)]
|
||||
(if expr
|
||||
(loop [in (read-next *in*)]
|
||||
(let [ctx (update-in ctx [:namespaces 'user] assoc (with-meta '*input*
|
||||
(when-not stream?
|
||||
{:sci.impl/deref! true}))
|
||||
(sci/new-dynamic-var '*input* in))]
|
||||
(let [_ (swap! env update-in [:namespaces 'user]
|
||||
assoc (with-meta '*input*
|
||||
(when-not stream?
|
||||
{:sci.impl/deref! true}))
|
||||
(sci/new-dynamic-var '*input* in))]
|
||||
(if (identical? ::EOF in)
|
||||
[nil 0] ;; done streaming
|
||||
(let [res [(let [res (eval-string expr ctx)]
|
||||
(let [res [(let [res (eval-string* sci-ctx expr)]
|
||||
(when (some? res)
|
||||
(if-let [pr-f (cond shell-out println
|
||||
edn-out prn)]
|
||||
|
|
@ -325,7 +322,7 @@ Everything after that is bound to *command-line-args*."))
|
|||
(if stream?
|
||||
(recur (read-next *in*))
|
||||
res)))))
|
||||
[(start-repl! ctx #(read-next *in*)) 0]))
|
||||
[(repl/start-repl! sci-ctx) 0]))
|
||||
(catch Throwable e
|
||||
(binding [*out* *err*]
|
||||
(let [d (ex-data e)
|
||||
|
|
|
|||
|
|
@ -2,32 +2,38 @@
|
|||
(:require
|
||||
[babashka.impl.repl :refer [start-repl!]]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t :refer [deftest is]]))
|
||||
[clojure.test :as t :refer [deftest is]]
|
||||
[sci.impl.opts :refer [init]]
|
||||
[sci.core :as sci]
|
||||
[sci.impl.vars :as vars]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
;; (vars/bindRoot sci/in *in*)
|
||||
;; (vars/bindRoot sci/out *out*)
|
||||
(vars/bindRoot sci/err *err*)
|
||||
|
||||
(defn repl! []
|
||||
(start-repl! {:bindings {(with-meta '*input*
|
||||
{:sci.impl/deref! true})
|
||||
(delay [1 2 3])
|
||||
'*command-line-args*
|
||||
["a" "b" "c"]}
|
||||
:env (atom {})}))
|
||||
(start-repl! (init {:bindings {'*command-line-args*
|
||||
["a" "b" "c"]}
|
||||
:env (atom {})})))
|
||||
|
||||
(defn assert-repl [expr expected]
|
||||
(is (str/includes? (with-out-str
|
||||
(with-in-str (str expr "\n:repl/quit")
|
||||
(is (str/includes? (sci/with-out-str
|
||||
(sci/with-in-str (str expr "\n:repl/quit")
|
||||
(repl!))) expected)))
|
||||
|
||||
(deftest repl-test
|
||||
(assert-repl "1" "1")
|
||||
(assert-repl "[1 2 3]" "[1 2 3]")
|
||||
(assert-repl "()" "()")
|
||||
(assert-repl "(+ 1 2 3)" "6")
|
||||
(assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6")
|
||||
(assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6")
|
||||
(assert-repl "1\n(inc *1)" "2")
|
||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||
(assert-repl "1\n(dec *1)(+ *2 *2)" "2")
|
||||
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]")
|
||||
(assert-repl "*input*" "[1 2 3]"))
|
||||
(assert-repl "*command-line-args*" "[\"a\" \"b\" \"c\"]"))
|
||||
|
||||
;;;; Scratch
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
[clojure.java.shell :refer [sh]]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as t :refer [deftest is testing]]
|
||||
[clojure.java.io :as io]))
|
||||
[clojure.java.io :as io]
|
||||
[sci.impl.opts :refer [init]]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
|
|
@ -31,13 +32,10 @@
|
|||
(deftest socket-repl-test
|
||||
(try
|
||||
(if tu/jvm?
|
||||
(start-repl! "0.0.0.0:1666" {:bindings {(with-meta '*input*
|
||||
{:sci.impl/deref! true})
|
||||
(delay [1 2 3])
|
||||
'*command-line-args*
|
||||
["a" "b" "c"]}
|
||||
:env (atom {})
|
||||
:features #{:bb}})
|
||||
(start-repl! "0.0.0.0:1666" (init {:bindings {'*command-line-args*
|
||||
["a" "b" "c"]}
|
||||
:env (atom {})
|
||||
:features #{:bb}}))
|
||||
(future
|
||||
(sh "bash" "-c"
|
||||
"echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c")))
|
||||
|
|
@ -47,8 +45,6 @@
|
|||
(sh "bash" "-c"
|
||||
"lsof -t -i:1666"))))))
|
||||
(is (socket-command "(+ 1 2 3)" "user=> 6"))
|
||||
(testing "*input*"
|
||||
(is (socket-command "*input*" "[1 2 3]")))
|
||||
(testing "*command-line-args*"
|
||||
(is (socket-command '*command-line-args* "\"a\" \"b\" \"c\"")))
|
||||
(testing "&env"
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@
|
|||
[clojure.java.shell :refer [sh]]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as test :refer [deftest is testing]]
|
||||
[sci.core :as sci]
|
||||
[babashka.test-utils :as tu]))
|
||||
[sci.core :as sci]))
|
||||
|
||||
(defn bb [input & args]
|
||||
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
|
||||
|
|
@ -186,9 +185,9 @@
|
|||
temp-dir-path))))))
|
||||
|
||||
(deftest wait-for-port-test
|
||||
(let [server (tu/start-server! 1777)]
|
||||
(let [server (test-utils/start-server! 1777)]
|
||||
(is (= 1777 (:port (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777)"))))
|
||||
(tu/stop-server! server)
|
||||
(test-utils/stop-server! server)
|
||||
(is (= :timed-out (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777 {:default :timed-out :timeout 50})")))))
|
||||
|
||||
(deftest wait-for-path-test
|
||||
|
|
@ -314,8 +313,15 @@
|
|||
(deftest compatibility-test
|
||||
(is (true? (bb nil "(set! *warn-on-reflection* true)"))))
|
||||
|
||||
(deftest clojure-main-repl-test
|
||||
(is (= "\"> foo!\\nnil\\n> \"\n" (test-utils/bb nil "
|
||||
(defn foo [] (println \"foo!\"))
|
||||
(with-out-str
|
||||
(with-in-str \"(foo)\"
|
||||
(clojure.main/repl :init (fn []) :prompt (fn [] (print \"> \")))))")))
|
||||
|
||||
;;;; Scratch
|
||||
|
||||
(comment
|
||||
(dotimes [_ 10] (wait-for-port-test))
|
||||
)
|
||||
(comment
|
||||
(dotimes [_ 10] (wait-for-port-test))
|
||||
))
|
||||
|
|
|
|||
Loading…
Reference in a new issue