スクリプト読んでたら,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