Skip to content

Commit b283072

Browse files
committed
Socket: Replaced keywords with string, documented protocol
1 parent 2b51200 commit b283072

File tree

3 files changed

+197
-61
lines changed

3 files changed

+197
-61
lines changed

cs_conn_socket_repl.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def read_loop(self):
5252
msg = cs_parser.parse_as_dict(line)
5353
self.handle_msg(msg)
5454
else:
55-
if '{:tag :started}' in line:
55+
if '{"tag" "started"}' in line:
5656
self.set_status(4, self.addr)
5757
started = True
5858
except OSError:
@@ -89,57 +89,57 @@ def eval(self, view, sel):
8989
ns = cs_parser.namespace(view, region.begin()) or 'user'
9090
file = view.file_name()
9191
msg = ('{' +
92-
f':id {batch_id}, ' +
93-
f':op :eval, ' +
94-
f':code "{code}", ' +
95-
f':ns "{ns}", ' +
96-
f':file "{file}", ' +
97-
f':line {line}, ' +
98-
f':column {column}' +
92+
f'"id" {batch_id}, ' +
93+
f'"op" "eval", ' +
94+
f'"code" "{code}", ' +
95+
f'"ns" "{ns}", ' +
96+
f'"file" "{file}", ' +
97+
f'"line" {line}, ' +
98+
f'"column" {column}' +
9999
'}')
100100
self.send(msg)
101101

102102
def load_file(self, view):
103103
self.eval(view, [sublime.Region(0, view.size())])
104104

105105
def lookup_impl(self, id, symbol, ns):
106-
msg = f'{{:id {id}, :op :lookup, :symbol "{symbol}", :ns {ns}}}'
106+
msg = f'{{"id" {id}, "op" "lookup", "symbol" "{symbol}", "ns" "{ns}"}}'
107107
self.send(msg)
108108

109109
def interrupt_impl(self, batch_id, id):
110-
msg = f'{{:id {batch_id}, :op :interrupt}}'
110+
msg = f'{{"id" {batch_id}, "op" "interrupt"}}'
111111
self.send(msg)
112112

113113
def handle_value(self, msg):
114-
if ':ret' == msg[':tag']:
115-
id = msg.get(':id')
116-
idx = msg.get(':idx')
117-
val = msg.get(':val')
114+
if 'ret' == msg['tag']:
115+
id = msg.get('id')
116+
idx = msg.get('idx')
117+
val = msg.get('val')
118118
time = msg.get('time')
119119
cs_eval.on_success(f'{id}.{idx}', val, time = time)
120120
return True
121121

122122
def handle_exception(self, msg):
123-
if ':ex' == msg[':tag']:
124-
id = msg.get(':id')
125-
idx = msg.get(':idx')
126-
val = msg.get(':val')
127-
line = msg.get(':line')
128-
column = msg.get(':column')
129-
trace = msg.get(':trace')
123+
if 'ex' == msg['tag']:
124+
id = msg.get('id')
125+
idx = msg.get('idx')
126+
val = msg.get('val')
127+
line = msg.get('line')
128+
column = msg.get('column')
129+
trace = msg.get('trace')
130130
cs_eval.on_exception(f'{id}.{idx}', val, line = line, column = column, trace = trace)
131131
return True
132132

133133
def handle_done(self, msg):
134-
if ':done' == msg[':tag']:
135-
batch_id = msg.get(':id')
134+
if 'done' == msg['tag']:
135+
batch_id = msg.get('id')
136136
cs_eval.on_done(batch_id)
137137
return True
138138

139139
def handle_lookup(self, msg):
140-
if ':lookup' == msg[':tag']:
141-
id = msg.get(':id')
142-
val = cs_parser.parse_as_dict(msg[':val'])
140+
if 'lookup' == msg['tag']:
141+
id = msg.get('id')
142+
val = cs_parser.parse_as_dict(msg['val'])
143143
cs_eval.on_lookup(id, val)
144144
return True
145145

docs/protocol_socket.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# Upgraded Socket REPL protocol
2+
3+
All messages are:
4+
5+
- EDN-formatted,
6+
- No keywords or symbols, only strings and ints,
7+
- '\n' inside strings escaped,
8+
- no newlines inside messages,
9+
- newline after each message.
10+
11+
---
12+
13+
```
14+
RCV {"tag" "started"}
15+
```
16+
17+
When client receives this message, REPL has finished upgrading and is ready to accept commands.
18+
19+
---
20+
21+
```
22+
SND {"op" => "eval"
23+
"id" => any, id
24+
"code" => string. Code to evaluate
25+
"ns" => string, optional. Namespace name. Defaults to user
26+
"file" => string, optional. File name where this code comes from. Defaults to NO_SOURCE_FILE
27+
"line" => int, optional. Line in file
28+
"column" => int, optional. Column position of first code character, 0-based}
29+
```
30+
31+
Evaluate form. If `code` contains multiple top-level forms, they are evaluated sequentially. After each successful evaluation, you get this:
32+
33+
```
34+
RCV {"tag" => "ret"
35+
"id" => any, id
36+
"idx" => int, sequential number of form in original `code`
37+
"val" => string, pr-str value
38+
"time" => int, execution time, ms
39+
"form" => string, form that being evaluated
40+
"from_line" => int, line at the beginning of the form
41+
"from_column" => int, column at the beginning of the form
42+
"to_line" => int, line at the end of the form
43+
"to_column" => int, column at the end of the form}
44+
```
45+
46+
Value string will be truncated after 1024 characters, so don’t rely on it be valid readable Clojure.
47+
48+
If some form fails, you get this and batch execution stops:
49+
50+
```
51+
RCV {"tag" => "ex"
52+
"id" => any. Id
53+
"val" => string. Error messasge
54+
"trace" => multiline string. Stacktrace
55+
"source" => string, if known. File name
56+
"line" => int, if known
57+
"column" => int, 0-based, if known
58+
"form", "from_"/"to_" "_line"/"_column" => same as in "ret"}
59+
```
60+
61+
Finally, success of failure, you’ll always recieve this:
62+
63+
```
64+
{"tag" => "done"
65+
"id" => any. Id}
66+
```
67+
68+
---
69+
70+
To interrupt evaluation, send this:
71+
72+
```
73+
SND {"op" => "interrupt"
74+
"id" => any}
75+
```
76+
77+
Whole batch will be stopped by throwing an exception. You’ll receive :ex and :done after this
78+
79+
---
80+
81+
To look up a symbol, send this:
82+
83+
```
84+
SND {"op" => "lookup"
85+
"id" => any
86+
"symbol" => string
87+
"ns" => string, optional, defaults to user}
88+
```
89+
90+
To which you’ll get either
91+
92+
```
93+
RCV {"tag" => "lookup"
94+
"id" => any
95+
"val" => map, description}
96+
```
97+
98+
Val map looks like this for functions:
99+
100+
```
101+
{"ns" "clojure.core"
102+
"name" "str"
103+
"arglists" "([] [x] [x & ys])"
104+
"doc" "With no args, returns the empty string. With one arg x, returns\n x.toString(). (str nil) returns the empty string. With more than\n one arg, returns the concatenation of the str values of the args."
105+
"file" "clojure/core.clj"
106+
"line" "546"
107+
"column" "1"
108+
"added" "1.0"}
109+
```
110+
111+
for vars:
112+
113+
```
114+
{"ns" "clojure.core"
115+
"name" "*warn-on-reflection*"
116+
"doc" "When set to true, the compiler will emit warnings when reflection is\n needed to resolve Java method calls or field accesses.\n\n Defaults to false."
117+
"added" "1.0"}
118+
```
119+
120+
and for special forms:
121+
122+
```
123+
{"ns" "clojure.core"
124+
"name" "do"
125+
"forms" "[(do exprs*)]"
126+
"doc" "Evaluates the expressions in order and returns the value of\n the last. If no expressions are supplied, returns nil."
127+
"file" "clojure/core.clj"
128+
"special-form" "true"}
129+
```
130+
131+
If symbol can’t be found, you’ll get:
132+
133+
```
134+
RCV {"tag" => "ex"
135+
"id" => any, id
136+
"val" => string, message}
137+
```

src_clojure/clojure_sublimed/socket_repl.clj

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@
4343
(str " " (core/bounded-pr-str data)))
4444
trace (core/trace-str root {:location? false})]
4545
(*out-fn*
46-
{:tag :ex
47-
:val val
48-
:trace trace
49-
:source source
50-
:line line
51-
:column column})))
46+
{"tag" "ex"
47+
"val" val
48+
"trace" trace
49+
"source" source
50+
"line" line
51+
"column" column})))
5252

5353
(defn reader ^LineNumberingPushbackReader [code line column]
5454
(let [reader (LineNumberingPushbackReader. (StringReader. code))]
@@ -69,7 +69,7 @@
6969
(.unread reader ch)))))
7070

7171
(defn eval-code [form]
72-
(let [{:keys [code ns line column file]} form
72+
(let [{:strs [code ns line column file]} form
7373
name (or (some-> file (str/split #"[/\\]") last) "NO_SOURCE_FILE")
7474
ns (symbol (or ns "user"))
7575
ns-obj (or
@@ -100,23 +100,23 @@
100100
Compiler/COLUMN_AFTER (.getColumnNumber reader)})
101101
ret (try
102102
(loop [idx 0]
103-
(vswap! *context* assoc :idx idx)
103+
(vswap! *context* assoc "idx" idx)
104104
(let [[obj obj-str] (read+string opts reader)]
105105
(when-not (identical? obj eof)
106106
(.set Compiler/LINE_AFTER (.getLineNumber reader))
107107
(.set Compiler/COLUMN_AFTER (.getColumnNumber reader))
108108
(vswap! *context* assoc
109-
:from_line (.get Compiler/LINE_BEFORE)
110-
:from_column (.get Compiler/COLUMN_BEFORE)
111-
:to_line (.get Compiler/LINE_AFTER)
112-
:to_column (.get Compiler/COLUMN_AFTER)
113-
:form obj-str)
109+
"from_line" (.get Compiler/LINE_BEFORE)
110+
"from_column" (.get Compiler/COLUMN_BEFORE)
111+
"to_line" (.get Compiler/LINE_AFTER)
112+
"to_column" (.get Compiler/COLUMN_AFTER)
113+
"form" obj-str)
114114
(let [start (System/nanoTime)
115115
ret (Compiler/eval obj false)]
116116
(*out-fn*
117-
{:tag :ret
118-
:val (core/bounded-pr-str ret)
119-
:time (-> (System/nanoTime) (- start) (quot 1000000))})
117+
{"tag" "ret"
118+
"val" (core/bounded-pr-str ret)
119+
"time" (-> (System/nanoTime) (- start) (quot 1000000))})
120120
(consume-ws reader)
121121
(.set Compiler/LINE_BEFORE (.getLineNumber reader))
122122
(.set Compiler/COLUMN_BEFORE (.getColumnNumber reader))
@@ -142,7 +142,7 @@
142142
(finally
143143
(pop-thread-bindings)))]))
144144

145-
(defn fork-eval [{:keys [id] :as form}]
145+
(defn fork-eval [{:strs [id] :as form}]
146146
(swap! *evals assoc id
147147
(future
148148
(try
@@ -155,20 +155,20 @@
155155
:ignore)))
156156
(finally
157157
(swap! *evals dissoc id)
158-
(vswap! *context* dissoc :idx :from_line :from_column :to_line :to_column :form)
158+
(vswap! *context* dissoc "idx" "from_line" "from_column" "to_line" "to_column" "form")
159159
(*out-fn*
160-
{:tag :done}))))))
160+
{"tag" "done"}))))))
161161

162-
(defn interrupt [{:keys [id]}]
162+
(defn interrupt [{:strs [id]}]
163163
(when-some [f (@*evals id)]
164164
(future-cancel f)))
165165

166166
(def safe-meta?
167167
#{:ns :name :doc :file :arglists :forms :macro :special-form :protocol :line :column :added :deprecated :resource})
168168

169169
(defn lookup-symbol [form]
170-
(let [{:keys [id op symbol ns] :or {ns 'user}} form
171-
ns (clojure.core/symbol ns)
170+
(let [{:strs [id op symbol ns]} form
171+
ns (clojure.core/symbol (or ns "user"))
172172
symbol (clojure.core/symbol symbol)
173173
meta (if (special-symbol? symbol)
174174
(assoc ((requiring-resolve 'clojure.repl/special-doc) symbol)
@@ -185,10 +185,10 @@
185185
m))
186186
nil
187187
meta)]
188-
{:tag :lookup
189-
:val meta'})
190-
{:tag :ex
191-
:val (str "Symbol '" symbol " not found in ns '" ns)}))))
188+
{"tag" "lookup"
189+
"val" meta'})
190+
{"tag" "ex"
191+
"val" (str "Symbol '" symbol " not found in ns '" ns)}))))
192192

193193
(defn out-fn [out]
194194
(let [lock (Object.)]
@@ -201,21 +201,20 @@
201201
*out* (.getRawRoot #'*out*)
202202
*err* (.getRawRoot #'*err*)
203203
core/*changed-vars (atom {})]
204-
(*out-fn* {:tag :started})
204+
(*out-fn* {"tag" "started"})
205205
(loop []
206206
(when
207207
(binding [*context* (volatile! {})]
208208
(try
209209
(let [form (read-command *in*)]
210210
(core/set-changed-vars!)
211-
(when-some [id (:id form)]
212-
(vswap! *context* assoc :id id))
213-
(case (:op form)
214-
:close (stop!)
215-
:eval (fork-eval form)
216-
:interrupt (interrupt form)
217-
:lookup (lookup-symbol form)
218-
(throw (Exception. (str "Unknown op: " (:op form)))))
211+
(when-some [id (form "id")]
212+
(vswap! *context* assoc "id" id))
213+
(case (get form "op")
214+
"eval" (fork-eval form)
215+
"interrupt" (interrupt form)
216+
"lookup" (lookup-symbol form)
217+
(throw (Exception. (str "Unknown op: " (get form "op")))))
219218
true)
220219
(catch Throwable t
221220
(when-not (-> t ex-data ::stop)

0 commit comments

Comments
 (0)