write good parser in perl

37
Write Good Parser in Perl Jiro Nishiguchi(西口次郎) id:spiritloose [email protected] Oct 15, 2010 YAPC::Asia Tokyo

Upload: jiro-nishiguchi

Post on 07-Dec-2014

3.926 views

Category:

Documents


0 download

DESCRIPTION

 

TRANSCRIPT

Page 1: Write good parser in perl

Write Good Parser in Perl

Jiro Nishiguchi(西口次郎)

id:spiritloose

[email protected]

Oct 15, 2010 YAPC::Asia Tokyo

Page 2: Write good parser in perl

BEGIN {}1. $self introduction2. パーサとは

3. Perlの代表的なパーサ

4. How to write parsers5. まとめ

Page 3: Write good parser in perl

$self● PAUSE ID: JIRO

http://search.cpan.org/~jiro/● フリーランスのエンジニア

● Image::ObjectDetect● Text::Migemo● http://d.hatena.ne.jp/spiritloose/

Page 4: Write good parser in perl

Parserとは?● なんらかの意味を持ったテキストを、その後の処理に適した

形にする。

● 例(YAML):foo: bar → { foo => 'bar’ }● 「正しく」「動作」して「あたりまえ」という期待● ソフトウェアの中での重要度は非常に高いはずだが…● 空気のような存在● でもベンチはとられまくり● かわいそうな子

Page 5: Write good parser in perl

Kind of parser?● HTML / XML● CSV / TSV● JSON● YAML● Program (Perl, Ruby, Template-Toolkit, etc)● Protocol (HTTP, SMTP, Memcached, etc)● 業務データ

Page 6: Write good parser in perl

Perlでよく使うパーサ● HTML::Parser● XML::LibXML● JSON, JSON::XS● HTTP::Parser::XS● Template-Toolkit● Cache::Memcached

Page 7: Write good parser in perl

HTML::Parser● Since 1996● Depended on by 408 modules

● ex. HTML::FIllInForm● Written in XS● 手書き(自前で1バイトずつ読み進める)

Page 8: Write good parser in perl

XML::LibXML● Perl binding for libxml2● XMLの代表的なパーサ

● Depended on by 267 modules● eg. Plagger

Page 9: Write good parser in perl

JSON(::PP)● Standard JSON module● Wrapper of JSON::PP and JSON::XS● Pure Perlで手書き

Page 10: Write good parser in perl

JSON::XS● 高速なJSONパーサ

● 手書き

Page 11: Write good parser in perl

HTTP::Parser::XS● Plackで使われている

● Written in XS● 手書き

Page 12: Write good parser in perl

Template-Toolkit● Pure Perl● Based on Parse::Yapp

Page 13: Write good parser in perl

Cache::Memcached● get コマンドのパース

● ::GetParser● ::GetParserXS (別ディストリビューション)

● 手書き

Page 14: Write good parser in perl

Why XS?● Perlのテキスト処理は遅い(Cに比べて)● 1バイトずつ読み進める処理はCが高速

Page 15: Write good parser in perl

How to write1.既存のモジュールを使う

2.正規表現

3.Parser Generater をつかう

4.手書き

Page 16: Write good parser in perl

手書き?● 自由度は最も高い● プログラマの腕によっては最高速になることも● 一方で…

● バッファオーバーラン● 漏れ

● (私のような) 怠惰なプログラマには向かない

Page 17: Write good parser in perl

既存のモジュールを使う● 重要!● 既知のフォーマットで、ライブラリもそろっているのに自作す

るといいことがあまりない● バグを生みやすい● 可能な限り新しいフォーマットを作らないのが重要

Page 18: Write good parser in perl

正規表現ベース● 書き捨て● 規則が小さい、シンプル

● 誰にでも(Perlをかける人なら)分かりやすい

● Perlの正規表現は十分に速い

● 規則が大きくなってくるとメンテナンスが大変

Page 19: Write good parser in perl

Regexp::Assemblemy $ra = Regexp::Assemble->new;

$ra->add('^(incr|decr) ([^ ]+) (\d+)( noreply)?$');

$ra->add('^(delete) ([^ ]+)( noreply)?$');

$ra->add('^(gets?) (.+)$');

$ra->re; # Optimized Regexp

Page 20: Write good parser in perl

Parser generator とは?● 文法などの定義情報からパーサを生成する● 怠惰なプログラマにうってつけ

● yacc(bison)● Parse::Yapp● Perse::Eyapp (Extended yapp)● Perse::RecDecent● Pegex● Ragel

Page 21: Write good parser in perl

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/

Page 22: Write good parser in perl

Ragel + XS● ステートマシンの定義を書く

● パースする関数をCセクションに書く

● XSセクションではその関数を呼び出すだけ

Page 23: Write good parser in perl

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

Page 24: Write good parser in perl

XSいやなんですけど…● たいしたことしないので大丈夫です● データ構造を作って返すだけ

● 文字列結合や、配列、ハッシュが触れればOK● Perlでデータを加工したい場合は中間表現を返したり

Page 25: Write good parser in 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;}

Page 26: Write good parser in perl

例:ログ解析(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;

Page 27: Write good parser in perl

Benchmark

Pure Perl

Ragel

0 50000 100000 150000 200000 250000 300000

Process per seconds

Page 28: Write good parser in perl

例:Whitespace● スペースとタブと改行だけで構成される言語● シンプルなスタックマシン● 実は教育用によい?

Page 29: Write good parser in perl

Whitespace(Hello world)

Page 30: Write good parser in perl

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]

Page 31: Write good parser in perl

Whitespace(disasm)PUSH 72PUTCPUSH 101PUTCPUSH 108PUTCPUSH 108PUTCPUSH 111PUTCPUSH 44PUTCPUSH 32PUTCPUSH 119PUTC

PUSH 111PUTCPUSH 114PUTCPUSH 108PUTCPUSH 100PUTCPUSH 33PUTCPUSH 10PUTCEXIT

Page 32: Write good parser in perl

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;

Page 33: Write good parser in perl

Graphviz%%{ machine test_parser; main := 'a' ('b' | 'c') 'd'+;}%%

$ ragel -Vp test.rl | dot -Tpng > test.png

Page 34: Write good parser in perl

Perl6● パーサのための専用構文が用意された

● grammer● 前述のRagelの例のようなことが built-inでできる

● Perl6のパーサも Perl6 の grammer で書かれている

● http://github.com/perl6/std/blob/master/STD.pm6

Page 35: Write good parser in perl

まとめ● よく使われるPerlのパーサライブラリを紹介した

● パーサを書く場合の手法をいくつか紹介した

● 速度が求められる場合はCのパーサジェネレータ+XSを検討してもよい

● 保守性と速度のトレードオフ

Page 36: Write good parser in perl

【未承諾広告】求人● コミュニティサイト 2001年~

● 5億pv / month● 100 servers● mod_perl + Sledge + MySQL● Subversion + Redmine + Capistrano● TheSchwartz, Memcached, Solr● perlbrew, cpanm● 私 <[email protected]> まで

Page 37: Write good parser in perl

END { thank_you(); }● References

● http://www.slideshare.net/spiritloose● http://github.com/spiritloose/● http://d.hatena.ne.jp/spiritloose/