write good parser in perl
DESCRIPTION
TRANSCRIPT
Write Good Parser in Perl
Jiro Nishiguchi(西口次郎)
id:spiritloose
Oct 15, 2010 YAPC::Asia Tokyo
BEGIN {}1. $self introduction2. パーサとは
3. Perlの代表的なパーサ
4. How to write parsers5. まとめ
$self● PAUSE ID: JIRO
http://search.cpan.org/~jiro/● フリーランスのエンジニア
● Image::ObjectDetect● Text::Migemo● http://d.hatena.ne.jp/spiritloose/
Parserとは?● なんらかの意味を持ったテキストを、その後の処理に適した
形にする。
● 例(YAML):foo: bar → { foo => 'bar’ }● 「正しく」「動作」して「あたりまえ」という期待● ソフトウェアの中での重要度は非常に高いはずだが…● 空気のような存在● でもベンチはとられまくり● かわいそうな子
Kind of parser?● HTML / XML● CSV / TSV● JSON● YAML● Program (Perl, Ruby, Template-Toolkit, etc)● Protocol (HTTP, SMTP, Memcached, etc)● 業務データ
Perlでよく使うパーサ● HTML::Parser● XML::LibXML● JSON, JSON::XS● HTTP::Parser::XS● Template-Toolkit● Cache::Memcached
HTML::Parser● Since 1996● Depended on by 408 modules
● ex. HTML::FIllInForm● Written in XS● 手書き(自前で1バイトずつ読み進める)
XML::LibXML● Perl binding for libxml2● XMLの代表的なパーサ
● Depended on by 267 modules● eg. Plagger
JSON(::PP)● Standard JSON module● Wrapper of JSON::PP and JSON::XS● Pure Perlで手書き
JSON::XS● 高速なJSONパーサ
● 手書き
HTTP::Parser::XS● Plackで使われている
● Written in XS● 手書き
Template-Toolkit● Pure Perl● Based on Parse::Yapp
Cache::Memcached● get コマンドのパース
● ::GetParser● ::GetParserXS (別ディストリビューション)
● 手書き
Why XS?● Perlのテキスト処理は遅い(Cに比べて)● 1バイトずつ読み進める処理はCが高速
How to write1.既存のモジュールを使う
2.正規表現
3.Parser Generater をつかう
4.手書き
手書き?● 自由度は最も高い● プログラマの腕によっては最高速になることも● 一方で…
● バッファオーバーラン● 漏れ
● (私のような) 怠惰なプログラマには向かない
既存のモジュールを使う● 重要!● 既知のフォーマットで、ライブラリもそろっているのに自作す
るといいことがあまりない● バグを生みやすい● 可能な限り新しいフォーマットを作らないのが重要
正規表現ベース● 書き捨て● 規則が小さい、シンプル
● 誰にでも(Perlをかける人なら)分かりやすい
● Perlの正規表現は十分に速い
● 規則が大きくなってくるとメンテナンスが大変
Regexp::Assemblemy $ra = Regexp::Assemble->new;
$ra->add('^(incr|decr) ([^ ]+) (\d+)( noreply)?$');
$ra->add('^(delete) ([^ ]+)( noreply)?$');
$ra->add('^(gets?) (.+)$');
$ra->re; # Optimized Regexp
Parser generator とは?● 文法などの定義情報からパーサを生成する● 怠惰なプログラマにうってつけ
● yacc(bison)● Parse::Yapp● Perse::Eyapp (Extended yapp)● Perse::RecDecent● Pegex● Ragel
Ragel● State Machine Compiler● C, C++, Objective-C, D, Java and Ruby(no Perl?)● BNF/Regexp に似た文法
● RubyのMongrel(HTTP Server), Hprecot(HTML Parser)● Graphvizでグラフを出力可能
● ロバストなパーサを作りやすい● ランタイムライブラリ不要
● http://www.complang.org/ragel/
Ragel + XS● ステートマシンの定義を書く
● パースする関数をCセクションに書く
● XSセクションではその関数を呼び出すだけ
Ragel + XS#include “xsutil.h” /* Module::Install::XSUtil */
%%{# ステートマシン定義部
}%%
static SV *parse(pTHX_ SV *text) {/* パーサに必要なデータ宣言 */%% write init;%% write exec;return res;
}
MODULE = MyParser PACKAGE = MyParser
SV *parse(SV *klass, SV *text)CODE:
RETVAL = parse(aTHX_ text);OUTPUT:
RETVAL
XSいやなんですけど…● たいしたことしないので大丈夫です● データ構造を作って返すだけ
● 文字列結合や、配列、ハッシュが触れればOK● Perlでデータを加工したい場合は中間表現を返したり
例:ログ解析(正規表現)our $RE = qr/([^ ]+) ([^ ])+ ([^ ]+) \[([^\]]+)\] "([^"]+)" ([^ ]+) ([^ ]+)/;our @COLS = qw(host logname user time request status bytes);
sub parse { my ($class, $line) = @_; if (my @matches = $line =~ $RE) { my %data; @data{@COLS} = @matches; return \%data; } return;}
例:ログ解析(Ragel)
word = [^ ]+;host = word >begin_host %end_directive;logname = word >begin_logname %end_directive;user = word >begin_user %end_directive;time_fmt = [^\]]+ >begin_time %end_directive;time = '[' time_fmt ']';req_fmt = [^"]+ >begin_request %end_directive;request = '"' req_fmt '"';status = word >begin_status %end_directive;bytes = word >begin_bytes %end_directive;
main := host ' ' logname ' ' user ' ' time ' ' request ' ' status ' ' bytes;
Benchmark
Pure Perl
Ragel
0 50000 100000 150000 200000 250000 300000
Process per seconds
例:Whitespace● スペースとタブと改行だけで構成される言語● シンプルなスタックマシン● 実は教育用によい?
Whitespace(Hello world)
Whitespace(Hello world)[S][S][S][T][S][S][T][S][S][S][LF][T][LF][S][S][S][S][S][T][T][S][S][T][S][T][LF][T][LF][S][S][S][S][S][T][T][S][T][T][S][S][LF][T][LF][S][S][S][S][S][T][T][S][T][T][S][S][LF][T][LF][S][S][S][S][S][T][T][S][T][T][T][T][LF][T][LF][S][S][S][S][S][T][S][T][T][S][S][LF][T][LF][S][S][S][S][S][T][S][S][S][S][S][LF][T][LF][S][S][S][S][S][T][T][T][S][T][T][T][LF][T][LF]
[S][S][S][S][S][T][T][S][T][T][T][T][LF][T][LF][S][S][S][S][S][T][T][T][S][S][T][S][LF][T][LF][S][S][S][S][S][T][T][S][T][T][S][S][LF][T][LF][S][S][S][S][S][T][T][S][S][T][S][S][LF][T][LF][S][S][S][S][S][T][S][S][S][S][T][LF][T][LF][S][S][S][S][S][T][S][T][S][LF][T][LF][S][S][LF][LF][LF]
Whitespace(disasm)PUSH 72PUTCPUSH 101PUTCPUSH 108PUTCPUSH 108PUTCPUSH 111PUTCPUSH 44PUTCPUSH 32PUTCPUSH 119PUTC
PUSH 111PUTCPUSH 114PUTCPUSH 108PUTCPUSH 100PUTCPUSH 33PUTCPUSH 10PUTCEXIT
Whitespaceadd = tb sp sp sp >{ op = ADD; } %end_op;sub = tb sp sp tb >{ op = SUB; } %end_op;mul = tb sp sp lf >{ op = MUL; } %end_op;div = tb sp tb sp >{ op = DIV; } %end_op;mod = tb sp tb tb >{ op = MOD; } %end_op;
Graphviz%%{ machine test_parser; main := 'a' ('b' | 'c') 'd'+;}%%
$ ragel -Vp test.rl | dot -Tpng > test.png
Perl6● パーサのための専用構文が用意された
● grammer● 前述のRagelの例のようなことが built-inでできる
● Perl6のパーサも Perl6 の grammer で書かれている
● http://github.com/perl6/std/blob/master/STD.pm6
まとめ● よく使われるPerlのパーサライブラリを紹介した
● パーサを書く場合の手法をいくつか紹介した
● 速度が求められる場合はCのパーサジェネレータ+XSを検討してもよい
● 保守性と速度のトレードオフ
【未承諾広告】求人● コミュニティサイト 2001年~
● 5億pv / month● 100 servers● mod_perl + Sledge + MySQL● Subversion + Redmine + Capistrano● TheSchwartz, Memcached, Solr● perlbrew, cpanm● 私 <[email protected]> まで
END { thank_you(); }● References
● http://www.slideshare.net/spiritloose● http://github.com/spiritloose/● http://d.hatena.ne.jp/spiritloose/