Significantly refactored and expanded to deal with various special
forms, including case, reify, and deftype. Some edge cases fixed, all
the ones I could find -- notably, the first test below the code
confirms the fixing of one of these. Let me know of any more edge
cases you find that need handling differently.

There's one major limitation: macrolet macros are invisible inside
macrolet macros. That is, this won't work:

(let [x 2]
  (macrolet [(foo [x] `(list 'quote ~x))
             (bar [y] `(println ~x ~(foo y)))]
    (bar sym)))

to give this:

2 sym
nil



Code:


(defn quo [expr] `(quote ~expr))

(def sfrms-ignoreall '#{quote var})

(def sfrms-ignore2 '#{new set! def})

(def sfrms-ignore1 '#{if do recur throw monitor-enter
                      monitor-exit .})

(defn union [s1 s2]
  (if (empty? s2)
    s1
    (apply conj s1 s2)))

(declare proc)

(defn proch [form fnmap locals newlocals skip]
  (let [fnmap (apply dissoc fnmap newlocals)
        locals (union locals newlocals)]
    (if skip
      (let [[skipped procd] (split-at skip form)]
        (concat
          skipped
          (map #(proc % fnmap locals) procd)))
      (proc form fnmap locals))))

(declare macrolet-v)

(defn proc [form fnmap locals]
  (if (or (not (coll? form)) (empty? form))
    form
    (if-not (seq? form)
      (map-ps #(proc % fnmap locals) form)
      (let [op (first form)]
        (cond
          (= op 'fn*) (if (vector? (second form))
                        (let [nls (second form)]
                          (proch form fnmap locals nls 2))
                        (let [fname (nth form 1 nil)
                              [nls skip] (if (symbol? fname)
                                           [[fname] 2]
                                           [nil 1])
                              [skipped procd] (split-at skip form)]
                          (concat
                            skipped
                            (map
                              (fn [[argl & rst]]
                                (cons argl
                                  (proch rst fnmap locals
                                    (concat argl nls) 0)))
                              procd))))
          (or
            (= op 'let*)
            (= op 'letfn*)
            (= op 'loop*)) (let [nls (take-nth 2 (second form))]
                             (conj
                               (proch (rest (rest form)) fnmap
                                 locals nls 0)
                               (loop [nls #{}
                                      out []
                                      in (seq (partition 2
                                                (second form)))]
                                 (if in
                                   (let [[nm expr] (first in)]
                                     (recur
                                       (conj nls nm)
                                       (conj out
                                         nm
                                         (proch expr fnmap
                                           locals nls nil))
                                       (next in)))
                                   out))
                               op))
          (= op 'try) (map
                        (fn [subf]
                          (if (or
                                (not (seq? subf))
                                (empty? subf))
                            (proc subf fnmap locals)
                            (condp = (first subf)
                              'finally (proch subf fnmap
                                         locals nil 1)
                              'catch (let [nl (nth subf 2 nil)
                                           nls (if nl [nl])]
                                       (proch subf fnmap
                                         locals nl 3))
                              (proc subf fnmap locals))))
                        form)
          ('#{deftype* reify*} op) (let [nls (if (= op 'deftype*)
                                               (nth form 3 nil))
                                         spos (if (= op 'deftype*)
                                                6
                                                2)
                                         [skipped procd] (split-at
                                                           spos
                                                           form)]
                                     (concat
                                       skipped
                                       (map
                                         (fn [subf]
                                           (if-not (seq? subf)
                                             subf
                                             (let [[fnm as] subf
                                                   nls (conj
                                                         (concat
                                                           nls as)
                                                         fnm)]
                                               (proch subf fnmap
                                                 locals nls 2))))
                                         procd)))
          (= op 'case*) (let [[skipd procd] (split-at 6 form)
                              [d m skip] procd]
                          (concat
                            skipd
                            [(proc d fnmap locals)]
                            [(into {}
                               (map
                                 (fn [[k [cl expr]]]
                                   [k (first {cl (proc expr
                                                   fnmap locals)})])
                                 m))]
                            [skip]))
          (sfrms-ignoreall op) form
          (sfrms-ignore2 op) (if (>= (count form) 2)
                               (conj
                                 (map #(proc % fnmap locals)
                                   (rest (rest form)))
                                 (second form)
                                 (first form)))
          (sfrms-ignore1 op) (conj
                               (map #(proc % fnmap locals)
                                 (rest form))
                               (first form))
          (fnmap op) (proc
                        (apply (fnmap op)
                          (rest form))
                        fnmap locals)
          (locals op) (map-ps
                        #(proc % fnmap locals)
                        form)
          :else (let [nested (if (symbol? op)
                               (if-let [v (ns-resolve *ns* op)]
                                 (if (.isBound v)
                                   (= @v macrolet-v))))]
                  (if nested
                    (proc
                      (macroexpand-1
                        `(macrolet*
                           ~(union locals (keys fnmap))
                           ~@(rest form)))
                      fnmap locals)
                    (let [f (macroexpand-1 form)]
                      (if-not (= f form)
                        (recur f fnmap locals)
                        (map-ps #(proc % fnmap locals)
                          form))))))))))

