|
45 | 45 | Used only for the purposed of instrumenting on special symbols which
|
46 | 46 | don't have arglists (such as `if` or `try`), or for macros whose
|
47 | 47 | usual arglist we wouldn't understand.
|
48 |
| - |
| 48 | +
|
49 | 49 | This can also be used to disable instrumenting of a form by setting
|
50 | 50 | its arglist to ([]).
|
51 | 51 |
|
52 | 52 | Finally, if the key's value is a symbol, it is taken as an alias and
|
53 | 53 | 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->}) |
60 | 70 |
|
61 | 71 | (defn- macro-arglists
|
62 | 72 | "Return a list of possible arglist vectors for symbol."
|
|
68 | 78 | (let [metadata (if (var? symbol)
|
69 | 79 | (meta symbol)
|
70 | 80 | (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). |
71 | 83 | (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))))))) |
73 | 90 |
|
74 | 91 | ;;; Because of the way we handle arglists, we need the modifiers such
|
75 | 92 | ;;; as + or & to come before the argument they're affecting. That's
|
|
192 | 209 | forms
|
193 | 210 | (range (count forms))))))
|
194 | 211 |
|
195 |
| -(defn- instrument-two-args |
| 212 | +(defn- instrument-second-arg |
196 | 213 | [{:keys [coor] :as ex} [form1 form2 & forms]]
|
197 |
| - (cons (instrument ex form1) |
| 214 | + (cons form1 |
198 | 215 | (let [n (last coor)
|
199 | 216 | coor (vec (butlast coor))]
|
200 | 217 | (cons (instrument (assoc ex :coor (conj coor (inc n))) form2)
|
201 | 218 | forms))))
|
202 | 219 |
|
| 220 | +(def instrument-two-args |
| 221 | + #(instrument-next-arg %1 (instrument-second-arg %1 %2))) |
| 222 | + |
203 | 223 | (declare instrument-special-form-try)
|
204 | 224 | (def specifier-map
|
205 | 225 | "Map between specifiers and [matcher handler] functions pairs.
|
|
220 | 240 | if the matcher returned nil."
|
221 | 241 |
|
222 | 242 | {;; Safe to instrument
|
223 |
| - "else" [always-1 instrument-next-arg] |
224 | 243 | "expr" [always-1 instrument-next-arg]
|
| 244 | + "init" [always-1 instrument-next-arg] |
225 | 245 | "pred" [always-1 instrument-next-arg]
|
226 |
| - "then" [always-1 instrument-next-arg] |
227 | 246 | "test" [always-1 instrument-next-arg]
|
| 247 | + "then" [always-1 instrument-next-arg] |
| 248 | + "else" [always-1 instrument-next-arg] |
228 | 249 | ;; Match everything.
|
229 | 250 | "body" [count instrument-all-args]
|
230 | 251 | ;; Not safe or not meant to be instrumented
|
231 | 252 | "form" [always-1 instrument-nothing]
|
232 | 253 | "oldform" [always-1 instrument-nothing]
|
233 | 254 | "params" [always-1 instrument-nothing]
|
234 | 255 | "name" [(fn [[f]] (if (symbol? f) 1)) instrument-nothing]
|
| 256 | + "symbol" [(fn [[f]] (if (symbol? f) 1)) instrument-nothing] |
235 | 257 | ;; Complicated
|
236 | 258 | "bindings" [specifier-match-bindings
|
237 | 259 | (fn [ex [bindings & forms]]
|
238 | 260 | (cons (instrument-bindings ex bindings) forms))]
|
239 | 261 | "docstring" [#(when (string? (first %)) 1) instrument-nothing]
|
240 | 262 | "string" [#(when (string? (first %)) 1) instrument-nothing]
|
241 | 263 | "map" [#(when (map? (first %)) 1) instrument-next-arg]
|
242 |
| - "clause" [#(when (> (count %) 1) 2) instrument-two-args] |
243 | 264 | "fn-tail" [#(when (vector? (first %)) (count %))
|
244 | 265 | instrument-all-but-first-arg]
|
245 | 266 | "dispatch-fn" [(fn [[[f & r]]]
|
246 | 267 | (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]}) |
248 | 277 |
|
249 | 278 | (defn- specifier-destructure
|
250 | 279 | "Take a symbol specifier and return a vector description.
|
|
268 | 297 | (if (.endsWith spec-name "s")
|
269 | 298 | (subs spec-name 0 (dec (count spec-name)))
|
270 | 299 | (str spec-name "s")))
|
| 300 | + (and (.endsWith spec-name "-symbol") (specifier-map "symbol")) |
271 | 301 | (and (.endsWith spec-name "-string") (specifier-map "string"))
|
272 | 302 | (and (.endsWith spec-name "-map") (specifier-map "map"))
|
273 | 303 | [always-1 instrument-nothing])
|
|
445 | 475 | ([form ex]
|
446 | 476 | `(~(:breakfunction ex) ~form ~ex)))
|
447 | 477 |
|
| 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 | + |
448 | 496 | (defn- instrument-function-like-form
|
449 | 497 | "Instrument form representing a function/macro call or special-form."
|
450 | 498 | [ex [name & args :as form]]
|
451 | 499 | (if (symbol? name)
|
452 | 500 | (let [name (or (ns-resolve *ns* name) name)]
|
453 | 501 | (if (or (resolve-special name)
|
454 | 502 | (:macro (meta name)))
|
455 |
| - (if (irrelevant-return-value-macros name) |
| 503 | + (if (dont-break? form) |
456 | 504 | (instrument-special-form ex form)
|
457 | 505 | (with-break instrument-special-form form ex))
|
458 | 506 | (with-break instrument-coll form ex)))
|
|
0 commit comments