理系学生日記

おまえはいつまで学生気分なのか

PPI って何なんだろう

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