Skip to content

Commit 78c810a

Browse files
authored
Fix #294: support multiple values in code input in nREPL eval message (#298)
1 parent 722d3c0 commit 78c810a

File tree

6 files changed

+142
-65
lines changed

6 files changed

+142
-65
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ For a list of breaking changes, check [here](#breaking-changes).
44

55
[Nbb](https://github.com/babashka/nbb): Scripting in Clojure on Node.js using [SCI](https://github.com/babashka/sci)
66

7+
## Unreleased
8+
9+
- [#294](https://github.com/babashka/nbb/issues/294): support multiple values in code input in nREPL eval message
10+
711
## 1.1.157
812

913
- [#295](https://github.com/babashka/nbb/issues/295): add `swap-vals!` and `reset-vals!`

script/nbb_nrepl_tests.clj

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
[babashka.wait :refer [wait-for-port]]
55
[bencode.core :as bencode]
66
[clojure.string :as str]
7-
[clojure.test :as t :refer [deftest is testing]])
7+
[clojure.test :as t :refer [deftest is testing]]
8+
[test-utils])
89
(:import [java.net Socket]))
910

10-
1111
(def debug? false)
1212

1313
(def port (atom 13337))
@@ -83,6 +83,7 @@
8383
"session" session "id" (new-id!)})
8484
(let [_msg (read-reply in session @id)
8585
_msg (read-reply in session @id)
86+
_done-msg (read-reply in session @id)
8687
msg (read-reply in session @id)
8788
out (:out msg)
8889
_ (is (= "{:delayed-by \"1 second\"}" out))]))
@@ -375,6 +376,7 @@
375376
"(ns example)\n(defmacro foo [] 'lul)"
376377
"session" session "id" (new-id!)})
377378
(let [_ (read-reply in session @id)
379+
_msg (read-reply in session @id)
378380
msg (read-reply in session @id)
379381
status (:status msg)
380382
_ (is (= ["done"] status))])
@@ -397,6 +399,34 @@
397399
(bencode/write-bencode os {"op" "eval" "code" "(js/process.exit 0)"
398400
"session" session "id" (new-id!)}))))
399401

