From 02d9a315cbfd64e9bcca5328302c9b6e4874167f Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Sat, 7 Dec 2019 11:48:57 +0100 Subject: [PATCH] sci: support for dynamic vars --- reflection.json | 4 +- src/babashka/impl/clojure/core.clj | 75 +----------------------------- src/babashka/impl/socket_repl.clj | 31 ++++++------ src/babashka/impl/utils.clj | 9 ++++ src/babashka/main.clj | 19 +++++--- test/babashka/main_test.clj | 27 +++++++---- test/babashka/test_utils.clj | 24 ++++++---- 7 files changed, 77 insertions(+), 112 deletions(-) create mode 100644 src/babashka/impl/utils.clj diff --git a/reflection.json b/reflection.json index f7253b1e..bec56a76 100644 --- a/reflection.json +++ b/reflection.json @@ -25,7 +25,9 @@ }, { "name":"java.io.BufferedReader", - "allPublicMethods":true + "allPublicMethods":true, + "allPublicFields": true, + "allPublicConstructors": true }, { "name": "java.lang.Class", diff --git a/src/babashka/impl/clojure/core.clj b/src/babashka/impl/clojure/core.clj index b998ca02..896d06a2 100644 --- a/src/babashka/impl/clojure/core.clj +++ b/src/babashka/impl/clojure/core.clj @@ -2,65 +2,8 @@ {:no-doc true} (:refer-clojure :exclude [future])) -(defn future - [_ _ & body] - `(~'future-call (fn [] ~@body))) - -(defn __close!__ [^java.io.Closeable x] - (.close x)) - -(defn with-open* - [_ _ bindings & body] - (cond - (= (count bindings) 0) `(do ~@body) - (symbol? (bindings 0)) `(let ~(subvec bindings 0 2) - (try - (with-open ~(subvec bindings 2) ~@body) - (finally - (~'__close!__ ~(bindings 0))))) - :else (throw (IllegalArgumentException. - "with-open only allows Symbols in bindings")))) - -(defn binding* - "This macro only works with symbols that evaluate to vars themselves. See `*in*` and `*out*` below." - [_ _ bindings & body] - `(do - (let [] - (push-thread-bindings (hash-map ~@bindings)) - (try - ~@body - (finally - (pop-thread-bindings)))))) - -;; this works now! -"(def w (java.io.StringWriter.)) (push-thread-bindings {clojure.core/*out* w}) (try (println \"hello\") (finally (pop-thread-bindings))) (prn \">\" (str w))" - -;; this also works now! "(def w (java.io.StringWriter.)) (binding [clojure.core/*out* w] (println \"hello\")) (str w)" - -(defn with-out-str* - [_ _ & body] - `(let [s# (java.io.StringWriter.)] - (binding [*out* s#] - ~@body - (str s#)))) - -(defn with-in-str* - [_ _ s & body] - `(with-open [s# (-> (java.io.StringReader. ~s) clojure.lang.LineNumberingPushbackReader.)] - (binding [*in* s#] - ~@body))) - (def core-extras - {'*in* #'*in* - '*out* #'*out* - 'binding (with-meta binding* {:sci/macro true}) - 'file-seq file-seq - 'future-call future-call - 'future (with-meta future {:sci/macro true}) - 'future-cancel future-cancel - 'future-cancelled? future-cancelled? - 'future-done? future-done? - 'future? future? + {'file-seq file-seq 'agent agent 'send send 'send-off send-off @@ -68,18 +11,4 @@ 'deliver deliver 'shutdown-agents shutdown-agents 'slurp slurp - 'spit spit - 'pmap pmap - 'pr pr - 'prn prn - 'print print - 'println println - 'println-str println-str - 'pop-thread-bindings pop-thread-bindings - 'push-thread-bindings push-thread-bindings - 'flush flush - 'read-line read-line - '__close!__ __close!__ - 'with-open (with-meta with-open* {:sci/macro true}) - 'with-out-str (with-meta with-out-str* {:sci/macro true}) - 'with-in-str (with-meta with-in-str* {:sci/macro true})}) + 'spit spit}) diff --git a/src/babashka/impl/socket_repl.clj b/src/babashka/impl/socket_repl.clj index 53cd465a..fe713acf 100644 --- a/src/babashka/impl/socket_repl.clj +++ b/src/babashka/impl/socket_repl.clj @@ -6,7 +6,9 @@ [clojure.java.io :as io] [clojure.string :as str] [clojure.tools.reader.reader-types :as r] - [sci.impl.interpreter :refer [opts->ctx eval-form]] + [sci.core :as sci] + [sci.impl.interpreter :refer [eval-form]] + [sci.impl.opts :refer [init]] [sci.impl.parser :as parser])) (set! *warn-on-reflection* true) @@ -32,17 +34,20 @@ v)) request-exit)) :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)] + (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))))) @@ -54,7 +59,7 @@ [(first parts) (Integer. ^String (second parts))]) host+port (if-not host (str "localhost:" port) host+port) - sci-ctx (opts->ctx sci-opts) + sci-ctx (init sci-opts) socket (server/start-server {:address host :port port diff --git a/src/babashka/impl/utils.clj b/src/babashka/impl/utils.clj new file mode 100644 index 00000000..55387bc3 --- /dev/null +++ b/src/babashka/impl/utils.clj @@ -0,0 +1,9 @@ +(ns babashka.impl.utils + {:no-doc true} + (:require [sci.core :as sci])) + +(defn eval-string [expr ctx] + (sci/with-bindings {sci/out *out* + sci/in *in* + sci/err *err*} + (sci/eval-string expr ctx))) diff --git a/src/babashka/main.clj b/src/babashka/main.clj index b9e2e29e..2d3a568e 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -10,12 +10,13 @@ [babashka.impl.pipe-signal-handler :refer [handle-pipe! pipe-signal-received?]] [babashka.impl.socket-repl :as socket-repl] [babashka.impl.tools.cli :refer [tools-cli-namespace]] + [babashka.impl.utils :refer [eval-string]] [babashka.wait :as wait] [clojure.edn :as edn] [clojure.java.io :as io] [clojure.java.shell :as shell] [clojure.string :as str] - [sci.core :as sci]) + [sci.addons :as addons]) (:gen-class)) (set! *warn-on-reflection* true) @@ -146,10 +147,10 @@ Everything after that is bound to *command-line-args*.")) (defn load-file* [ctx file] (let [s (slurp file)] - (sci/eval-string s ctx))) + (eval-string s ctx))) (defn eval* [ctx form] - (sci/eval-string (pr-str form) ctx)) + (eval-string (pr-str form) ctx)) (defn start-socket-repl! [address ctx read-next] (let [ctx (update ctx :bindings assoc @@ -163,6 +164,10 @@ Everything after that is bound to *command-line-args*.")) (defn exit [n] (throw (ex-info "" {:bb/exit-code n}))) +;; (sci/set-var-root! sci/*in* *in*) +;; (sci/set-var-root! sci/*out* *out*) +;; (sci/set-var-root! sci/*err* *err*) + (defn main [& args] (handle-pipe!) @@ -215,6 +220,7 @@ Everything after that is bound to *command-line-args*.")) 'java.lang.AssertionError AssertionError 'java.lang.Boolean Boolean 'java.io.BufferedWriter java.io.BufferedWriter + 'java.io.BufferedReader java.io.BufferedReader 'java.lang.Class Class 'java.lang.Double Double 'java.lang.Exception Exception @@ -247,8 +253,9 @@ Everything after that is bound to *command-line-args*.")) System java.lang.System Thread java.lang.Thread}} ctx (update ctx :bindings assoc 'eval #(eval* ctx %) - 'load-file #(load-file* ctx %)) - _preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (sci/eval-string ctx)) + 'load-file #(load-file* ctx %)) + ctx (addons/future ctx) + _preloads (some-> (System/getenv "BABASHKA_PRELOADS") (str/trim) (eval-string ctx)) exit-code (or #_(binding [*out* *err*] @@ -269,7 +276,7 @@ Everything after that is bound to *command-line-args*.")) {:sci/deref! true})) in)] (if (identical? ::EOF in) [nil 0] ;; done streaming - (let [res [(let [res (sci/eval-string expr ctx)] + (let [res [(let [res (eval-string expr ctx)] (when (some? res) (if-let [pr-f (cond shell-out println edn-out prn)] diff --git a/test/babashka/main_test.clj b/test/babashka/main_test.clj index af5c342d..92d3807b 100644 --- a/test/babashka/main_test.clj +++ b/test/babashka/main_test.clj @@ -6,10 +6,11 @@ [clojure.java.shell :refer [sh]] [clojure.string :as str] [clojure.test :as test :refer [deftest is testing]] - [clojure.java.io :as io])) + [clojure.java.io :as io] + [sci.core :as sci])) (defn bb [input & args] - (edn/read-string (apply test-utils/bb (str input) (map str args)))) + (edn/read-string (apply test-utils/bb (when (some? input) (str input)) (map str args)))) (deftest parse-opts-test (is (= {:expression "(println 123)"} @@ -82,6 +83,9 @@ "(map-indexed #(-> [%1 %2]) *in*)") (bb "(keep #(when (re-find #\"(?i)clojure\" (second %)) (first %)) *in*)")))))) +(deftest println-test + (is (= "hello\n" (test-utils/bb nil "(println \"hello\")")))) + (deftest input-test (testing "bb doesn't wait for input if *in* isn't used" (is (= "2\n" (with-out-str (main/main "(inc 1)")))))) @@ -93,8 +97,10 @@ (is (not-empty s)))) (let [out (java.io.StringWriter.) err (java.io.StringWriter.) - exit-code (binding [*out* out *err* err] - (main/main "--time" "(println \"Hello world!\") (System/exit 42)"))] + exit-code (sci/with-bindings {sci/out out + sci/err err} + (binding [*out* out *err* err] + (main/main "--time" "(println \"Hello world!\") (System/exit 42)")))] (is (= (str out) "Hello world!\n")) (is (re-find #"took" (str err))) (is (= 42 exit-code)))) @@ -110,12 +116,7 @@ "Usage:")))) (deftest ssl-test - (let [graalvm-home (System/getenv "GRAALVM_HOME") - lib-path (format "%1$s/jre/lib:%1$s/jre/lib/amd64" graalvm-home) - ;; _ (prn "lib-path" lib-path) - resp (bb nil (format "(System/setProperty \"java.library.path\" \"%s\") - (slurp \"https://www.google.com\")" - lib-path))] + (let [resp (bb nil "(slurp \"https://www.google.com\")")] (is (re-find #"doctype html" resp)))) (deftest stream-test @@ -288,3 +289,9 @@ (java.nio.file.Files/copy p p' (into-array [java.nio.file.StandardCopyOption/REPLACE_EXISTING]))))))" temp-path)) (is (.exists f2)))) + +(deftest future-print-test + (testing "the root binding of sci/*out*" + (is (= "hello" (bb nil "@(future (prn \"hello\"))")))) + + ) diff --git a/test/babashka/test_utils.clj b/test/babashka/test_utils.clj index b5996386..5a42415d 100644 --- a/test/babashka/test_utils.clj +++ b/test/babashka/test_utils.clj @@ -1,19 +1,25 @@ (ns babashka.test-utils (:require [babashka.main :as main] - [me.raynes.conch :refer [let-programs] :as sh])) + [me.raynes.conch :refer [let-programs] :as sh] + [sci.core :as sci])) (set! *warn-on-reflection* true) (defn bb-jvm [input & args] - (let [es (java.io.StringWriter.) - os (java.io.StringWriter.)] - (binding [*err* es - *out* os] - (let [res (if input - (with-in-str input - (apply main/main args)) - (apply main/main args))] + (let [os (java.io.StringWriter.) + es (java.io.StringWriter.) + is (when input + (java.io.StringReader. input)) + bindings-map (cond-> {sci/out os + sci/err es} + is (assoc sci/in is))] + (sci/with-bindings bindings-map + (let [res (binding [*out* os + *err* es] + (if input + (with-in-str input (apply main/main args)) + (apply main/main args)))] (if (zero? res) (str os) (throw (ex-info (str es)