Last active
August 29, 2015 14:17
-
-
Save ohpauleez/4ca0b646b89512f38ea2 to your computer and use it in GitHub Desktop.
Another debug repl
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Simpler debug-repl based on https://github.com/stuarthalloway/circumspec/blob/master/src/clojure/contrib/debug.clj | |
;; This `debug-repl` also tries to capture line metadata from forms/files, as well as exceptions | |
;; Some people may want to rename `debug-repl` to `break`, and `quit-dr` to `continue` | |
(defmacro local-bindings | |
"Produces a map of the names of local bindings to their values." | |
[] | |
(let [symbols (map key @clojure.lang.Compiler/LOCAL_ENV)] | |
(zipmap (map (fn [sym] `(quote ~sym)) symbols) symbols))) | |
(def ^:dynamic *locals*) | |
(defn eval-with-locals | |
"Evals a form with given locals. | |
The locals should be a map of symbols to values." | |
[locals form] | |
(binding [*locals* locals] | |
(eval | |
`(let ~(vec (mapcat #(list % `(*locals* '~%)) (keys locals))) | |
~form)))) | |
(defn dr-read | |
[request-prompt request-exit] | |
(let [input (clojure.main/repl-read request-prompt request-exit)] | |
(if (#{'() request-exit request-prompt} input) | |
request-exit | |
input))) | |
(def ^{:dynamic true :tag 'long} level (long 0)) | |
(def counter (atom 1000)) | |
(defn inc-counter [] | |
(swap! counter inc)) | |
(def element (atom nil)) | |
(def quit-dr-exception | |
(proxy [Exception java.util.Enumeration] [] | |
(nextElement [] @element))) | |
(defn quit-dr [& form] | |
(reset! element (first form)) | |
(throw quit-dr-exception)) | |
(defn caught [exc] | |
(if (or (= exc quit-dr-exception) | |
(= (.getCause ^Throwable exc) quit-dr-exception)) | |
(throw quit-dr-exception) | |
(clojure.main/repl-caught exc))) | |
(defmacro debug-repl | |
"Starts a REPL with the local bindings available." | |
([] | |
`(debug-repl nil ~(meta &form))) | |
([retform] | |
`(debug-repl retform ~(meta &form))) | |
([retform form-meta] | |
`(let [debug-meta# ~(assoc form-meta | |
:ns *ns* | |
:file *file*) | |
eval-fn# (fn [e-form#] | |
(eval-with-locals (merge {(symbol "debug-meta") debug-meta# | |
(symbol "in-debug-ns") (fn [] (in-ns (ns-name (:ns debug-meta#))))} | |
(local-bindings) | |
{(symbol "quit-dr") ~quit-dr}) e-form#))] | |
(try | |
(binding [level (unchecked-inc level)] | |
(clojure.main/repl | |
:init (fn [] | |
(use 'clojure.core) | |
(use 'clojure.repl)) | |
:prompt #(print (str "dr-" level "-" (inc-counter) " [" *ns* "]=> ")) | |
:eval eval-fn# | |
:read dr-read | |
:caught caught)) | |
(catch Exception e# | |
(if (= e# quit-dr-exception) | |
(if-let [new-form# (.nextElement ^java.util.Enumeration quit-dr-exception)] | |
(eval-fn# new-form#) | |
(eval-fn# ~retform)) | |
(throw e#))))))) | |
(defmacro assert-repl [assertion-form] | |
`(when *assert* | |
(if-not ~assertion-form | |
(debug-repl) | |
;;Forward standard assert behavior, even if that's just pr-str | |
(assert ~assertion-form)))) | |
(defmacro try-repl [body-form] | |
`(try | |
~body-form | |
(catch Throwable ex# | |
(let [~'debug-exception ex#] | |
(debug-repl))))) | |
(defn make-error-handler | |
"This takes a function that takes two args | |
and returns an UncaughtExceptionHandler that will run that function with the | |
thread/runnable and the Exception." | |
[f] | |
(proxy [Thread$UncaughtExceptionHandler] [] | |
(uncaughtException [thread exception] | |
(f thread exception)))) | |
(def debug-ex-handler (make-error-handler (fn [thread debug-exception] (debug-repl)))) | |
(defn debug-uncaught-exceptions! [] | |
(Thread/setDefaultUncaughtExceptionHandler debug-ex-handler)) | |
;; Easy searching for the function I want :: https://gist.github.com/alandipert/1619740 | |
(defn findcore | |
"Returns a lazy sequence of functions in clojure.core that, when applied to args, | |
return ret." | |
([args ret] | |
(findcore (filter #(not (:macro (meta %))) | |
(vals (ns-publics 'clojure.core))) args ret)) | |
([[f & fns] args ret] | |
(lazy-seq | |
(when f | |
(if (binding [*out* (proxy [java.io.Writer] [] | |
(write [_]) | |
(close []) | |
(flush []))] | |
(try | |
(= ret (apply f args)) | |
(catch Throwable t))) | |
(cons (:name (meta f)) (findcore fns args ret)) | |
(findcore fns args ret)))))) | |
(defmacro fc [args ret] | |
`(findcore '~args '~ret)) | |
;; Useful one-off functions | |
;; Construct | |
;; (construct [inc dec] [10 10]) => (11 9) | |
;(def construct (partial map deliver)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment