PPIの簡単な使い方
PPIはなんだかとっつきにくかったが,使ってみると意外といける。パースするコードは読まなくても,PDOM(Perl Document Object Model)が分かればとりあえず使うことができる。
ppidump.pl
さて,まず実際にPPI使う前に,ダンプユーティリティを用意しておくと作業が楽になる*1。
#!perl -w # Uasge: ppidump.pl "say(q{Hello, world!})" use strict; use PPI::Document; use PPI::Dumper; my $document = PPI::Document->new(\"@ARGV"); PPI::Dumper->new($document, whitespace => 0, comments => 0)->print();
PPIを使ってみる
基本的には以下のような流れ。
- $document = PPI::Document->new(\$src) か PPI::Document->new($file)でPPI::Documentオブジェクトを生成
- $elems_ref = $document->find(\&want)で特定のオブジェクトを探す
- $documentの要素$elemからは,$elem->parent(), $elem->schild(0), $elem->snext_sibling(), $elem->sprevious_sibling()なので周囲のエレメントにアクセス可能。なお,メソッドのプレフィクス"s"は"significant"の略で,コメントやpodなどの重要ではない要素を除いた「次」やら「前」やらの要素を返す。
- $elem->__inseret_before($elem2)*2や$elem->__insert_after($elem2)で要素の追加
- $elem->remove()で要素$elemの削除
- $document->prune(\&want)で特定のオブジェクトを削除
- $elems_ref = $document->find(\&want)で特定のオブジェクトを探す
- 必要なら$document->serialize()で変更したソースコードを復元
大体において,まずfind()で特定の要素を探して,その特定の要素に何かをするというのがパターンだと思う。
find()に渡す関数は,たとえば以下のようになる。これは'use macro ...;'の行の,更にそのuse文に引数が与えられているケースを探すための関数である。
#macro/0.2より package macro; sub _want_use_macro{ my(undef, $it) = @_; # 第一引数は$document my $elem; # use ...;文のクラスはStatement::Include return $it->isa('PPI::Statement::Include') # 子要素のうち,空白でない最初の要素 && ($elem = $it->schild(0)) # $elemはおそらくToken::Word # その中身が'use'か否か && ($elem->content eq 'use') # 'use'の次の空白でない要素 # これはToken::Word(モジュール名) # またはToken::Number (バージョン番号) && ($elem = $elem->snext_sibling) # モジュール名が目的のものか否か && ($elem->content eq __PACKAGE__ or $elem->content eq 'macro') # 更にその次の要素を見て,引数の有無を確認 && _has_args($it, $elem->snext_sibling); }
このように,単純なことならそれほど難しくない。少なくとも,自分でパーサーを書いたり正規表現でがんばるよりは楽だと思われる。