Skip to content

Commit 6249eb0

Browse files
committed
Merge pull request #177 from Malabarba/master
Some improvements to intrument.clj
2 parents fc717e3 + 29e5d80 commit 6249eb0

File tree

3 files changed

+160
-28
lines changed

3 files changed

+160
-28
lines changed

src/cider/nrepl/middleware/debug.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
(:transport @debugger-message)
3636
(response-for @debugger-message
3737
(assoc extras
38-
:value (pr-str value)
38+
:debug-value (pr-str value)
3939
:breakfunction nil)))
4040
;; Send the actual break.
4141
(binding [*out* (new java.io.StringWriter)]

src/cider/nrepl/middleware/util/instrument.clj

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,28 @@
4545
Used only for the purposed of instrumenting on special symbols which
4646
don't have arglists (such as `if` or `try`), or for macros whose
4747
usual arglist we wouldn't understand.
48-
48+
4949
This can also be used to disable instrumenting of a form by setting
5050
its arglist to ([]).
5151
5252
Finally, if the key's value is a symbol, it is taken as an alias and
5353
we use this symbol's arglist."
54-
55-
'{if ([& expr])
56-
try ([])
57-
def ([form doc-string? expr?])
58-
defn- defn
59-
fn ([name? [params*] exprs*] [name? ([params*] exprs*) +])})
54+
55+
'{;; Possible in theory, but not supported yet.
56+
try ([& form]),
57+
58+
;; More sophisticated cases with badly written arglists.
59+
defn- defn,
60+
fn ([name? [params*] exprs*] [name? ([params*] exprs*) +]),
61+
62+
;; condp, cond, case, cond->, cond->>, all use different meanings
63+
;; for the "clause" argument. We do the best we can in the
64+
;; `specifier-map` below, but we need to override a few here too.
65+
;; case clauses are of the form-expression type.
66+
case ([expr fe-clause* expr?]),
67+
;; cond-> clauses are of the expression-form type.
68+
cond-> ([expr & ef-clause]),
69+
cond->> cond->})
6070

6171
(defn- macro-arglists
6272
"Return a list of possible arglist vectors for symbol."
@@ -68,8 +78,15 @@
6878
(let [metadata (if (var? symbol)
6979
(meta symbol)
7080
(info-clj (ns-name *ns*) symbol))]
81+
;; :debugspec let's people define their own macros in a way we
82+
;; can instrument it (if they don't want to use the arglist).
7183
(or (:debugspec metadata)
72-
(:arglists metadata)))))
84+
;; Then we look for an arglist.
85+
(:arglists metadata)
86+
;; If it doesn't have an arglist, it's probably a special
87+
;; form. Try to construct an arglist from :forms.
88+
(map (comp vec rest)
89+
(filter seq? (:forms metadata)))))))
7390

7491
;;; Because of the way we handle arglists, we need the modifiers such
7592
;;; as + or & to come before the argument they're affecting. That's
@@ -192,14 +209,17 @@
192209
forms
193210
(range (count forms))))))
194211

