Perl 言語自身すら拡張する Filter 機能をお勉強
PERL HACKS を読んだときに何となく流し読みをしてしまった Source Filters ですが改めて勉強中です。perl 上級者の方々は既にご存じで、ここぞという使い方をされていることと思います。
Source Filters とは何か? perlfilter - Source Filters - search.cpan.org まずここから勉強です。ものすごく簡単に説明すると、perl の処理系が構文解析を実行する1つ手前でソースそのものを変更しちゃう機能ってことです。
で、そんなことをして何が嬉しいかというと、perl に独自の言語仕様(構文)を加えることができます。ぱっと思いつくものでいえば、Filter::SQL や Switch あたりで使われていたりします。
そもそもなんで今毎 source filter 機能なんて調べているかというと、つい最近、このブログのカテゴリ部分に jQuery の jquery.pager.js を使ってページ分割をしてみました。最近エントリ数が多くなってきて見づらいなぁ〜と思っていたところなんです。
脱線しないように詳しい話はまた別エントリにするとして、MovableType 3.3 の ContextHandlers.pm を一部書き換えないとコレが実現できなかったんですけど、どうせなんでプラグインで配布しようかなぁ〜と考えてしまったわけです。・・・がですね、MT4 系等をサポートしようとすると・・・結構ソースが違っていろいろ面倒でして、それこそソースフィルタ使ってピンポイントでソースを書き換えちゃうか??
ってここでソースフィルタを思い出したので勉強しなおしているわけですが、元々の目的のプラグイン公開はハゲしく面倒になってきたのでやめました。そのかわりに、せっかくソースフィルタ機能について調べたのでメモ書きしている次第です。Filter::Util::Call ってモジュールがソースフィルタ機能の元締め的なモジュールなんですけど、より簡単に扱える Filter::Simple ってヤツだけまとめておこうと思います。
ちなみに Filter::Simple の説明は、PERL HACKS の #94,#95 にも若干説明があります。
たとえば・・・
最初の perl の処理系の流れの説明に戻るけど、
において、元ファイルは必ずしも 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 の使い方は一通り覚えましたが、正直なところ・・・使い方は若干微妙なところと感じております。ソースフィルタ以外の方法のスマートな解決方法があるときにはそちらを使いたいところです。
オライリー・ジャパン
売り上げランキング: 192013
アイディア本
つぎは、Inline - Write Perl subroutines in other programming languages. - search.cpan.org あたりをお勉強しようかしら。ちょっとみたところ、こっちは解析するにはレベル高そうなんだけど・・・
コメントやシェアをお願いします!