diff --git a/README.md b/README.md index 7ba50736..bdcc0bc1 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/sci b/sci index 6b75e107..79563a0c 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 6b75e1071ee425c6f468c73b2cef3ceae9c24af5 +Subproject commit 79563a0c94daa11e56e0969c963a52af9f7ee00f diff --git a/src/babashka/impl/clojure/core/server.clj b/src/babashka/impl/clojure/core/server.clj index ea6f14c8..2b1c821f 100644 --- a/src/babashka/impl/clojure/core/server.clj +++ b/src/babashka/impl/clojure/core/server.clj @@ -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) diff --git a/src/babashka/impl/clojure/main.clj b/src/babashka/impl/clojure/main.clj index b7c9010a..43e2b434 100644 --- a/src/babashka/impl/clojure/main.clj +++ b/src/babashka/impl/clojure/main.clj @@ -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.) diff --git a/src/babashka/impl/repl.clj b/src/babashka/impl/repl.clj index d97cec3a..ddb89594 100644 --- a/src/babashka/impl/repl.clj +++ b/src/babashka/impl/repl.clj @@ -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) + ) diff --git a/src/babashka/impl/socket_repl.clj b/src/babashka/impl/socket_repl.clj index 13195287..a18025b9 100644 --- a/src/babashka/impl/socket_repl.clj +++ b/src/babashka/impl/socket_repl.clj @@ -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 diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 1f62b057..9a823a95 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -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) diff --git a/test/babashka/impl/repl_test.clj b/test/babashka/impl/repl_test.clj index 9d49344c..d0f1bc5b 100644 --- a/test/babashka/impl/repl_test.clj +++ b/test/babashka/impl/repl_test.clj @@ -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 diff --git a/test/babashka/impl/socket_repl_test.clj b/test/babashka/impl/socket_repl_test.clj index 9ff3f86d..83038f0e 100644 --- a/test/babashka/impl/socket_repl_test.clj +++ b/test/babashka/impl/socket_repl_test.clj @@ -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" diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index 7370cc72..374a1af7 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -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)) + ))