Skip to content

Instantly share code, notes, and snippets.

@sogaiu
Forked from saikyun/save.clj
Last active February 11, 2019 23:54
Show Gist options
  • Save sogaiu/61e71eaf7fc81b8c0fe70ff30d9ceb8e to your computer and use it in GitHub Desktop.
Save sogaiu/61e71eaf7fc81b8c0fe70ff30d9ceb8e to your computer and use it in GitHub Desktop.
Proto repl save functionality for ClojureCLR + initial docs attempt
(ns miracle.tools.save
(:require [clojure.walk :refer [postwalk]]))
;; usage
;;
;; (save) or (save :whatever) somewhere in a function to save all the local
;; bindings at that point in time.
;;
;; (save-func ...) where ... is the body of your functions (e.g. (defn hej [a]
;; (save-func (let [b 5] (+ a b)))))
;;
;; (print-saves hej) or (print-saves hej :whatever) where hej is either a var
;; or a symbol, and the keyword matches what you wrote in save. without a
;; keyword it uses :__funcall, which is what save-func uses automatically. save
;; without a keyword uses :__default. if you're ever unsure, just write @saves
;; and you'll see all stored data.
;;
;; (clear-saves!) clears the atom containing the saves
;;
;; tips and notes
;;
;; you also have (ld hej) to load the last values of a function call, or
;; (ld hej :whatever) to load values stored with save
;; this defs all the locals so you can evaluate parts of your functions :)
;;
;; also any error that occured in a save-func will be stored as well
;;
;; currently save without keyword needs to be printed with :__default as
;; a keyword. That's because print currently uses :__funcall as the
;; default :)
;;
;; Basically save without keyword = :__default
;; save-func without keyword = :__funcall
;; And print without keyword = :__funcall
(def ^:dynamic *max-saves* 1000000000)
(defn gensym? [s]
(re-find #"__\d+" s))
(defn fn->var
"Takes a function and returns a var. Works even on function objects."
[f]
(-> (str f)
clojure.repl/demunge
(clojure.string/replace #"eval--\d+" "")
(clojure.string/replace #"--\d+" "")
(clojure.string/replace #"(/(?=[^/]*/))" ".") ;; replace all but last / with .
symbol
(#(eval `(var ~%)))))
;; Contains the saved local bindings of various vars and identifiers.
(defonce saves (atom {}))
(defn clear-saves! [] (reset! saves {}))
(defn new*
"Creates a new save at `var` and `id`, with `bindings`."
([var id]
(new* var id {}))
([var id bindings]
(swap! saves update-in [var id]
(fn [saves]
(-> (conj saves bindings) ;; put it on the stack!
(#(if (< (count %) *max-saves*)
(into '() (reverse (take *max-saves* %)))
%)))))))
(defn update*
"Updates a specific binding `k` using `f` for a `var` and an `id`entifier."
([sym id k val f]
(let [binding-list (get-in @saves [sym id])]
(if binding-list
(let [v (get (peek binding-list) k)
res (f v val)]
(swap! saves assoc-in [sym id] (conj (pop binding-list) (assoc (peek binding-list) k res)))
val)
(let [res (f nil val)]
(swap! saves assoc-in [sym id] (list {k res}))
val)))))
(defn save-fn
([bindings] (save-fn bindings :__default))
([bindings id]
(let [callee `(fn->var (second (last (sort-by first (filter #(re-matches #"fn__\d+" (name (key %))) ~bindings)))))] (save-fn bindings callee id)))
([bindings callee id]
(let [bindings `(into {} (remove #(gensym? (name (key %))) ~bindings))]
`(new* ~callee ~id ~bindings))))
(defmacro get-env
[]
(into {} (for [k (keys &env)]
[`'~k k])))
(defmacro save
"Used to save all local bindings, takes an optional identifier as a parameter.
The identifier is used with `ld` in order to load the local bindings at a specific point.
If no identifier is provided, a default value is used instead."
[& args]
(apply save-fn
(into {} (for [k (keys &env)]
[`'~k k]))
args))
(defn get-save
([sym]
(get-save sym :__default))
([sym id]
(let [sym (cond (keyword? sym) sym
(var? sym) sym
(symbol? sym) (resolve sym)
:otherwise (fn->var sym))]
(get-in @saves [sym id]))))
(defn ld
"Loads the local bindings that have been saved in function `v` at point `id`.
If `id` is omitted, a default value is used instead."
([v] (ld v :__default))
([v id]
(let [locals (first (get-save v id))]
(when-not (nil? locals)
(do
(println "Defining" (keys locals))
(doseq [[sym val] locals]
(try
(eval `(def ~(symbol sym) '~val))
(catch Exception e (do (prn sym val))))))))))
(defn fld
"Loads the local bindings that have been saved in function `v` using save-func."
([v]
(let [locals (first (get-save v :__funcall))]
(when-not (nil? locals)
(let [lets (into {} (:lets locals))
args (into {} (:args locals))
locals (merge args lets)]
(println "Defining" (keys locals))
(doseq [[sym val] locals]
(try
(eval `(def ~(symbol sym) '~val))
(catch Exception e (do (prn sym val)))))))))
([v & [extra]]
(let [pred (if (= extra :error) :error (constantly true))
locals (first (filter pred (get-save v :__funcall)))]
(when-not (nil? locals)
(let [lets (into {} (:lets locals))
args (into {} (:args locals))
all-lets (:lets locals)
error (:error locals)
locals (merge args lets {'all-lets all-lets} {'args_ args} (when error {'error error}))]
(prn (:error locals))
(println "Defining" (keys locals))
(doseq [[sym val] locals]
(try
(eval `(def ~(symbol sym) '~val))
(catch Exception e (do (prn sym val))))))))))
(defmacro let-save-specific
[sym id bindings & body]
(let [bindings (->> (partition 2 bindings)
(map (fn [[k v]] [k `(update* ~sym ~id :lets ~v #(into [] (conj %1 ['~k %2])))]))
(apply concat)
(into []))]
`(let ~bindings ~@body)))
(defmacro save-func-specific
[id & body]
(let [env (into {} (for [k (keys &env)]
[`'~k k]))
callee `(fn->var (second (last (sort-by first (filter #(re-matches #"fn__\d+" (name (key %))) ~env)))))]
`(do
(new* ~callee ~id {:__args (into {} (remove #(gensym? (name (key %))) ~env))})
~@(postwalk #(if (and (list? %)
(symbol? (first %))
(= (resolve (first %))
#'clojure.core/let))
(apply list 'let-save-specific callee id (rest %)) %)
body))))
(defmacro save-func
[& body]
(let [env (into {} (for [k (keys &env)]
[`'~k k]))
callee `(fn->var (second (last (sort-by first (filter #(re-matches #"fn__\d+" (name (key %))) ~env)))))]
`(do
(miracle.tools.save/new* ~callee :__funcall {:args (into {} (remove #(gensym? (name (key %))) ~env))})
(try
~@(postwalk #(if (and (list? %)
(symbol? (first %))
(= (resolve (first %))
#'clojure.core/let))
(apply list 'let-save-specific callee :__funcall (rest %)) %)
body)
(catch Exception ~'e (do (update* ~callee :__funcall :error ~'e #(identity %2))
(throw ~'e)))))))
(defn inspect-map [map-to-print & {:keys [desired-level safe-count]
:or {desired-level 4 safe-count 10}}]
(binding [*print-level* desired-level *print-length* safe-count]
(clojure.pprint/pprint map-to-print)))
(defn print-saves
"Loads the local bindings that have been saved in function `v` at point `id`.
If `id` is omitted, a default value is used instead."
([v] (print-saves
v :__funcall))
([v id]
(let [v (cond (keyword? v) v
(var? v) v
(symbol? v) (resolve v)
:otherwise (fn->var v))
locals (take 10 (get-in @saves [v id]))]
(doseq [i (reverse (range (count locals)))]
(println "Entry no." i)
(inspect-map (first (drop i locals)))
(prn)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment