Skip to content

Instantly share code, notes, and snippets.

@saikyun
Last active May 27, 2019 09:33
Show Gist options
  • Save saikyun/909627cff10e5aea31226aa645b0b6b5 to your computer and use it in GitHub Desktop.
Save saikyun/909627cff10e5aea31226aa645b0b6b5 to your computer and use it in GitHub Desktop.
defn macro that prints the form that threw an error
(ns miracle.catcher
(:require [clojure.walk :as w]
[clojure.test :as t]))
(defn unwrap
"Traverses a body using `f` (e.g. `map`), and removes calls to `try-body`."
[f body]
(f
#(if (and (coll? %)
(= (first %) 'miracle.catcher/try-body))
(second %)
%)
body))
(defmacro try-body
[body]
`(try ~body
(catch Exception ~'e
(println (unwrap w/postwalk '~body) "threw an exception!")
(arcadia.core/log (unwrap w/postwalk '~body) "threw an exception!")
(throw ~'e))))
#_(macroexpand '(try-body (+ 1 1)))
#_(w/macroexpand-all '(try-body (+ (try-bod (/ 1 0)) 1)))
#_(try-body (+ (try-body (/ 1 0)) 1))
(use 'game.save)
(defn macro?
"Takes a sym, check if it resolves to a macro."
[sym]
(some-> (resolve sym)
meta
:macro))
(defn wrap-body
"If node is a collection, wrap it in `try-body`."
[node]
(if (and (or (list? node) (seq? node))
(ifn? (first node)))
(let [head (first node)]
(cond
(and (symbol? head) (macro? head))
`(try-body ~(map #(w/postwalk wrap-body %) (w/macroexpand-all (unwrap w/postwalk node))))
(special-symbol? head)
`(try-body ~(unwrap w/postwalk node))
:default `(try-body ~node)))
node))
(defn instrument-fn*
"Instruments a fn-form, e.g. (fn ([x] (+ x x))).
Every call inside the form will be wrapped with `try-body`."
[[f & bodies]]
(let [new-bodies (doall (for [[params & body] bodies]
(concat (list params)
(map #(w/postwalk wrap-body %) body))))]
(concat (list f) new-bodies)))
#_(instrument-fn* '(fn ([x] (+ x x) (* x (* 10 x)) (- 10 10))))
(defn instrument-fn
[node]
(if (and (seq? node)
(= (first node) 'clojure.core/fn))
(instrument-fn* node)
node))
#_(instrument-fn '(clojure.core/fn ([x] (+ x x) (* x (* 10 x)) (- 10 10))))
(defmacro defn-catch [& all]
"Works like `defn`, but when something inside it throws, you get to see which form threw."
(let [defn-sexps (macroexpand `(defn ~@all))]
(w/postwalk
instrument-fn
defn-sexps)))
#_(macroexpand '(defn-catch a [x] (+ x x) (* x x) (/ x x)))
#_(macroexpand '(defn-catch instrument-fn
[node]
(if (and (seq? node)
(= (first node) 'clojure.core/fn))
(instrument-fn* node)
node)))
#_(defn-catch a [x] (+ x x) (* x x) (/ x x))
#_(a 5)
#_(a 0)
#_(macroexpand '(defn-catch b [x]
(+ x x)
(* x x)
(/ x
(let [x (+ 5 5)
y '(1 2 3)]
(do 5 (/ x x))))))
#_(defn-catch b [x]
(+ x x)
(* x x)
(/ x
(let [z (+ x x)
y '(1 2 z)]
z
(/ x x))))
#_(b 5)
#_(b 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment