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