Skip to content

Commit

Permalink
Removing previous upgrade design, see #34
Browse files Browse the repository at this point in the history
  • Loading branch information
cgrand committed Feb 23, 2018
1 parent 8b9811d commit 1af8fbf
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 133 deletions.
80 changes: 2 additions & 78 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ To be more precise it's a stream of 2/3-item tuples, e.g. `[:read {:some :payloa
2. Second component is the payload.
3. Third (optional) component is a group id, meant to group together messages.

Ten core tags are defined: `:unrepl/hello`, `:bye`, `:prompt`, `:read`, `:started-eval`, `:eval`, `:out`, `:err`, `:log`, and `:exception`. More tags are defined in standard [actions](#actions).
Ten core tags are defined: `:unrepl/hello`, ``:prompt`, `:read`, `:started-eval`, `:eval`, `:out`, `:err`, `:log`, and `:exception`. More tags are defined in standard [actions](#actions).

| Tag | Payload |
|-----|---------|
|`:unrepl/hello`|A map or nil|
|`:bye`|A map or nil|
|`:prompt`|A map or nil|
|`:read` | A map |
|`:started-eval`|A map or nil|
Expand All @@ -55,56 +54,10 @@ Its payload is a map which may have a `:actions` key mapping to another map of [

This is how an unrepl implementation advertises its capabilities: by listing them along a machine-readable specification of the message needed to be sent to trigger them.

The hello map may also have a `:session` key which is just an identifier (any type) allowing a client to recognize a session it has already visited (e.g. when getting a `:unrepl/hello` after a `:bye`).
The hello map may also have a `:session` key which is just an identifier (any type) allowing a client to recognize a session it has already visited.

The hello map may also have a `:about` key mapped to a map. The intent of the `:about` map is to contain information about the REPL implementation, supported language, running environment (VM, OS etc.).

#### `:bye`

The `:bye` message must be the last unrepl message before yielding control of the input and output streams (eg nesting another REPL... or [Eliza](https://en.wikipedia.org/wiki/ELIZA)).

Implementation note: this can be detected when the expression being evaluated tries to read from the input. When evaluation returns, the unrepl impl can reassume control of the input and output stream. If it does so, its first message must be a `:unrepl/hello`.

Its payload is a map.

```clj
(spec/def :unrepl/bye-payload
(spec/keys :opt-un [:unrepl.bye/reason :unrepl.bye/outs :unrepl/actions]))

(spec/def :unrepl.bye/reason #{:disconnection :upgrade})

;; describes what happen to background outputs after the `:bye` message:
(spec/def :unrepl.bye/outs
#{:muted ; they are muted (think `/dev/null`)
:blocked ; writing threads are blocked
:closed ; they are closed (unless handled, the IO exception kills the writer)
:cobbled}) ; everything is cobbled together (like with a plain repl)
```


Example:

```clj
< [:prompt {:ns #object[clojure.lang.Namespace 0x2d352c62 "unrepl.core"], :*warn-on-reflection* nil}]
> (loop [] (let [c (char (.read *in*))] (case c \# :ciao (do (println "RAW" c) (recur)))))
< [:started-eval {} 1]
< [:bye nil]
> A
< RAW A
< RAW
<
> ABC
< RAW A
< RAW B
< RAW C
< RAW
<
> #
< [:unrepl/hello {:actions {}}]
< [:eval :ciao 1]
< [:prompt {:ns #object[clojure.lang.Namespace 0x2d352c62 "unrepl.core"], :*warn-on-reflection* nil}]
```

#### `:exception`

The payload is a map with a required key `:ex` which maps to the exception, and a second optional key `:phase` which can take 5 values:
Expand Down Expand Up @@ -329,35 +282,6 @@ No parameter. Transforms the current running evaluation in a Future. Upon succes

Upon completion of the future a `[:bg-eval value id]` is sent (on the main repl).

#### Bye actions
(Advertised in `:bye` messages.)

##### `:reattach-outs`

No parameter.

Redirects all outs to the repl (unrepl or not) in which the action has been issued.

##### `:set-mute-mode`

__DEPRECATED__

By default all spurious output is blocked after a `:bye` message.

Parameter:

```clj
(spec/def :unrepl/mute-mode #{:block :mute :redirect})
```

Returns true on success.

This actions expects a parameter `:unrepl/mute-mode` which can be one of:

* `:block` (default behavior),
* `:mute` (aka `/dev/null`),
* `:redirect` which redirects all outs to the control repl in which the action has been issued.

## License

Copyright © 2017 Christophe Grand
Expand Down
91 changes: 36 additions & 55 deletions src/unrepl/repl.clj
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@

(def ^:dynamic write)

(defn unrepl-reader [^java.io.Reader r before-read]
(defn unrepl-reader [^java.io.Reader r]
(let [offset (atom 0)
offset! #(swap! offset + %)]
(proxy [clojure.lang.LineNumberingPushbackReader clojure.lang.ILookup] [r]
Expand All @@ -82,17 +82,14 @@
([k not-found] (case k :offset @offset not-found)))
(read
([]
(before-read)
(let [c (proxy-super read)]
(when-not (neg? c) (offset! 1))
c))
([cbuf]
(before-read)
(let [n (proxy-super read cbuf)]
(when (pos? n) (offset! n))
n))
([cbuf off len]
(before-read)
(let [n (proxy-super read cbuf off len)]
(when (pos? n) (offset! n))
n)))
Expand Down Expand Up @@ -250,9 +247,7 @@
(write x)))

(defn start []
(with-local-vars [in-eval false
unrepl false
eval-id 0
(with-local-vars [eval-id 0
prompt-vars #{#'*ns* #'*warn-on-reflection*}
current-eval-future nil]
(let [session-id (keyword (gensym "session"))
Expand All @@ -265,14 +260,7 @@
java.io.BufferedWriter.
(doto schedule-writer-flush!)))
edn-out (scheduled-writer :out (fn [x] (binding [p/*string-length* Integer/MAX_VALUE] (write-here x))))
ensure-raw-repl (fn []
(when (and @in-eval @unrepl) ; reading from eval!
(var-set unrepl false)
(write [:bye {:reason :upgrade :actions {}}])
(flush)
; (reset! aw (blocking-write))
(set! *out* raw-out)))
in (unrepl-reader *in* ensure-raw-repl)
in (unrepl-reader *in*)
session-state (atom {:current-eval {}
:in in
:write-atom aw
Expand All @@ -284,37 +272,34 @@
:side-loader (atom nil)
:prompt-vars #{#'*ns* #'*warn-on-reflection*}})
current-eval-thread+promise (atom nil)
ensure-unrepl (fn []
(when-not @unrepl
(var-set unrepl true)
(flush)
(set! *out* edn-out)
(non-eliding-write
[:unrepl/hello {:session session-id
:actions (into
{:exit `(exit! ~session-id)
:start-aux `(start-aux ~session-id)
:log-eval
`(some-> ~session-id session :log-eval)
:log-all
`(some-> ~session-id session :log-all)
:print-limits
`(let [bak# {:unrepl.print/string-length p/*string-length*
:unrepl.print/coll-length *print-length*
:unrepl.print/nesting-depth *print-level*}]
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/string-length) (set! p/*string-length*))
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/coll-length) (set! *print-length*))
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/nesting-depth) (set! *print-level*))
bak#)
:set-source
`(unrepl/do
(set-file-line-col ~session-id
~(tagged-literal 'unrepl/param :unrepl/sourcename)
~(tagged-literal 'unrepl/param :unrepl/line)
~(tagged-literal 'unrepl/param :unrepl/column)))
:unrepl.jvm/start-side-loader
`(attach-sideloader! ~session-id)}
#_ext-session-actions)}])))
say-hello
(fn []
(non-eliding-write
[:unrepl/hello {:session session-id
:actions (into
{:exit `(exit! ~session-id)
:start-aux `(start-aux ~session-id)
:log-eval
`(some-> ~session-id session :log-eval)
:log-all
`(some-> ~session-id session :log-all)
:print-limits
`(let [bak# {:unrepl.print/string-length p/*string-length*
:unrepl.print/coll-length *print-length*
:unrepl.print/nesting-depth *print-level*}]
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/string-length) (set! p/*string-length*))
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/coll-length) (set! *print-length*))
(some->> ~(tagged-literal 'unrepl/param :unrepl.print/nesting-depth) (set! *print-level*))
bak#)
:set-source
`(unrepl/do
(set-file-line-col ~session-id
~(tagged-literal 'unrepl/param :unrepl/sourcename)
~(tagged-literal 'unrepl/param :unrepl/line)
~(tagged-literal 'unrepl/param :unrepl/column)))
:unrepl.jvm/start-side-loader
`(attach-sideloader! ~session-id)}
#_ext-session-actions)}]))

interruptible-eval
(fn [form]
Expand All @@ -332,8 +317,7 @@
{:interrupt (list `interrupt! session-id @eval-id)
:background (list `background! session-id @eval-id)}}
@eval-id])
(let [v (with-bindings {in-eval true}
(blame :eval (eval form)))]
(let [v (blame :eval (eval form))]
(deliver p {:eval v :bindings (get-thread-bindings)})
v)
(catch Throwable t
Expand All @@ -358,7 +342,7 @@
(f k x))))]
(swap! session-state assoc :class-loader slcl)
(swap! sessions assoc session-id session-state)
(binding [*out* raw-out
(binding [*out* edn-out
*err* (tagging-writer :err write)
*in* in
*file* "unrepl-session"
Expand All @@ -370,9 +354,10 @@
(with-bindings {clojure.lang.Compiler/LOADER slcl}
(try
(m/repl
:init #(swap! session-state assoc :bindings (get-thread-bindings))
:init #(do
(swap! session-state assoc :bindings (get-thread-bindings))
(say-hello))
:prompt (fn []
(ensure-unrepl)
(non-eliding-write [:prompt (into {:file *file*
:line (.getLineNumber *in*)
:column (.getColumnNumber *in*)
Expand Down Expand Up @@ -407,10 +392,8 @@
*out* (scheduled-writer :out id write)]
(interruptible-eval form))))
:print (fn [x]
(ensure-unrepl)
(write [:eval x @eval-id]))
:caught (fn [e]
(ensure-unrepl)
(let [{:keys [::ex ::phase]
:or {ex e phase :repl}} (ex-data e)]
(write [:exception {:ex ex :phase phase} @eval-id]))))
Expand All @@ -428,8 +411,6 @@
(finally
(.setContextClassLoader (Thread/currentThread) cl)))))

;; WIP for extensions

(defmacro ensure-ns [[fully-qualified-var-name & args :as expr]]
`(do
(require '~(symbol (namespace fully-qualified-var-name)))
Expand Down

0 comments on commit 1af8fbf

Please sign in to comment.