スクリプト読んでたら,PPI::Document ってモジュールが使われていてチョー気になったもんだから調べてみました.
Nothing but perl can parse Perl
そもそも perl なしに Perl を解析できないかという試みは次々に試され,そして失敗していったそうです.あまりに Perl の文法が複雑で動的なもんだからってのがその大きな原因みたいですね.
begin{} ブロックなんてのは,解析とともに普通実行も伴うわけで,"実行なし" に Perl のソースコードを解析するのは至難の技だったりします.また,以下のような問題も発生してしまいます.
@result = (dothis $foo, $bar); # Which of the following is it equivalent to? @result = (dothis($foo), $bar); @result = dothis($foo, $bar);
PPI
そこで出てきたのがソースを Perl としてではなく,Document として解析しようという話で,それを実行するのが PPI 関連モジュールの模様.PPI は元々モジュールとしていて存在した Perl::Parse::Isolated の略とか,I Parse Perl のバクロニムとか,Perl Parsing Interface とか.まぁどうでもいい.まぁともかく,CPAN 上にある 99% の Perl コードがうまいこと解析できたってことなので,PPI はエラいのです!!
個人的にはここのエントリがとても参考になりました.
構造
- PPI::Tokenizer がソースコードを Token 列に変換する
- PPI::Lexer が PPI::Tokenizer から PPI::Document に変換する
ただし,普通は PPI::Document を直接生成するとのこと.
Perl の構造を表す Perl Document Object Model の体系は本エントリの末尾に示しました.PPI::Document はこの中にある一クラスであり,一ファイルを抽象化したものになっているようです.
使ってみる
解析対象のファイルとして,ppi-document-test.pl を用意しました.いわゆる hello world プログラムです.
#!/opt/local/bin/perl print "hello world\n";
さて,この ppi-document-test.pl がどういう構造なのかを PPI を使って調べてみましょう! 以下のプログラムを実行します.
#!/opt/local/bin/perl use strict; use warnings; use PPI::Document; use PPI::Dumper; my $path = './ppi-document-test.pl'; my $doc = PPI::Document->new( $path ) or die "cannot create PPI::Document for $path: $!"; my $dumper = PPI::Dumper->new( $doc, ); $dumper->print;
あれあれ,こんな出力が出てきましたね.クラス名を見ると,見事に解析できてるみたいです.スゴい!!
$ ./ppi-document.pl PPI::Document PPI::Token::Comment '#!/opt/local/bin/perl\n' PPI::Token::Whitespace '\n' PPI::Statement PPI::Token::Word 'print' PPI::Token::Whitespace ' ' PPI::Token::Quote::Double '"hello world\n"' PPI::Token::Structure ';' PPI::Token::Whitespace '\n' PPI::Token::Whitespace '\n'
二重引用符で囲まれたドキュメントのみ出力するには,こんな風にすれば良さそうです.
#!/opt/local/bin/perl use strict; use warnings; use PPI::Document; use PPI::Dumper; my $path = './ppi-document-test.pl'; my $doc = PPI::Document->new( $path ) or die "cannot create PPI::Document for $path: $!"; my $dblquote = $doc->find( 'PPI::Token::Quote::Double' ); for my $q ( @$dblquote ) { print $q->content, "\n"; }
PPI::Document#find にクラス名を渡してやると,PPI::Document オブジェクト配下に存在するそのクラス参照の配列を返してくれるので,content メソッドでその内容を出力しているって感じです.
体系
PPI::Element PPI::Node PPI::Document PPI::Document::Fragment PPI::Statement PPI::Statement::Package PPI::Statement::Include PPI::Statement::Sub PPI::Statement::Scheduled PPI::Statement::Compound PPI::Statement::Break PPI::Statement::Data PPI::Statement::End PPI::Statement::Expression PPI::Statement::Variable PPI::Statement::Null PPI::Statement::UnmatchedBrace PPI::Statement::Unknown PPI::Structure PPI::Structure::Block PPI::Structure::Subscript PPI::Structure::Constructor PPI::Structure::Condition PPI::Structure::List PPI::Structure::ForLoop PPI::Structure::Unknown PPI::Token PPI::Token::Whitespace PPI::Token::Comment PPI::Token::Pod PPI::Token::Number PPI::Token::Number::Binary PPI::Token::Number::Octal PPI::Token::Number::Hex PPI::Token::Number::Float PPI::Token::Number::Exp PPI::Token::Number::Version PPI::Token::Word PPI::Token::DashedWord PPI::Token::Symbol PPI::Token::Magic PPI::Token::ArrayIndex PPI::Token::Operator PPI::Token::Quote PPI::Token::Quote::Single PPI::Token::Quote::Double PPI::Token::Quote::Literal PPI::Token::Quote::Interpolate PPI::Token::QuoteLike PPI::Token::QuoteLike::Backtick PPI::Token::QuoteLike::Command PPI::Token::QuoteLike::Regexp PPI::Token::QuoteLike::Words PPI::Token::QuoteLike::Readline PPI::Token::Regexp PPI::Token::Regexp::Match PPI::Token::Regexp::Substitute PPI::Token::Regexp::Transliterate PPI::Token::HereDoc PPI::Token::Cast PPI::Token::Structure PPI::Token::Label PPI::Token::Separator PPI::Token::Data PPI::Token::End PPI::Token::Prototype PPI::Token::Attribute PPI::Token::Unknown