Personal tools
You are here: Home ブログ matsuyama Emacs Lisp にリーダーマクロを実装してみた
Document Actions

Emacs Lisp にリーダーマクロを実装してみた

今回は、前々から構想を練っていた Emacs Lisp 上でリーダーマクロを実現する話をします。

リーダーマクロって?

Lisp インタプリタが S 式を read するときに自由に展開することができるマクロの一種で、 Lisp の構文を独自に拡張したりすることができます。

実際に Lisp インタプリタにリーダーマクロを登録するには set-macro-character を、第一引数にマクロ展開を実行したい文字、第二引数にマクロ展開を実行する関数を指定して呼び出します。第二引数で指定された関数内で stream から文字を読み込んだりして S 式を構築し、多値として返すことでマクロを展開したという意味になります。例えば $ という文字 [1]nil に展開する場合は以下のように書きます。

[1]φっぽい
(set-macro-character #\$ (lambda (stream char)
                           nil))
(setq foo $) ; => (setq foo nil)

詳しい説明は他所でなされているので本題に移ります。

Emacs Lisp でリーダーマクロ

Emacs Lisp は残念ながらリーダーマクロに対応していません。ただし、ソースコードに少し手を入れることでそれっぽいことはできるようになります。

準備

Emacs 22.1 のソースコードに対して本エントリに添付されているパッチを適用してコンパイルしてください。

% cd ~/tmp
% wget http://ftp.gnu.org/pub/gnu/emacs/emacs-22.1.tar.gz
% wget http://dev.ariel-networks.com/Members/matsuyama/stuff/emacs-22-1-reader-macro-patch/download -O emacs-22.1-reader-macro.patch
% tar xf emacs-22.1.tar.gz
% patch -p0 < emacs-22.1-reader-macro.patch
% cd emacs-22.1
% ./configure --with-x --with-gtk
% make

動作を確認には以下のコマンドを実行するとよいでしょう。

% src/emacs -q&

追加された関数

cl-read-char
cl-read-char &optional stream eof-error-p eof-value recursive-p => char

Common Lisp の read-char と同じですが、 Emacs Lisp 標準の関数と名前が被るので、仕方なく cl- というプリフィックスが付いています [2]

[2]マクロ展開時だけ read-char の関数セルをいれかえるとか考えたけど面倒
unread-char
unread-char char &optional stream => nil

Common Lisp の unread-char と同じです。

peek-char
peek-char &optional stream eof-error-p eof-value recursive-p => char

Common Lisp の peek-char と同じです。

read-delimited-list
read-delimited-list char &optional stream recursive-p => list

Common Lisp の read-delimited-list と同じです。

set-macro-character
set-macro-character char function &optional non-terminating-p readtable => nil

Common Lisp の set-macro-character とほぼ同じですが、 function の意味が若干異なります。 Emacs Lisp は多値を返せないので、かわりに function はリストを返します。先ほどの $nil に展開するリーダーマクロは、 Emacs Lisp では以下のように書きます。

(set-macro-character ?$ (lambda (stream char)
                          (list nil)))

functionnil を返した場合は展開結果が無視されます。

; 独自のコメント構文
(set-macro-character ?! (lambda (stream char)
                          (while (not (eq (cl-read-char stream nil ?\n) ?\n)))
                          nil))
! ここはコメントとして解釈される
(message "Hello")

また、 functiont を返した場合は通常の展開処理が実行されます。

; C++ 風のコメント構文
(set-macro-character ?/ (lambda (stream char)
                          (if (eq (peek-char stream) ?/)
                              (prog1 nil
                                (while (not (eq (cl-read-char stream nil ?\n) ?\n))))
                            t))
                     t)
// ここはコメントとして解釈される
(/ 4 2) // => 2
get-macro-character
get-macro-character char &optional readtable => list

Common Lisp の get-macro-character と同じです。

Perl みたいに連想リストにアクセスする

連想配列にアクセスするには assocassq を使いますが、 Perl っぽく {} でアクセスしたい!

(defvar _keys nil)
(defmacro _assoc (a)
  `(cdr (assoc ,(pop _keys) ,a)))
(set-macro-character ?{ (lambda (stream char)
                          (push (read stream) _keys)
                          nil))
(set-macro-character ?} (lambda (stream char)
                          (list (list '_assoc (read stream)))))

(setq foo '((:foo . (:bar . 100))))
{:foo}{:bar}foo ; => 100

連想リストは書くのが大変なので JSON で書く

(defvar read-in-json nil)

(set-macro-character ?: (lambda (stream char)
                          (not read-in-json))
                     t)

(set-macro-character ?, (lambda (stream char)
                          (not read-in-json)))

(set-macro-character ?{ (lambda (stream char)
                          (let (hash key value
                                (read-in-json t))
                            (while (not (eq key '}))
                              (setq key (read stream))
                              (when (not (eq key '}))
                                (setq value (read stream))
                                (push (cons key value) hash)))
                            (list (nreverse hash)))))

(set-macro-character ?} (lambda (stream char)
                          (list (intern "}"))))

(pp '{
       "foo": { "bar": 100 },
       "hoge": [1, 2, 3, 5]
     })
; =>
; (("foo"
;   ("bar" . 100))
;  ("hoge" .
;   [1 2 3 5]))

最後に

何の役に立つのかと聞かれると答えに窮するわけですが、たぶんなんかいろいろと便利になるんじゃないのかと思いますたぶん。

Have fun!


emacs-22.1-reader-macro.patch emacs-22.1-reader-macro.patch
Size 6.7 kB - File type text/x-patch
Category(s)
emacs
The URL to Trackback this entry is:
http://dev.ariel-networks.com/Members/matsuyama/implement-emacs-lisp-reader-macro/tbping
Add comment

You can add a comment by filling out the form below. Plain text formatting.

(Required)
(Required)
(Required)
(Required)
(Required)
This helps us prevent automated spamming.
Captcha Image


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.