Perlで10行テンプレートエンジン

コードジェネレータ書いてるときに生成するコード量が多いと,スクリプト中にコピペできる程度にミニミニなテンプレートエンジンが欲しいと思うことがあるので書いた.エスケープとかは必要ないので,ベリーシンプルな置換とevalで書ける.

my $Token = join '', map { chr(65 + rand(26)) } 1..64;
sub compile_template {
    my ($tmpl) = @_;

    $tmpl =~ s{<%(=)?(.*?)%>\n?|((?:(?!<%).)+)}{
        $2 ? $1 ? "\$$Token .= qq{\@{[do{ $2 }]}};" : $2
           : "chop( \$$Token .= <<$Token );\n".quotemeta($3)."\n$Token\n";
    }gse;
    eval "sub { my \$$Token = ''; $tmpl \$$Token }"
        or die "ERROR: Cannot compile template: $@";
}

my $renderer = compile_template <<'EOS';
static const key_value_t <%= $_[0]->{name} %>[] = {
<% for my $d (@{ $_[0]->{elems} }) { %>
    { <%= $d->{key} %>, <%= $d->{value} %> },
<% } %>
};
EOS

print $renderer->({
    name  => 'hogehoge',
    elems => [
        { key => 'hoge', value => 'fuga' },
        { key => 'foo',  value => 'bar'  },
    ],
});

こんな感じで.perlのコードがそのまま書けて,forとかifとか使えるのが良い.シンタックスはText::MicroTemplate風で.

実装としては,<% %>で囲まれたperlのコードをそのまま残し,<%= %>で囲まれた式の評価結果と,それ以外の文字列を変数にどんどんappendしていくように置換していく感じ.置換結果をsub{ }でくくってevalしてcoderefにして返すので,evalのコストはコンパイルの時だけ.

変数名が被ったり,heredocが予想外に終端されることを防ぐために64文字のランダムな文字列を生成して使用している.

GSoCで書いたコードにこんな感じで使ったりしている.