Booでマクロ
Nemerleもマクロが強力でしたが、Booも負けてはいません。さて、次のようなBooのコードがあったとします。
ファイル:tm.boo
import System.Threading
def wait(ms as int):
print "..." * (ms / 1000)
Thread.Sleep(ms)
def Main():
print "1秒待つ"
time:
wait(1000)
print "3秒待つ"
time:
wait(3000)
これをコンパイルして実行すると、
1秒待つ 2004/12/18 0:45:16 ... 2004/12/18 0:45:17 3秒待つ 2004/12/18 0:45:17 ......... 2004/12/18 0:45:20
このように、timeブロックに入った時刻と出た時刻が自動的に表示され・・・ると良いのですが、残念ながらtimeという構文はBooにはありません。しかし、Booではマクロを使ってこれを実現することが出来ます。Booのマクロはどのように動作するかというと、Booコンパイラ(booc)はコンパイル時に知らないキーワードを見つけると、それがマクロである可能性があると判断し、参照しているアセンブリ内に該当マクロ(クラス)が無いか探します。見つけるルールはキーワードの先頭を大文字にして後ろにMacroをつけたクラス名です。timeマクロの場合は、TimeMacroというクラスになります。ただし、IAstMacroインタフェースを実装している必要があります。
では、timeマクロを作成してみましょう。マクロは.NETアセンブリなので必ずしもBooでコーディングしなくてもいいです。ここでは、C#で書いてみます。
ファイル:TimeMacro.cs
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.Ast;
public class TimeMacro : AbstractAstMacro {
static Expression WriteLine = AstUtil.CreateReferenceExpression("System.Console.WriteLine");
static Expression Now = AstUtil.CreateReferenceExpression("System.DateTime.Now");
override public Statement Expand(MacroStatement macro) {
Statement stm = new ExpressionStatement(
AstUtil.CreateMethodInvocationExpression(
WriteLine.CloneNode(), Now.CloneNode()));
macro.Block.Insert(0, stm);
macro.Block.Add(stm);
return macro;
}
}
こんな感じです。肝はmacro.Blockの先頭と最後にConsole.WriteLine(DateTime.Now)を埋め込んでいるところです。で、コイツをコンパイルしてアセンブリを作成します。
> csc /t:library /r:Boo.Lang.Compiler.dll TimeMacro.cs
出来上がったTimeMacro.dllがBooのマクロになります。これを最初のBooのソースをコンパイルするときに一緒にしていして挙げると、無事コンパイルが通るようになります。
> booc -r:TimeMacro.dll tm.boo
ちょっと注意ですが、tm.booは日本語を含むのでソースファイルはutf-8でエンコーディングしておく必要があります。
Booのマクロはもっと色々なことが出来るのですが、調査不足のため、現状はこれで精一杯です。(^^; ちなみに、lockやusingなどもマクロで実現されています。興味がある方はBooのソースコードを覗いてみると面白いと思います。