[#154] Expose clojure.main/repl to babashka

This commit is contained in:
Michiel Borkent 2019-12-24 10:01:32 +01:00 committed by GitHub
parent 1835d7d219
commit acfc79660a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 135 additions and 128 deletions

View file

@ -175,7 +175,9 @@ enumerated explicitly.
- `clojure.java.shell` aliases as `shell`: - `clojure.java.shell` aliases as `shell`:
- `sh` - `sh`
- `clojure.java.io` aliased as `io`: - `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 - [`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 `async`. The `alt` and `go` macros are not available but `alts!!` does work as
it is a function. it is a function.

2
sci

@ -1 +1 @@
Subproject commit 6b75e1071ee425c6f468c73b2cef3ceae9c24af5 Subproject commit 79563a0c94daa11e56e0969c963a52af9f7ee00f

View file

@ -14,10 +14,11 @@
:no-doc true} :no-doc true}
babashka.impl.clojure.core.server babashka.impl.clojure.core.server
(:refer-clojure :exclude [locking]) (:refer-clojure :exclude [locking])
(:require [sci.core :as sci])
(:import (:import
[clojure.lang LineNumberingPushbackReader] [clojure.lang LineNumberingPushbackReader]
[java.net InetAddress Socket ServerSocket SocketException] [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) (set! *warn-on-reflection* true)
@ -41,9 +42,9 @@
args - to pass to accept-fn" args - to pass to accept-fn"
[^Socket conn client-id in out err accept args] [^Socket conn client-id in out err accept args]
(try (try
(binding [*in* in (sci/with-bindings {sci/in in
*out* out sci/out out
*err* err] sci/err err}
(swap! server assoc-in [:sessions client-id] {}) (swap! server assoc-in [:sessions client-id] {})
(apply accept args)) (apply accept args))
(catch SocketException _disconnect) (catch SocketException _disconnect)

View file

@ -15,9 +15,7 @@
:author "Stephen C. Gilardi and Rich Hickey" :author "Stephen C. Gilardi and Rich Hickey"
:no-doc true} :no-doc true}
babashka.impl.clojure.main babashka.impl.clojure.main
(:refer-clojure :exclude [with-bindings]) (:refer-clojure :exclude [with-bindings]))
(:import (java.io StringReader)
(clojure.lang LineNumberingPushbackReader LispReader$ReaderException)))
(defmacro with-bindings (defmacro with-bindings
"Executes body in the context of thread-local bindings for several vars "Executes body in the context of thread-local bindings for several vars
@ -44,18 +42,6 @@
*e nil] *e nil]
~@body)) ~@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 (defn repl
"Generic, reusable, read-eval-print loop. By default, reads from *in*, "Generic, reusable, read-eval-print loop. By default, reads from *in*,
writes to *out*, and prints exception summaries to *err*. If you use the 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 read, eval, or print throws an exception or error
default: repl-caught" default: repl-caught"
[& options] [& options]
(let [{:keys [init need-prompt prompt flush read eval print caught] (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}}
(apply hash-map options) (apply hash-map options)
request-prompt (Object.) request-prompt (Object.)
request-exit (Object.) request-exit (Object.)

View file

@ -5,50 +5,72 @@
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.string :as str] [clojure.string :as str]
[clojure.tools.reader.reader-types :as r] [clojure.tools.reader.reader-types :as r]
[sci.core :as sci]
[sci.impl.interpreter :refer [eval-form]] [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 (defn repl
"REPL with predefined hooks for attachable socket server." "REPL with predefined hooks for attachable socket server."
[sci-ctx] ([sci-ctx] (repl sci-ctx nil))
(let [in (r/indexing-push-back-reader (r/push-back-reader *in*))] ([sci-ctx {:keys [:init :read :eval :need-prompt :prompt :flush :print :caught]}]
(m/repl (let [in (r/indexing-push-back-reader (r/push-back-reader @sci/in))]
:init #(do (println "Babashka" (m/repl
(str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION")))) :init (or init
"REPL.") #(do (sio/println "Babashka"
(println "Use :repl/quit or :repl/exit to quit the REPL.") (str "v" (str/trim (slurp (io/resource "BABASHKA_VERSION"))))
(println "Clojure rocks, Bash reaches.") "REPL.")
(println)) (sio/println "Use :repl/quit or :repl/exit to quit the REPL.")
:read (fn [_request-prompt request-exit] (sio/println "Clojure rocks, Bash reaches.")
(if (r/peek-char in) ;; if this is nil, we reached EOF (sio/println)))
(let [v (parser/parse-next sci-ctx in)] :read (or read
(if (or (identical? :repl/quit v) (fn [_request-prompt request-exit]
(identical? :repl/exit v) ;; (prn "PEEK" @sci/in (r/peek-char @sci/in))
(identical? :edamame.impl.parser/eof v)) ;; (prn "PEEK" @sci/in (r/peek-char @sci/in)) this works fine
request-exit (if (r/peek-char in) ;; if this is nil, we reached EOF
v)) (let [v (parser/parse-next sci-ctx in)]
request-exit)) (if (or (identical? :repl/quit v)
:eval (fn [expr] (identical? :repl/exit v)
(let [ret (sci/with-bindings {sci/in *in* (identical? :edamame.impl.parser/eof v))
sci/out *out* request-exit
sci/err *err*} v))
(eval-form (update sci-ctx request-exit)))
:env :eval (or eval
(fn [env] (fn [expr]
(swap! env update-in [:namespaces 'clojure.core] (let [ret (eval-form (update sci-ctx
assoc :env
'*1 *1 (fn [env]
'*2 *2 (swap! env update-in [:namespaces 'clojure.core]
'*3 *3 assoc
'*e *e) '*1 *1
env)) '*2 *2
expr))] '*3 *3
ret)) '*e *e)
:need-prompt (fn [] true) env))
:prompt #(printf "%s=> " (-> sci-ctx :env deref :current-ns))))) 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] (defn start-repl!
(let [sci-ctx (init ctx)] ([sci-ctx] (start-repl! sci-ctx nil))
(repl sci-ctx))) ([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)
)

View file

@ -3,19 +3,17 @@
(:require (:require
[babashka.impl.clojure.core.server :as server] [babashka.impl.clojure.core.server :as server]
[babashka.impl.repl :as repl] [babashka.impl.repl :as repl]
[clojure.string :as str] [clojure.string :as str]))
[sci.impl.opts :refer [init]]))
(set! *warn-on-reflection* true) (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 #":") (let [parts (str/split host+port #":")
[host port] (if (= 1 (count parts)) [host port] (if (= 1 (count parts))
[nil (Integer. ^String (first parts))] [nil (Integer. ^String (first parts))]
[(first parts) (Integer. ^String (second parts))]) [(first parts) (Integer. ^String (second parts))])
host+port (if-not host (str "localhost:" port) host+port (if-not host (str "localhost:" port)
host+port) host+port)
sci-ctx (init sci-opts)
socket (server/start-server socket (server/start-server
{:address host {:address host
:port port :port port

View file

@ -21,7 +21,9 @@
[clojure.string :as str] [clojure.string :as str]
[sci.addons :as addons] [sci.addons :as addons]
[sci.core :as sci] [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)) (:gen-class))
(set! *warn-on-reflection* true) (set! *warn-on-reflection* true)
@ -179,21 +181,10 @@ Everything after that is bound to *command-line-args*."))
(defn eval* [ctx form] (defn eval* [ctx form]
(eval-string (pr-str form) ctx)) (eval-string (pr-str form) ctx))
(defn start-repl! [ctx read-next] (defn start-socket-repl! [address ctx]
(let [ctx (update ctx :bindings assoc (socket-repl/start-repl! address ctx)
(with-meta '*input* ;; hang until SIGINT
{:sci.impl/deref! true}) @(promise))
(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 exit [n] (defn exit [n]
(throw (ex-info "" {:bb/exit-code 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 ctx (update-in ctx [:namespaces 'clojure.core] assoc
'eval #(eval* ctx %) 'eval #(eval* ctx %)
'load-file #(load-file* 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) ctx (addons/future ctx)
_preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx)) _preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx))
expression (if main expression (if main
(format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)" (format "(ns user (:require [%1$s])) (apply %1$s/-main *command-line-args*)"
main) main)
expression) expression)
sci-ctx (sci-opts/init ctx)
exit-code exit-code
(sci/with-bindings {reflection-var false} (sci/with-bindings {reflection-var false}
(or (or
@ -299,20 +295,21 @@ Everything after that is bound to *command-line-args*."))
[(print-version) 0] [(print-version) 0]
help? help?
[(print-help) 0] [(print-help) 0]
repl [(start-repl! ctx #(read-next *in*)) 0] repl [(repl/start-repl! sci-ctx) 0]
socket-repl [(start-socket-repl! socket-repl ctx #(read-next *in*)) 0] socket-repl [(start-socket-repl! socket-repl sci-ctx) 0]
:else :else
(try (try
(let [expr (if file (read-file file) expression)] (let [ expr (if file (read-file file) expression)]
(if expr (if expr
(loop [in (read-next *in*)] (loop [in (read-next *in*)]
(let [ctx (update-in ctx [:namespaces 'user] assoc (with-meta '*input* (let [_ (swap! env update-in [:namespaces 'user]
(when-not stream? assoc (with-meta '*input*
{:sci.impl/deref! true})) (when-not stream?
(sci/new-dynamic-var '*input* in))] {:sci.impl/deref! true}))
(sci/new-dynamic-var '*input* in))]
(if (identical? ::EOF in) (if (identical? ::EOF in)
[nil 0] ;; done streaming [nil 0] ;; done streaming
(let [res [(let [res (eval-string expr ctx)] (let [res [(let [res (eval-string* sci-ctx expr)]
(when (some? res) (when (some? res)
(if-let [pr-f (cond shell-out println (if-let [pr-f (cond shell-out println
edn-out prn)] edn-out prn)]
@ -325,7 +322,7 @@ Everything after that is bound to *command-line-args*."))
(if stream? (if stream?
(recur (read-next *in*)) (recur (read-next *in*))
res))))) res)))))
[(start-repl! ctx #(read-next *in*)) 0])) [(repl/start-repl! sci-ctx) 0]))
(catch Throwable e (catch Throwable e
(binding [*out* *err*] (binding [*out* *err*]
(let [d (ex-data e) (let [d (ex-data e)

View file

@ -2,32 +2,38 @@
(:require (:require
[babashka.impl.repl :refer [start-repl!]] [babashka.impl.repl :refer [start-repl!]]
[clojure.string :as str] [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) (set! *warn-on-reflection* true)
;; (vars/bindRoot sci/in *in*)
;; (vars/bindRoot sci/out *out*)
(vars/bindRoot sci/err *err*)
(defn repl! [] (defn repl! []
(start-repl! {:bindings {(with-meta '*input* (start-repl! (init {:bindings {'*command-line-args*
{:sci.impl/deref! true}) ["a" "b" "c"]}
(delay [1 2 3]) :env (atom {})})))
'*command-line-args*
["a" "b" "c"]}
:env (atom {})}))
(defn assert-repl [expr expected] (defn assert-repl [expr expected]
(is (str/includes? (with-out-str (is (str/includes? (sci/with-out-str
(with-in-str (str expr "\n:repl/quit") (sci/with-in-str (str expr "\n:repl/quit")
(repl!))) expected))) (repl!))) expected)))
(deftest repl-test (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 "(+ 1 2 3)" "6")
(assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "6") (assert-repl "(defn foo [] (+ 1 2 3)) (foo)" "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(inc *1)" "2")
(assert-repl "1\n(dec *1)(+ *2 *2)" "2") (assert-repl "1\n(dec *1)(+ *2 *2)" "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 "*command-line-args*" "[\"a\" \"b\" \"c\"]"))
(assert-repl "*input*" "[1 2 3]"))
;;;; Scratch ;;;; Scratch

View file

@ -5,7 +5,8 @@
[clojure.java.shell :refer [sh]] [clojure.java.shell :refer [sh]]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :as t :refer [deftest is testing]] [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) (set! *warn-on-reflection* true)
@ -31,13 +32,10 @@
(deftest socket-repl-test (deftest socket-repl-test
(try (try
(if tu/jvm? (if tu/jvm?
(start-repl! "0.0.0.0:1666" {:bindings {(with-meta '*input* (start-repl! "0.0.0.0:1666" (init {:bindings {'*command-line-args*
{:sci.impl/deref! true}) ["a" "b" "c"]}
(delay [1 2 3]) :env (atom {})
'*command-line-args* :features #{:bb}}))
["a" "b" "c"]}
:env (atom {})
:features #{:bb}})
(future (future
(sh "bash" "-c" (sh "bash" "-c"
"echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c"))) "echo '[1 2 3]' | ./bb --socket-repl 0.0.0.0:1666 a b c")))
@ -47,8 +45,6 @@
(sh "bash" "-c" (sh "bash" "-c"
"lsof -t -i:1666")))))) "lsof -t -i:1666"))))))
(is (socket-command "(+ 1 2 3)" "user=> 6")) (is (socket-command "(+ 1 2 3)" "user=> 6"))
(testing "*input*"
(is (socket-command "*input*" "[1 2 3]")))
(testing "*command-line-args*" (testing "*command-line-args*"
(is (socket-command '*command-line-args* "\"a\" \"b\" \"c\""))) (is (socket-command '*command-line-args* "\"a\" \"b\" \"c\"")))
(testing "&env" (testing "&env"

View file

@ -7,8 +7,7 @@
[clojure.java.shell :refer [sh]] [clojure.java.shell :refer [sh]]
[clojure.string :as str] [clojure.string :as str]
[clojure.test :as test :refer [deftest is testing]] [clojure.test :as test :refer [deftest is testing]]
[sci.core :as sci] [sci.core :as sci]))
[babashka.test-utils :as tu]))
(defn bb [input & args] (defn bb [input & args]
(edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args)))) (edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args))))
@ -186,9 +185,9 @@
temp-dir-path)))))) temp-dir-path))))))
(deftest wait-for-port-test (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)")))) (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})"))))) (is (= :timed-out (bb nil "(wait/wait-for-port \"127.0.0.1\" 1777 {:default :timed-out :timeout 50})")))))
(deftest wait-for-path-test (deftest wait-for-path-test
@ -314,8 +313,15 @@
(deftest compatibility-test (deftest compatibility-test
(is (true? (bb nil "(set! *warn-on-reflection* true)")))) (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 ;;;; Scratch
(comment (comment
(dotimes [_ 10] (wait-for-port-test)) (dotimes [_ 10] (wait-for-port-test))
) ))