define-macroをR6RSで
skySchemeは自前のexpanderで伝統的なマクロを実装していたが、冷静に考えるとそのためだけにexpanderを持つのは無駄なので可能な限りR6RS側のmacroを使うことにする。(moshのcompilerではdefine-macroを使っているが、moshの中で実装を見つけられなかった。)
R6RSのmacroはsyntax-caseと呼ばれていて、伝統的なマクロとはだいぶ趣が異なる。syntax-caseのリファレンス実装はSRFI-93で提供される。
syntax-caseで書かれたdefine-macroを探してくると :
- https://webmail.iro.umontreal.ca/pipermail/gambit-list/2007-March/001195.html - Marc Feeley(Gambitの作者)による実装
- 以下のコードはこれを元にしている。R6RSよりも前のsyntax-caseでは、*-objectのようにうしろにobjectが付いているようだ。
- http://d.hatena.ne.jp/fujita-y/20090106/1231239469 - Ypsilonの実装
等がある。
Gaucheのdefine-macroの説明を見ると、次のようになっている
Special Form: define-macro name procedure ;←A
Special Form: define-macro (name . formals) body … ;←B
この2行をR6RSのsyntax-caseで表現すると :
(define-syntax define-macro (lambda (x) (syntax-case x () ((_ (name . formals) body1 body2 ...) ?????) ;←B ((_ name procedure) ?????)))) ;←A
????の所に、実際にどういう変換をするのかを書く。
追記 : 順序が逆だった。
Bは、Aの省略形なので、式をAに変形するものを書く。つまり、いま定義しているdefine-macroを再帰的にAの形で呼ぶようにする。
((_ (name . formals) body1 body2 ...) (syntax (define-macro name (lambda formals body1 body2 ...))))
syntaxはquoteの一種で、R6RSでは(syntax hoge)は#'hogeと書ける。ypsilonではこっちの表現を使っている。
ここに書かれるdefine-macroはちゃんとAの形式になる。あとはAの形式を書けばいい。コードをデータに変換するために、さらにsyntax-caseを使って、
((_ name procedure) (syntax (define-syntax name (lambda (p) (syntax-case p () ((k . args) (let ((lst (syntax->datum (syntax args)))) (datum->syntax (syntax k) (apply procedure lst)))))))))
リストを引数と見なして関数を呼ぶにはapplyを使う。
全体をmoshで試すと、
oku@ps3-gentoo ~/mosh.ps3 $ cat check.scm (import (rnrs)) (define-syntax define-macro (lambda (x) (syntax-case x () ((_ (name . formals) body1 body2 ...) (syntax (define-macro name (lambda formals body1 body2 ...)))) ((_ name procedure) (syntax (define-syntax name (lambda (p) (syntax-case p () ((k . args) (let ((lst (syntax->datum (syntax args)))) (datum->syntax (syntax k) (apply procedure lst)))))))))))) (define-macro foo (lambda () "bar") ) (display (foo))(newline) oku@ps3-gentoo ~/mosh.ps3 $ ./mosh check.scm bar