[#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`:
- `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

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

View file

@ -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)

View file

@ -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.)

View file

@ -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)
)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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"

View file

@ -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))
))