195-
(defn- instrument-two-args
212+
(defn- instrument-second-arg
196213
[{:keys [coor] :as ex} [form1 form2 & forms]]
197-
(cons (instrument ex form1)
214+
(cons form1
198215
(let [n (last coor)
199216
coor (vec (butlast coor))]
200217
(cons (instrument (assoc ex :coor (conj coor (inc n))) form2)
201218
forms))))
202219

220+
(def instrument-two-args
221+
#(instrument-next-arg %1 (instrument-second-arg %1 %2)))
222+
203223
(declare instrument-special-form-try)
204224
(def specifier-map
205225
"Map between specifiers and [matcher handler] functions pairs.
@@ -220,31 +240,40 @@
220240
if the matcher returned nil."
221241

222242
{;; Safe to instrument
223-
"else" [always-1 instrument-next-arg]
224243
"expr" [always-1 instrument-next-arg]
244+
"init" [always-1 instrument-next-arg]
225245
"pred" [always-1 instrument-next-arg]
226-
"then" [always-1 instrument-next-arg]
227246
"test" [always-1 instrument-next-arg]
247+
"then" [always-1 instrument-next-arg]
248+
"else" [always-1 instrument-next-arg]
228249
;; Match everything.
229250
"body" [count instrument-all-args]
230251
;; Not safe or not meant to be instrumented
231252
"form" [always-1 instrument-nothing]
232253
"oldform" [always-1 instrument-nothing]
233254
"params" [always-1 instrument-nothing]
234255
"name" [(fn [[f]] (if (symbol? f) 1)) instrument-nothing]
256+
"symbol" [(fn [[f]] (if (symbol? f) 1)) instrument-nothing]
235257
;; Complicated
236258
"bindings" [specifier-match-bindings
237259
(fn [ex [bindings & forms]]
238260
(cons (instrument-bindings ex bindings) forms))]
239261
"docstring" [#(when (string? (first %)) 1) instrument-nothing]
240262
"string" [#(when (string? (first %)) 1) instrument-nothing]
241263
"map" [#(when (map? (first %)) 1) instrument-next-arg]
242-
"clause" [#(when (> (count %) 1) 2) instrument-two-args]
243264
"fn-tail" [#(when (vector? (first %)) (count %))
244265
instrument-all-but-first-arg]
245266
"dispatch-fn" [(fn [[[f & r]]]
246267
(when (instrument-special-form-try [] f r) 1))
247-
instrument-next-arg]})
268+
instrument-next-arg]
269+
;; Clauses are a mess. cond, condp, case, all take them to mean
270+
;; different things. And don't get me started on cond->.
271+
;; I hereby declare `clause` to mean "instrument anything".
272+
"clause" [always-1 instrument-next-arg]
273+
;; Expression-Form type clauses instrument every odd argument.
274+
"ef-clause" [#(if (> (count %) 1) 2) instrument-next-arg]
275+
;; Form-Expression type clauses instrument every even argument.
276+
"fe-clause" [#(if (> (count %) 1) 2) instrument-second-arg]})
248277

249278
(defn- specifier-destructure
250279
"Take a symbol specifier and return a vector description.
@@ -268,6 +297,7 @@
268297
(if (.endsWith spec-name "s")
269298
(subs spec-name 0 (dec (count spec-name)))
270299
(str spec-name "s")))
300+
(and (.endsWith spec-name "-symbol") (specifier-map "symbol"))
271301
(and (.endsWith spec-name "-string") (specifier-map "string"))
272302
(and (.endsWith spec-name "-map") (specifier-map "map"))
273303
[always-1 instrument-nothing])
@@ -445,14 +475,32 @@
445475
([form ex]
446476
`(~(:breakfunction ex) ~form ~ex)))
447477

478+
(defn- contains-recur?
479+
"Return true if form is not a `loop` and a `recur` is found in it."
480+
[form]
481+
(if (listy? form)
482+
(condp = (first form)
483+
'recur true
484+
'loop false
485+
(some contains-recur? (rest form)))))
486+
487+
(defn- dont-break?
488+
"Return true if it's NOT ok to wrap form in a breakpoint.
489+
Expressions we don't want to wrap are those containing a `recur`
490+
form, and those whose `name` is contained in
491+
`irrelevant-return-value-macros`."
492+
[form]
493+
(or (irrelevant-return-value-macros name)
494+
(contains-recur? form)))
495+
448496
(defn- instrument-function-like-form
449497
"Instrument form representing a function/macro call or special-form."
450498
[ex [name & args :as form]]
451499
(if (symbol? name)
452500
(let [name (or (ns-resolve *ns* name) name)]
453501
(if (or (resolve-special name)
454502
(:macro (meta name)))
455-
(if (irrelevant-return-value-macros name)
503+
(if (dont-break? form)
456504
(instrument-special-form ex form)
457505
(with-break instrument-special-form form ex))
458506
(with-break instrument-coll form ex)))

test/cider/nrepl/middleware/util/instrument_test.clj

Lines changed: 96 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626

2727
(deftest macro-arglists
2828
(are [s a] (= (#'t/macro-arglists s) a)
29-
'if '([& expr])
29+
'if '([test then else?])
3030
'when '([test & body])
31-
'let '([bindings & body])
31+
'let '([bindings & body])
3232
'defn '([name doc-string? attr-map? [params*] prepost-map? body]
3333
[name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
34-
'def '([form doc-string? expr?])))
34+
'def '([symbol doc-string? init?])))
3535

3636
(deftest reorder-+
3737
(are [a b] (= (#'t/reorder-+ a) b)
@@ -44,27 +44,111 @@
4444

4545
(deftest always-1
4646
(are [a] (= (#'t/always-1 a) 1)
47-
'[+ 1 2 3 4] '(+ 1 2 3 4)
48-
'([& expr]) '([test & body])
47+
'[+ 1 2 3 4]
48+
'(+ 1 2 3 4)
49+
'([& expr])
50+
'([test & body])
4951
'pikachu
5052
"Charizard"
5153
:Blastoise))
5254

5355
;;; Dummy ex
5456
(def dex {:coor [13] :breakfunction 'b})
57+
(defn- id [v] (assoc dex :coor v))
58+
(defmacro bt [form vec]
59+
`'(~'b ~(eval form) ~(id vec)))
5560

5661
(deftest instrument-nothing
57-
(are [a b] (= (#'t/instrument-nothing '{:coor a} b) b)
62+
(are [a b] (= (#'t/instrument-nothing dex b) b)
5863
'[+ 1 2 3 4] '(+ 1 2 3 4)
59-
'([& expr]) '([test & body])
60-
'pikachu "Charizard"
61-
:Blastoise :Magikarp))
64+
'([& expr]) '([test & body])
65+
'pikachu "Charizard"
66+
:Blastoise :Magikarp))
6267

6368
(deftest instrument-map
6469
(is (= (#'t/instrument-map dex '{:a 1, (name :b) (inc 2)})
65-
'{:a 1,
66-
(b (name :b) {:breakfunction b, :coor [13 2]})
67-
(b (inc 2) {:breakfunction b, :coor [13 3]})})))
70+
{:a 1,
71+
(bt '(name :b) [13 2])
72+
(bt '(inc 2) [13 3])})))
73+
74+
(deftest instrument-basics
75+
(are [f o] (= (f dex '(a b c)) o)
76+
#'t/instrument-all-args (list (bt 'a [13]) (bt 'b [14]) (bt 'c [15]))
77+
#'t/instrument-next-arg (list (bt 'a [13]) 'b 'c)
78+
#'t/instrument-nothing (list 'a 'b 'c)
79+
#'t/instrument-all-but-first-arg (list 'a (bt 'b [14]) (bt 'c [15]))
80+
#'t/instrument-second-arg (list 'a (bt 'b [14]) 'c)
81+
#'t/instrument-two-args (list (bt 'a [13]) (bt 'b [14]) 'c)))
82+
83+
(deftest instrument-clauses
84+
(are [exp res] (= (#'t/instrument dex exp)
85+
res)
86+
87+
'(cond-> value
88+
v2 form
89+
v3 form)
90+
`(~'b (~'cond-> ~(bt 'value [13 1])
91+
~(bt 'v2 [13 2]) ~'form
92+
~(bt 'v3 [13 4]) ~'form)
93+
~(id [13]))
94+
95+
'(case value
96+
const expr
97+
default)
98+
`(~'b (~'case ~(bt 'value [13 1])
99+
~'const ~(bt 'expr [13 3])
100+
~(bt 'default [13 4]))
101+
~(id [13]))
102+
103+
'(condp pred value
104+
v4 :key v5)
105+
`(~'b (~'condp ~(bt 'pred [13 1]) ~(bt 'value [13 2])
106+
~(bt 'v4 [13 3]) :key ~(bt 'v5 [13 5]))
107+
~(id [13]))
108+
109+
'(condp pred value
110+
v2 v3
111+
default)
112+
`(~'b (~'condp ~(bt 'pred [13 1]) ~(bt 'value [13 2])
113+
~(bt 'v2 [13 3]) ~(bt 'v3 [13 4])
114+
~(bt 'default [13 5]))
115+
~(id [13]))
116+
117+
'(cond
118+
(= x 1) true
119+
false never
120+
:else final)
121+
`(~'b
122+
(~'cond (~'b (~'= ~(bt 'x [13 1 1]) 1) ~(id [13 1]))
123+
true
124+
false ~(bt 'never [13 4])
125+
:else ~(bt 'final [13 6]))
126+
~(id [13]))))
127+
128+
129+
(deftest instrument-recur
130+
(is (= (#'t/instrument dex '(loop [x '(1 2)]
131+
(if (seq x)
132+
(recur (rest x))
133+
x)))
134+
`(~'b (~'loop [~'x ~(bt ''(1 2) [13 1 1])]
135+
(~'if (~'b (~'seq ~(bt 'x [13 2 1 1]))
136+
~(id [13 2 1]))
137+
(~'recur (~'b (~'rest ~(bt 'x [13 2 2 1 1]))
138+
~(id [13 2 2 1])))
139+
~(bt 'x [13 2 3])))
140+
~(id [13]))))
141+
142+
(is (= (#'t/instrument dex '(fn [x]
143+
(if (seq x)
144+
(recur (rest x))
145+
x)))
146+
`(~'fn [~'x]
147+
(~'if (~'b (~'seq ~(bt 'x [13 2 1 1]))
148+
~(id [13 2 1]))
149+
(~'recur (~'b (~'rest ~(bt 'x [13 2 2 1 1]))
150+
~(id [13 2 2 1])))
151+
~(bt 'x [13 2 3]))))))
68152

69153
(deftest specifier-match-bindings
70154
(are [f] (= 1 (#'t/specifier-match-bindings f))

0 commit comments

Comments
 (0)