(defmacro macrolet* [shadowlist fnspecs & body]
  (let [locals (keys &env)
        gensyms (for [_ locals] (gensym))
        qlocals (map quo (for [_ locals] (gensym)))
        lqmap (zipmap locals qlocals)
        qgensyms (map quo gensyms)
        fnmap (reduce
                (fn [fnm [name argl & body]]
                  (assoc fnm name
                    (eval
                      `(fn ~name ~argl
                         (let ~(vec
                                 (mapcat identity
                                   (apply dissoc lqmap
                                     (flatten argl))))
                           `(let ~~(vec (interleave
                                          qlocals qgensyms))
                              ~~@body))))))
                  {}
                  fnspecs)]
    `(let ~(vec (interleave gensyms locals))
       ~@(map
           #(proc % fnmap (union (set shadowlist) locals))
           body))))

(defmacro macrolet [fnspecs & body]
  `(macrolet* #{} ~fnspecs ~@body))

(def macrolet-v @(ns-resolve *ns* 'macrolet))


New tests (let binding expressions, function locals, case, reify, deftype):

Let/loop variables shadow macro for later expressions in binding
vector, except own, but not earlier ones:

user=> (macrolet [(x [y]
                    `(do (println ~y)
                    (fn [a#] (inc a#))))]
         (let [x (x 3) x (x 3)] x))
3
4


Function locals shadow macro:

user=> (macrolet [(x [a] `(println ~a))]
         ((fn
            ([x]
              (x 42))
            ([y z]
              (x "boo!")
              (+ y z)))
           inc))
43


Function locals in one arity do not shadow macro in another:

user=> (macrolet [(x [a] `(println ~a))]
         ((fn
            ([x]
              (+ 3 x))
            ([y z]
              (x "boo!")
              (+ y z)))
           3 4))
boo!
7


Function name shadows macro in named fn:

user=> (macrolet [(x [a] `(println ~a))]
         ((fn x
            ([x]
              (+ 3 x))
            ([y z]
              (println (x 12))
              (+ y z)))
         3 4))
15
7


Case still works despite case* expecting naked MapEntry objects (as
map vals), and case labels that are unquoted local macro names are
handled correctly:

user=> (macrolet [(foo [x] (println "macro expanded") `(* 3 ~x))]
         (case 'foo
           foo "case foo"
           "default"))
"case foo"


Local macros are expanded in cases, including default case:

user=> (macrolet [(foo [x] (println "macro expanded") `(* 3 ~x))]
         (case 'bar
           foo "case foo"
           (foo 3)))
macro expanded
9
user=> (macrolet [(foo [x] (println "macro expanded") `(* 3 ~x))]
         (case 'foo
           foo (foo 3)
           "bar"))
macro expanded
9


Instance variables shadow macro in deftype:

user=> (defprotocol IFoo (foo [this]) (bar [this that]))
user.IFoo
user=> (macrolet [(x [a] `(println ~a))]
         (deftype Foo [x]
           IFoo
           (foo [this]
             (x 42))
           (bar [this y]
             (+ (.foo this) y))))
user.Foo
user=> (.bar (Foo. (constantly 42)) 8)
50


Local variables shadow macro in deftype and reify methods; also the
fn* form #() expands into is handled correctly:

user=> (defprotocol IFoo (foo [this]) (bar [this y]))
user.IFoo
user=> (macrolet [(x [a] `(println ~a))]
         (deftype Foo [y]
           IFoo
           (foo [this]
             y)
           (bar [this x]
             (x this))))
user.Foo
user=> (.bar (Foo. 42) #(inc (.foo %)))
43
user=> (macrolet [(x [a] `(println ~a))]
         (.bar
           (reify
             IFoo
             (foo [this]
               42)
             (bar [this x]
               (x this))) #(inc (.foo %))))
43


Macro does not clobber deftype or reify method of same name:

user=> (defprotocol IFoo (foo [this]) (bar [this y]))
user.IFoo
user=> (macrolet [(foo [a b] `(println ~a ~b))]
         (deftype Foo [y]
           IFoo
           (foo [this]
             y)
           (bar [this x]
             (x this))))
user.Foo
user=> (.bar (Foo. 42) #(inc (.foo %)))
43
user=> (macrolet [(foo [a b] `(println ~a ~b))]
         (.bar
           (reify
             IFoo
             (foo [this]
               42)
             (bar [this x]
               (x this))) #(inc (.foo %))))
43


Older tests (basic locals shadowing, nesting, etc. behavior):

Local variables in let/loop body shadow macro; macro body closes over
local bindings correctly:

user=> (defmacro foo [x] 3)
#'user/foo
user=> (let [x 2]
         (macrolet [(foo [y] `(println ~x ~y))
                    (bar [x] `(println "macro bar" ~x))]
           (println "bar-1")
           (bar 42)
           (let [x 3 bar (fn [x] (str "fn bar " x))]
             (println "foo-1")
             (foo x)
             (println "bar-2")
             (bar 42))))
bar-1
macro bar 42
foo-1
2 3
bar-2
"fn bar 42"


Outer macrolet shadows global macro within inner macrolet:

user=> (defmacro foo [x] 3)
#'user/foo
user=> (macrolet [(foo [] 1)]
         (macrolet [(bar [] 2)]
           (foo)))
1


Inner macrolet shadows outer macrolet and global macro with
intervening macrolet:

user=> (defmacro foo [x] 3)
#'user/foo
user=> (macrolet [(foo [] 1)]
         (macrolet [(bar [] 2)]
           (macrolet [(foo [] 42)]
             (foo))))

42


Macrolet itself is shadowed by locals named "macrolet" within a macrolet:

user=> (macrolet [(foo [] 1)]
         (let [bar (fn [x y] 17)
               macrolet (fn [x y] y)]
           (macrolet [(bar [+ *] 42)]
             (bar 1 2))))
17

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to