-
Notifications
You must be signed in to change notification settings - Fork 13
/
api.cljc
400 lines (334 loc) · 15.1 KB
/
api.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
(ns sc.api
(:require [sc.impl :as i]
[sc.api.logging]
[sc.api.from])
#?(:cljs (:require-macros sc.api)))
(defn spy-emit
"Helper function for implementing (your own version of) the
`spy` macro. See the source of `spy` for a usage example."
[opts expr amp-env amp-form]
(i/spy-emit opts expr amp-env amp-form))
(defmacro spy
"Records the scope (i.e bindings of local names) and value
of the expression it wraps at runtime;
yields the value of the wrapped expression.
At macro-expansion time, a Code Site Id (a negative integer)
will be created and logged. Using this id, static information
about the Code Site may be retrieved using (sc.api/cs-info cs-id).
Each time the expression is evaluated, an Execution Point Id
(a positive integer) will be created and logged.
Dynamic information about this Execution Point
(and the associated Code Site) may be retrieved using
(sc.api/ep-info ep-id).
Having noted the Execution Point Id, you can then 'recreate'
the local runtime environment of the expression (its scope)
using the `letsc` and `defsc` macros:
(letsc ep-id
...)
(defsc ep-id)
The behaviour of this macro may be customized by passing a map literal
as a first argument, containing some of the following keys:
- :sc/spy-cs-logger-id (keyword, defaults to :sc.api.logging/log-spy-cs)
The id of the logger function that will be called at macro-expansion time
with Code Site information. This function must have been registered using
`sc.api/register-cs-logger`.
- :sc/spy-ep-pre-eval-logger-fn (expression, defaults to `sc.api.logging/log-spy-ep-pre-eval)
An expression which evaluates to a function (typically a Var name), which will
be invoked at runtime before the wrapped expression is evaluated with
Execution Point information - typically to log the Execution Point Id.
- :sc/spy-ep-post-eval-logger (expression, defaults to `sc.api.logging/log-spy-ep-post-eval)
Like :sc/spy-ep-pre-eval-logger-fn, but the function will be invoked after the
wrapped expression is evaluated - typically to log the value of the
expression.
- :sc/dynamic-vars (Seq of symbols, defaults to [])
The names of the dynamic Vars which bindings should be captured at evaluation
time.
- :sc/called-from (expression, defaults to nil)
When set, recording and logging will only occur when the Dynamic Var sc.api.from/*from*
holds the same value as the :sc/called-from expression (typically a keyword).
Useful in generic functions, for only spying downstream of some caller.
See also: sc.api/calling-from.
You may also provide your own defaults for these options by defining
your own version of the `spy` macro: to do that,
use `sc.api/spy-emit`.
You can completely disable the logging and recording behaviour of `spy`
at a given Code Site by using `(cs.api/disable! cs-id)`.
You can discard the recorded information about a given Execution Point
(typically for freeing memory) by using `(sc.api/dispose! ep-id)`.
"
([] (spy-emit nil nil &env &form))
([expr] (spy-emit nil expr &env &form))
([opts expr] (spy-emit opts expr &env &form)))
(def ^:private spyqt-opts
`{:sc/spy-ep-post-eval-logger sc.api.logging/log-spy-ep-post-eval--quiet})
(defmacro spyqt
"'Spy QuieTly' - like sc.api/spy but less verbose.
Will not print the :sc.ep/value, :sc.ep/error nor :sc.cs/expr;
useful when dealing with large values."
([] (spy-emit spyqt-opts nil &env &form))
([expr] (spy-emit spyqt-opts expr &env &form))
([opts expr] (spy-emit (merge spyqt-opts opts) expr &env &form)))
(defn brk-emit
"Helper function for implementing (your own version of) the
`brk` macro. See the source of `brk` for a usage example."
[opts expr amp-env amp-form]
(i/brk-emit opts expr amp-env amp-form))
(defmacro brk
"Like `spy`, but will block the executing Thread until
you release it from the REPL.
There are 3 ways of resuming execution at an Execution Point
where it was suspended by `brk`:
- `(sc.api/loose ep-id)`: resumes execution by evaluating
the wrapped expression.
- `(sc.api/loose-with ep-id value)`: resumes execution by
yielding `value` instead of evaluating the wrapped expression.
- `(sc.api/loose-with-err ep-id err)`: resumes execution by
throwing `err` instead of evaluating the wrapped expression.
Similarly to `spy`, you can customize the behaviour of `brk`
by passing a literal map of options; the accepted keys are:
- :sc/brk-cs-logger-id
- :sc/brk-ep-pre-eval-logger
- :sc/brk-ep-post-eval-logger
- :sc/dynamic-vars
- :sc/called-from
You can completely disable the logging, recording, and blocking behaviour of `spy`
at a given Code Site by using `(cs.api/disable! cs-id)`.
You can discard the recorded information about a given Execution Point
(typically for freeing memory) by using `(sc.api/dispose! ep-id)`. Execution
Points which are being blocked will see their execution resumed by
throwing an Exception.
Since you will likely need your REPL to un-block, you should never
execute a `(brk ...)` block directly from your REPL thread!
"
([] (brk-emit nil nil &env &form))
([expr] (brk-emit nil expr &env &form))
([opts expr] (brk-emit opts expr &env &form)))
(def ^:private brkqt-opts
`{:sc/brk-ep-post-eval-logger sc.api.logging/log-brk-ep-post-eval--quiet})
(defmacro brkqt
"'BReaK QuieTly' - like sc.api/brk but less verbose.
Will not print the :sc.ep/value, :sc.ep/error nor :sc.cs/expr;
useful when dealing with large values."
([] (brk-emit brkqt-opts nil &env &form))
([expr] (brk-emit brkqt-opts expr &env &form))
([opts expr] (brk-emit (merge brkqt-opts opts) expr &env &form)))
(defn last-ep-id
"Returns the id of the last saved Execution Point, in [ep-id cs-id] form.
Throws an Exception if no Execution Point has been saved."
[]
(i/last-ep-id))
(defn ep-info
"Given an Execution Point Id,
returns a map of information about that Execution Point,
and the associated Code Site.
Returns a map featuring the following keys:
- :sc.ep/id (positive integer)
The id of the Execution Point
- :sc.ep/code-site
A map of data about the associated Code Site, see `sc.api/cs-info`
- :sc.ep/value
The value the wrapped expression evaluated to (if any)
- :sc.ep/error
The exception thrown by the evaluation of the wrapped exception (if any)
- :sc.ep/local-bindings
A map which keys are the names of the locals in the scope of the wrapped expression,
and which values are the values the locals were bound to at this Execution Point.
- :sc.ep/dynamic-var-bindings
A map which keys are the fully-qualified names of the dynamic Vars recorded
for the Code Site, and which values are the values they were bound to at
the Execution Point.
As a convenience, when ep-id is not supplied, uses (sc.api/last-ep-id)."
([]
(ep-info (last-ep-id)))
([ep-id]
(i/ep-info ep-id)))
(defn ep-value
"Returns the value that was saved at the Execution Point
identified by ep-id (or nil if none was saved).
Shorthand for (:sc.ep/value (sc.api/ep-info ep-id)).
As a convenience, when ep-id is not supplied, uses (sc.api/last-ep-id)."
([]
(ep-value (last-ep-id)))
([ep-id]
(:sc.ep/value (ep-info ep-id))))
(defn cs-info
"Given a Code Site Id, retrieves a map of information about that Code Site,
featuring the following keys:
- :sc.cs/id (negative integer):
the ID of the Code Site
- :sc.cs/expr (code form):
the wrapped code expression
- :sc.cs/local-names (vector of symbols):
the names of the locals that are in the lexical environment of the Code Site.
- :sc.cs/dynamic-vars (vector of symbols);
the names of the dynamic Vars which bindings will be recorded at that Code Site.
- :sc.cs/file (String):
the path of the source file of the wrapped expr, when available
- :sc.cs/line (integer):
the line number of the `spy` or `brk` call in the source file, when available
- :sc.cs/column (integer):
the column number of the `spy` or `brk` call in the source file, when available
- :sc.cs/disabled (boolean):
whether this Code Site is in a disabled state."
[cs-id]
(i/cs-info cs-id))
(defmacro letsc
"Given an Execution Point Id `ep-id`, will expand to `let` and `binding`
forms wrapping `body` which will reproduce the local and dynamic Var bindings
that were recorded by the `spy` and `brk` macro at the Execution Point.
In practice, this enables you to run `body` in the same local environment where
the Execution Point was recorded.
`ep-id` may be provided either as a positive integer literal (the Execution Point Id, e.g 8),
or as a vector literal of one positive integer (the Execution Point Id) and one
negative integer (the Code Site Id), e.g [8 -3]. Note that in Clojure environments
where compilation and execution don't happen in the same address space
(which is the case for JVM-compiled ClojureScript), only the second form is accepted,
as there is no way of inferring the Code Site Id from the Execution Point Id
at macro-expansion."
[ep-id & body]
(let [cs (i/resolve-code-site ep-id)
ep-id (i/resolve-ep-id ep-id)]
`(binding
~(into []
(mapcat (fn [dvn]
[dvn `(i/ep-var-binding ~ep-id (quote ~dvn))]))
(:sc.cs/dynamic-var-names cs))
(let ~(into []
(mapcat
(fn [ln]
[ln `(i/ep-binding ~ep-id (quote ~ln))]))
(:sc.cs/local-names cs))
~@body))))
(defmacro defsc
"Given an Execution Point Id, expands to several `(def ...)` expressions,
defining Vars which names match the names of the locals captured
by the `spy` or `brk` macros at the Execution Point.
In practice, this enables you to make some local, temporary bindings available
globally and durably in a namespace (as Vars), which is convenient
for evaluating forms using these bindings at the REPL.
Note: `defsc` will not recreate the dynamic Var bindings at this Execution Point.
You can un-define (via ns-unmap) the Vars `def`ined by `defsc`
for a code site by using `sc.api/undefsc`.
`ep-id` may be provided either as a positive integer literal (the Execution Point Id, e.g 8),
or as a vector literal of one positive integer (the Execution Point Id) and one
negative integer (the Code Site Id), e.g [8 -3]. Note that in Clojure environments
where compilation and execution don't happen in the same address space
(which is the case for JVM-compiled ClojureScript), only the second form is accepted,
as there is no way of inferring the Code Site Id from the Execution Point Id
at macro-expansion."
[ep-id]
(let [cs (i/resolve-code-site ep-id)
ep-id (i/resolve-ep-id ep-id)]
(into []
(map (fn [ln]
`(def ~ln (i/ep-binding ~ep-id (quote ~ln)))))
(:sc.cs/local-names cs))))
(defmacro undefsc
"Given an identifier `ep-or-cs-id` for a Code Site,
undoes the effect of `defsc` by un`def`ing the Vars defined by `defsc`
(via `ns-unmap`).
`ep-or-cs-id` may be provided in one of the following forms:
- a positive integer literal, identifying an Execution Point, e.g 8
- a negative integer literal, identifying a Code Site, e.g -3
- an [ep-id cs-id] vector literal, e.g [8 -3]"
[ep-or-cs-id]
(let [cs (cond
(vector? ep-or-cs-id) (cs-info (second ep-or-cs-id))
(integer? ep-or-cs-id)
(cond
(pos? ep-or-cs-id) (:sc.ep/code-site (ep-info ep-or-cs-id))
(neg? ep-or-cs-id) (cs-info ep-or-cs-id))
:else (throw (ex-info "ep-or-cs-id should be a positive integer, a negative integer, or a vector of one positive integer and one negative integer."
{:ep-or-cs-id ep-or-cs-id})))
nz (ns-name *ns*)]
`(do
~@(map
(fn [ln]
`(ns-unmap (quote ~nz) (quote ~ln)))
(:sc.cs/local-names cs))
nil)))
(defn loose
"Given an Execution Point Id `ep-id` for an Execution Point
created by `brk` which is currently in a suspended state,
resumes execution by evaluating the wrapped form.
As a convenience, when ep-id is not supplied, uses (sc.api/last-ep-id)."
([]
(loose (last-ep-id)))
([ep-id]
(i/brk-send-resume-cmd ep-id
{:sc.brk/type :sc.brk.type/loose})))
(defn loose-with
"Same as `sc.api/loose`, except that execution is resumed by
yielding the provided value `v` instead of evaluating the wrapped
expression.
As a convenience, when ep-id is not supplied, uses (sc.api/last-ep-id)."
([v]
(loose-with (last-ep-id) v))
([ep-id v]
(i/brk-send-resume-cmd ep-id
{:sc.brk/type :sc.brk.type/loose-with
:sc.brk/loose-value v})))
(defn loose-with-err
"Same as `sc.api/loose`, except that execution is resumed by
throwing `err` instead of evaluating the wrapped
expression.
As a convenience, when ep-id is not supplied, uses (sc.api/last-ep-id)."
([err]
(loose-with-err (last-ep-id) err))
([ep-id err]
(i/brk-send-resume-cmd ep-id
{:sc.brk/type :sc.brk.type/loose-with-err
:sc.brk/loose-error err})))
(defn disable!
"Disables all logging, recording, and blocking behaviour at a Code Site.
No more Execution Points will be created at that Code Site, but the previously
collected ones remain fully available. Idempotent. The Code Site can be re-enabled
by calling `sc.api/enable!`"
[cs-id]
(i/disable! cs-id))
(defn enable!
"Undoes the action of `sc.api/disable!`. Idempotent."
[cs-id]
(i/enable! cs-id))
(defn dispose!
"Frees up resources held by the given Execution Point.
Useful for freeing memory.
If the Execution Point is in a suspened state (as by `brk`),
will resume execution by throwing an Exception."
[ep-id]
(i/dispose! ep-id))
(defn dispose-all!
"Disposes of all Execution Points (as per `sc.api/dispose!`)"
[]
(i/dispose-all!))
(defn gen-ep-id
"Generates a unique Execution Point Id, a positive integer."
[]
(i/gen-ep-id))
(defn save-ep
"Creates or updates an Execution Point. `ep-data` should follow
the same schema as what is returned by ep-info; only the :sc.ep/local-bindings
and :sc.ep/code-site keys are required, and only :sc.cs/id will be taken in account
for the Code Site. Returns ep-id.
Useful for artificially creating an Execution Point that is a slight variation
of another one, e.g:
(sc.api/save-ep (sc.api/gen-ep-id)
(-> (sc.api/ep-info 17)
(select-keys [:sc.ep/local-bindings :sc.ep/code-site])
(assoc-in [:sc.ep/local-bindings 'x] 32)))
"
[ep-id ep-data]
(i/save-ep ep-id ep-data))
(defmacro calling-from
"Binds sc.api.from/*caller* to `from-v`; to be used in combination with the :sc/called-from option.
When a Code Site has the :sc/called-from option set to a non-nil value `v`,
(e.g via a SPY call of the form `(sc.api/spy `{:sc/called-from :foo} MY-EXPR)`)
logging and recording of Execution Points at this Code Site will only occur
when the Dynamic Var `sc.api.from/*caller*` is bound to `v`.
Any non-nil value is legal for `from-v`; you'll typically use a keyword, number or boolean.
Use this when you're only interested in the executions of a Code Site that
are invoked from a given other place in your code
(which you wrap with `(sc.api/calling-from ...)`)"
[from-v & body]
`(binding [sc.api.from/*caller* ~from-v]
~@body))