Skip to content

Instantly share code, notes, and snippets.

@mhuebert
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mhuebert/c03c131ca0f43f034f0c to your computer and use it in GitHub Desktop.
Save mhuebert/c03c131ca0f43f034f0c to your computer and use it in GitHub Desktop.
Clojure macro for printing docstrings as functions are called (eventually, interpolated w/ args)
(ns prndoc.core
(require
[clojure.core.strint :refer [<<]]
[clojure.core.match :refer [match]]))
(defn has-splat? [arglist]
((complement nil?) (first (last (split-with #(not= % '&) arglist)))))
(defn parse-arg-pattern
"Argument pattern [num-args has-splat?]. Pattern of [func & body] is [1 true]."
[arglist]
(if
(has-splat? arglist) [(- (count arglist) 2) true] [(count arglist) false]
)
)
(defn match-arglist
"Find the matching arglist."
[arglists args]
(first (filter #(or (= (parse-arg-pattern args) (parse-arg-pattern %))
(and (= (last (parse-arg-pattern %)) true) (> (first (parse-arg-pattern args)) (first (parse-arg-pattern %))))
)
arglists)))
(defn arg-bindings
"Return bindings of form [n 4 members []]"
[arglists args]
(let [arglist (match-arglist arglists args)
[num-args splat?] (parse-arg-pattern arglist)
split-args (split-at num-args args)]
(if splat? (vec (interleave (filter #(not= '& %) arglist) (conj (vec (first split-args)) (vec (last split-args)))))
(vec (interleave arglist args)))
))
(defn docprint-decorator
"Print a function's docstring at runtime, with interpolated values of args."
[m# f]
(fn [& args]
(eval `(let ~(arg-bindings (:arglists m#) args) (prn (<< ~(:doc m#)))))
(apply f args)))
(defmacro docprint!
"Apply docprint decorator to function. Do not apply more than once."
[sym]
`(let [m# (meta (var ~sym))]
(when (not= true (:docprint m#))
(def ~sym (docprint-decorator m# ~sym))
(reset-meta! (var ~sym) (assoc m# :docprint true)))
(var ~sym)))
; toy functions
(defn elect-n
"Electing ~{n} members to a group"
([n] (elect-n n []))
([n members] members)
([n members & options] members))
(defn accept-many-args
"This function was called with ~(count more) additional arguments, ~(+ 2 (count more)) in total."
[n1 n2 & more])
(defn add-two
"Adding ~{a} to ~{b}."
[a b]
(+ a b))
(docprint! accept-many-args)
(docprint! elect-n)
(docprint! add-two)
(do
(accept-many-args 1 2 3 4 5 6 7 8 9 10)
(elect-n 5 [])
(add-two 1 2))
;has-splat?
(= false (has-splat? '[n members]))
(= true (has-splat? '[func & body]))
;match-arglist
(= '[n] (match-arglist '([n] [n members] [n members & options]) '(4)))
(= '[n members] (match-arglist '([n] [n members] [n members & options]) '(4 [])))
(= '[n members & options] (match-arglist '([n] [n members] [n members & options]) '(4 [] "fred")))
;parse-arg-pattern
(= [1 true] (parse-arg-pattern '[func & body]))
(= [2 false] (parse-arg-pattern '[n members]))
;arg-bindings
(= '[n 4] (arg-bindings '([n] [n members] [n members & options]) '(4)))
(= '[n 4 members []] (arg-bindings '([n] [n members] [n members & options]) '(4 [])))
(= '[n 4 members [] options (1 2)] (arg-bindings '([n] [n members] [n members & options]) '(4 [] 1 2)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment