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のソースコードを覗いてみると面白いと思います。