402+
(deftest eval-multiple-test
403+
(nrepl-server)
404+
(wait-for-port "localhost" @port)
405+
(with-open [socket (Socket. "127.0.0.1" @port)
406+
in (.getInputStream socket)
407+
in (java.io.PushbackInputStream. in)
408+
os (.getOutputStream socket)]
409+
(bencode/write-bencode os {"op" "clone"})
410+
(let [session (:new-session (read-msg (bencode/read-bencode in)))
411+
id (atom 0)
412+
new-id! #(swap! id inc)]
413+
(testing "send multiple values to be evaluated"
414+
(bencode/write-bencode os {"op" "eval"
415+
"code"
416+
"(+ 1 2 3) (ns dude) (defn foo [] 1) (foo)"
417+
"session" session "id" (new-id!)})
418+
(let [msg (read-reply in session @id)
419+
_ (is (= "6" (:value msg)))
420+
_msg (read-reply in session @id) ;; ns
421+
msg (read-reply in session @id)
422+
_ (is (= "#'dude/foo" (:value msg)))
423+
msg (read-reply in session @id)
424+
_ (is (= "1" (:value msg)))
425+
msg (read-reply in session @id)
426+
_ (is (= ["done"] (:status msg)))]))
427+
(bencode/write-bencode os {"op" "eval" "code" "(js/process.exit 0)"
428+
"session" session "id" (new-id!)}))))
429+
400430

401431
(defn -main [& _]
402432
(let [{:keys [:error :fail]} (t/run-tests 'nbb-nrepl-tests)]

script/test_utils.clj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
str/lower-case
77
(str/starts-with? "win")))
88

9+
(defmethod clojure.test/report :begin-test-var [m]
10+
(println "====" (:name (meta (:var m)))))
911

1012
(defmethod clojure.test/report :end-test-var [_m]
1113
(when-let [rc *report-counters*]

src/nbb/core.cljs

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
[sci.impl.unrestrict :refer [*unrestricted*]]
1717
[sci.impl.vars :as vars]
1818
[sci.lang]
19-
[shadow.esm :as esm])
19+
[shadow.esm :as esm]
20+
[cljs.tools.reader.reader-types])
2021
(:require-macros [nbb.macros :as macros]))
2122

2223
(set! *unrestricted* true)
@@ -335,12 +336,12 @@
335336

336337
(declare eval-next)
337338

338-
(defn eval-seq [reader form opts]
339+
(defn eval-seq [reader form opts eval-next]
339340
(let [fst (first form)]
340341
(cond (= 'do fst)
341342
(reduce (fn [acc form]
342343
(.then acc (fn [_]
343-
(eval-seq reader form opts))))
344+
(eval-seq reader form opts eval-next))))
344345
(js/Promise.resolve nil)
345346
(rest form))
346347
(= 'ns fst)
@@ -378,20 +379,24 @@
378379

379380
(deftype Reject [v])
380381

382+
383+
(defn read-next [reader opts]
384+
(try (sci/binding [sci/ns (:ns opts)]
385+
(if-let [parse-fn (:parse-fn opts)]
386+
(parse-fn reader)
387+
(parse-next reader)))
388+
(catch :default e
389+
(js/Promise.reject e))))
390+
381391
(defn eval-next
382392
"Evaluates top level forms asynchronously. Returns promise of last value."
383393
[prev-val reader opts]
384-
(let [next-val (try (sci/binding [sci/ns (:ns opts)]
385-
(if-let [parse-fn (:parse-fn opts)]
386-
(parse-fn reader)
387-
(parse-next reader)))
388-
(catch :default e
389-
(js/Promise.reject e)))]
394+
(let [next-val (read-next reader opts)]
390395
(if (instance? js/Promise next-val)
391396
next-val
392397
(if (not= :sci.core/eof next-val)
393398
(if (seq? next-val)
394-
(eval-seq reader next-val opts)
399+
(eval-seq reader next-val opts eval-next)
395400
(let [v (try
396401
(sci/binding [sci/ns (:ns opts)
397402
sci/file (:file opts)]
@@ -403,9 +408,40 @@
403408
(recur v reader opts))))
404409
;; wrap normal value in promise
405410
(js/Promise.resolve
406-
(let [wrap (or (:wrap opts)
407-
identity)]
408-
(wrap prev-val {:ns (:ns opts)})))))))
411+
prev-val)))))
412+
413+
(defn reader? [rdr]
414+
(instance? cljs.tools.reader.reader-types/IndexingPushbackReader rdr))
415+
416+
(def init-sentinel (js/Object.))
417+
418+
(defn -eval-next*
419+
"Evaluates top level forms asynchronously. Has options for REPL."
420+
[prev-val next-val opts]
421+
(if (instance? js/Promise next-val)
422+
next-val
423+
(if (identical? init-sentinel prev-val)
424+
(if (seq? next-val)
425+
(eval-seq next-val next-val opts -eval-next*)
426+
(let [v (try
427+
(sci/binding [sci/ns (:ns opts)
428+
sci/file (:file opts)]
429+
(sci/eval-form (store/get-ctx) next-val))
430+
(catch :default e
431+
(->Reject e)))]
432+
(if (instance? Reject v)
433+
(js/Promise.reject (.-v v))
434+
(recur v next-val opts))))
435+
;; wrap normal value in promise
436+
(js/Promise.resolve
437+
(let [wrap (or (:wrap opts)
438+
identity)]
439+
(wrap prev-val {:ns (:ns opts)}))))))
440+
441+
(defn eval-next*
442+
"Evaluates top level forms asynchronously. Has options for REPL."
443+
[val opts]
444+
(-eval-next* init-sentinel val opts))
409445

410446
(defn eval-string* [s opts]
411447
(let [reader (sci/reader s)]

src/nbb/impl/nrepl_server.cljs

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -88,42 +88,58 @@
8888
(pr-str value)))
8989
(pr-str value)))
9090

91+
(defn send-value [request send-fn v]
92+
(let [[v opts] v
93+
sci-ns (:ns opts)]
94+
(reset! last-ns sci-ns)
95+
(sci/alter-var-root sci/*3 (constantly @sci/*2))
96+
(sci/alter-var-root sci/*2 (constantly @sci/*1))
97+
(sci/alter-var-root sci/*1 (constantly v))
98+
(let [v (format-value (:nrepl.middleware.print/print request)
99+
(:nrepl.middleware.print/options request)
100+
v)]
101+
(send-fn request {"value" v
102+
"ns" (str sci-ns)}))))
103+
91104
(defn do-handle-eval [{:keys [ns code file
92105
_load-file? _line] :as request} send-fn]
93-
(with-async-bindings
94-
{sci/ns ns
95-
sci/file file
96-
sci/print-length @sci/print-length
97-
sci/print-newline true}
98-
;; we alter-var-root this because the print-fn may go out of scope in case
99-
;; of returned delays
100-
(sci/alter-var-root sci/print-fn (constantly
101-
(fn [s]
102-
(send-fn request {"out" s}))))
103-
(-> (nbb/eval-next nil (sci/reader code) {:ns ns
104-
:file file
105-
:wrap vector})
106-
(.then (fn [v]
107-
(let [[v opts] v
108-
sci-ns (:ns opts)]
109-
(reset! last-ns sci-ns)
110-
(sci/alter-var-root sci/*3 (constantly @sci/*2))
111-
(sci/alter-var-root sci/*2 (constantly @sci/*1))
112-
(sci/alter-var-root sci/*1 (constantly v))
113-
(let [v (format-value (:nrepl.middleware.print/print request)
114-
(:nrepl.middleware.print/options request)
115-
v)]
116-
(send-fn request {"value" v
117-
"ns" (str sci-ns)})))
118-
(send-fn request {"status" ["done"]})))
119-
(.catch (fn [e]
120-
(sci/alter-var-root sci/*e (constantly e))
121-
(let [data (ex-data e)]
122-
(when-let [message (or (:message data) (.-message e))]
123-
(send-fn request {"err" (str message "\n")}))
124-
(send-fn request {"ex" (str e)
125-
"ns" (str @sci/ns)
126-
"status" ["done"]})))))))
106+
(let [rdr (sci/reader code)
107+
loop-fn (fn loop-fn [prev-val]
108+
(let [ns (or (:ns (second prev-val)) @last-ns ns)
109+
next-val (nbb/read-next rdr {:ns ns
110+
:file file
111+
:wrap vector})]
112+
(if (= :sci.core/eof next-val)
113+
(js/Promise.resolve prev-val)
114+
(let [v (nbb/eval-next* next-val {:ns ns
115+
:file file
116+
:wrap vector})]
117+
(.then v
118+
(fn [v]
119+
;; (prn :v v)
120+
(send-value request send-fn v)
121+
(loop-fn v)))))))]
122+
(with-async-bindings
123+
{sci/ns ns
124+
sci/file file
125+
sci/print-length @sci/print-length
126+
sci/print-newline true}
127+
;; we alter-var-root this because the print-fn may go out of scope in case
128+
;; of returned delays
129+
(sci/alter-var-root sci/print-fn (constantly
130+
(fn [s]
131+
(send-fn request {"out" s}))))
132+
(-> (loop-fn nil)
133+
(.catch (fn [e]
134+
(sci/alter-var-root sci/*e (constantly e))
135+
(let [data (ex-data e)]
136+
(when-let [message (or (:message data) (.-message e))]
137+
(send-fn request {"err" (str message "\n")}))
138+
(send-fn request {"ex" (str e)
139+
"ns" (str @sci/ns)}))))
140+
(.finally (fn []
141+
(send-fn request {"ns" (str last-ns)
142+
"status" ["done"]})))))))
127143

128144
(defn handle-eval [{:keys [ns] :as request} send-fn]
129145
(do-handle-eval (assoc request :ns (or (when ns

src/nbb/impl/repl.cljs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
[nbb.api :as api]
88
[nbb.core :as nbb]
99
[nbb.impl.repl-utils :refer [handle-complete*]]
10-
[sci.core :as sci]
11-
[sci.ctx-store :as store])
10+
[sci.core :as sci])
1211
(:require-macros [nbb.macros :as macros]))
1312

1413
(def last-ns (atom @sci/ns))
@@ -111,22 +110,12 @@
111110
(do (erase-processed rdr)
112111
(if-not (= :sci.core/eof the-val)
113112
(macros/with-async-bindings {sci/ns @last-ns}
114-
;; (prn :pending @pending)
115113
(-> (eval-expr
116114
socket
117-
#(nbb/eval-next nil nil
118-
{:ns @last-ns
119-
:file @sci/file
120-
:wrap vector
121-
;; TODO this is a huge workaround
122-
;; we should instead re-organize the code in nbb.core
123-
:parse-fn (let [realized? (atom false)]
124-
(fn [_]
125-
(if-not @realized?
126-
(do
127-
(reset! realized? true)
128-
the-val)
129-
:sci.core/eof)))}))
115+
#(nbb/eval-next* the-val
116+
{:ns @last-ns
117+
:file @sci/file
118+
:wrap vector}))
130119
(.then (fn [v]
131120
(let [[val {:keys [ns]}] v]
132121
(reset! last-ns ns)

0 commit comments

Comments
 (0)