Perl 言語自身すら拡張する Filter 機能をお勉強

PERL HACKS を読んだときに何となく流し読みをしてしまった Source Filters ですが改めて勉強中です。perl 上級者の方々は既にご存じで、ここぞという使い方をされていることと思います。

Source Filters とは何か? perlfilter - Source Filters - search.cpan.org まずここから勉強です。ものすごく簡単に説明すると、perl の処理系が構文解析を実行する1つ手前でソースそのものを変更しちゃう機能ってことです。

Perl program(元ファイル) ---> source filter(ソースフィルタ) ---> parser(構文解析)

で、そんなことをして何が嬉しいかというと、perl に独自の言語仕様(構文)を加えることができます。ぱっと思いつくものでいえば、Filter::SQL や Switch あたりで使われていたりします。

- スポンサーリンク -

そもそもなんで今毎 source filter 機能なんて調べているかというと、つい最近、このブログのカテゴリ部分に jQuery の jquery.pager.js を使ってページ分割をしてみました。最近エントリ数が多くなってきて見づらいなぁ〜と思っていたところなんです。

img02.jpg

脱線しないように詳しい話はまた別エントリにするとして、MovableType 3.3 の ContextHandlers.pm を一部書き換えないとコレが実現できなかったんですけど、どうせなんでプラグインで配布しようかなぁ〜と考えてしまったわけです。・・・がですね、MT4 系等をサポートしようとすると・・・結構ソースが違っていろいろ面倒でして、それこそソースフィルタ使ってピンポイントでソースを書き換えちゃうか??

ってここでソースフィルタを思い出したので勉強しなおしているわけですが、元々の目的のプラグイン公開はハゲしく面倒になってきたのでやめました。そのかわりに、せっかくソースフィルタ機能について調べたのでメモ書きしている次第です。Filter::Util::Call ってモジュールがソースフィルタ機能の元締め的なモジュールなんですけど、より簡単に扱える Filter::Simple ってヤツだけまとめておこうと思います。

ちなみに Filter::Simple の説明は、PERL HACKS の #94,#95 にも若干説明があります。

 Perl コンパイラは、ソースフィルタの処理結果しか見ないので、正しい Perl コードでなければならないのは、その最終的な処理結果だけだ。フィルタが横取りする元々のソースコードは、フィルだが正しい Perl コードに変換できるようなモノであれば、何でも構わない。
 たとえば・・・

 ソースコードフィルタは、新しい構文を追加するだけでなく、既存の構文の動作を変えることもできる。たとえば、ヒアドキュメトに・・・

最初の perl の処理系の流れの説明に戻るけど、

Perl program(元ファイル) ---> source filter(ソースフィルタ) ---> parser(構文解析)

において、元ファイルは必ずしも Perl の文法的に正しいモノが書いてなくてもOKというわけですね。最終的にソースフィルタがヨロシク正しい文法のプログラムに変換してくれればよいと。

Filter::Simple の構文

いろいろ書き方のオプションがあるわけですが、自分的に覚えておくのは、フィルタの範囲指定が可能な(意図しない部分に影響を及ぼさないようにするため)書式にしておこうと思います。
use Filter::Simple;
FILTER_ONLY
    code                   => sub {  置換処理  },
    code_no_comments       => sub {  置換処理  },
    executable             => sub {  置換処理  },
    executable_no_comments => sub {  置換処理  },
    quotelike              => sub {  置換処理  },
    string                 => sub {  置換処理  },
    regex                  => sub {  置換処理  },
    all                    => sub {  置換処理  },
;

それぞれのキーの意味は以下の通り。

quotelike Text::Balanced::extract_quotelike にて判定された文字列のみ対象( "...", '...', q#...# 等)
code quotelikes, POD, __DATA__ 以外が対象
code_no_comments quotelikes コメント文, POD, __DATA__ 以外が対象
executable POD, __DATA__ 以外が対象
executable_no_comments POD, コメント文, __DATA__ 以外が対象
string quotelike 中の文字リテラルのみ対象
regex quotelike 中の正規表現リテラルのみ対象
all 全てが対象

ってわけで、Filter::Simple に付属しているチュートリアル(demoフォルダ配下)を順を追って確認していきました。その中から1つをを抜粋。ちなみに、今更ながらですが、5.8系以上でないと動作しませんし、ActivePerl でも正常に動作しないものもありました(少なくともうちの環境では・・・)。

demo.pl / Demo1.pm

use Demo1 qr/bill/i => "William", is => 'was' ;

sub bill { print "My name is Bill\n"; "explicitly named" }

bill();
&bill;

print "Thanks, Bill, your bill is @{[bill()]}\n";

----

package Demo1;
$VERSION = '0.01';

use Filter::Simple sub {
    my $class = shift;
    while (my ($from, $to) = splice @_, 0, 2) {
        s/$from/$to/g;
    }
};

1;

実行結果

My name was William
My name was William
My name was William
Thanks, William, your William was explicitly named


一応、当初の目的であったソースの動的な書き換えを試してみたら動作はしました。

use Filter::Test2;
sub MT::Template::Context::_hdlr_entries{
...snip...
    my $i = 0;
    local $ctx->{__stash}{entries} = \@entries;
    my $glue = $args->{glue};
    my $rowcount = 0;
    for my $e (@entries) {
        local $ctx->{__stash}{entry} = $e;
        local $ctx->{current_timestamp} = $e->created_on;
        local $ctx->{modification_timestamp} = $e->modified_on;
        my $this_day = substr $e->created_on, 0, 8;
        my $next_day = $this_day;
        my $footer = 0;
...snip...
}

----

package Filter::Test2;
use Filter::Simple;

FILTER_ONLY
  code  => sub {
    my $src = '^\s+local\s\$ctx->{__stash}{entry}\s=\s\$e;';
    my $add = 'local $ctx->{entry_rowcount} = $rowcount++;' . "\n";
    s/($src)/$add$1/msxg;
    print $_;  # テスト的に変更後の文字列を出力してみる
  };
1;

実行結果。いちおうちゃんとソースに命令が加わっているのが確認できた。

...snip...
    my $i = 0;
    local $ctx->{__stash}{entries} = \@entries;
    my $glue = $args->{glue};
    my $rowcount = 0;
    for my $e (@entries) {
local $ctx->{entry_rowcount} = $rowcount++;
        local $ctx->{__stash}{entry} = $e;
        local $ctx->{current_timestamp} = $e->created_on;
        local $ctx->{modification_timestamp} = $e->modified_on;
        my $this_day = substr $e->created_on, 0, 8;
        my $next_day = $this_day;
        my $footer = 0;
...snip...

う〜〜〜ん・・・・・・・・・

とりあえず、Filter::Simple の使い方は一通り覚えましたが、正直なところ・・・使い方は若干微妙なところと感じております。ソースフィルタ以外の方法のスマートな解決方法があるときにはそちらを使いたいところです。

Perl Hacks ―プロが教えるテクニック & ツール101選
chromatic Damian Conway Curtis "Ovid" Poe
オライリー・ジャパン
売り上げランキング: 192013
おすすめ度の平均: 5.0
5 Perl Hacker になるための一冊
5 アイディア本


つぎは、Inline - Write Perl subroutines in other programming languages. - search.cpan.org あたりをお勉強しようかしら。ちょっとみたところ、こっちは解析するにはレベル高そうなんだけど・・・

- スポンサーリンク -