Hatena::Groupcadr

kozima の日記

2009-07-22

macroexpand-all

| 00:20

なんだか流行りのようなので思いつくままに作り方を。*1 xyzzy で作ろうとしたことがあるような。たぶん作りかけで不完全なままになってます。

基本

基本的にはこんな感じになると思うんですが。

(defun macroexpand-all (x)
  (let ((y (macroexpand x)))
    (if (consp y)
        (cons (car y) (mapcar #'macroexpand-all (cdr y)))
      y)))

これだけだと special form のことが考えられてないので、意地悪なコードを展開すると

;; (and or) は変数の宣言であってマクロ呼び出しではない
(macroexpand-all '(let (and or) and))
;=> (let or and)

ということになります。あと

(macroexpand-all '(lambda (x) x))

が無限ループしたりもします。

もう少し真面目に

special form を考慮するなら、その部分だけ別関数にするとか。例えば

(defun macroexpand-all (x)
  (let ((y (macroexpand x)))
    (cond ((atom y) y)
          ((special-form-p (car y))
           (macroexpand-in-special-form (car y) (cdr y)))
          (t
           ;; function call
           (cons (car y) (mapcar #'macroexpand-all (cdr y)))))))

とでもしておいて、macroexpand-in-special-form のところは処理系で定義されている special form すべてについて個別に定義することになるでしょう。

もっと真面目にやるなら

本当は flet などの局所関数でグローバルなマクロ定義が隠されていた場合には展開しないようにする必要があります。これは、隠されている名前を &optional ででも受け取って適当に増やしたりするようにすればいいでしょう。

また、もしかしたら展開中のフォームの中に defmacro があったりして、そこで定義されているマクロを考慮して展開する必要もあるかもしれません。マクロ定義を生成するマクロの中には (progn (defmacro ...) ...) のようなコードに展開されるものも存在するので。

このあたりまで対応すると、簡単なインタプリタを作るのと同じくらいの難易度か。

参考

Emacs の macroexpand-all は macroexp.el で定義されています。見てみると面白かったような記憶があります。

*1:内容的にはわりと既出かも