Contenu connexe
Similaire à Write good parser in perl
Similaire à Write good parser in perl (20)
Write good parser in perl
- 1. Write Good Parser in Perl
Jiro Nishiguchi(西口次郎)
id:spiritloose
jiro@cpan.org
Oct 15, 2010 YAPC::Asia Tokyo
- 2. BEGIN {}
1. $self introduction
2. パーサとは
3. Perlの代表的なパーサ
4. How to write parsers
5. まとめ
- 3. $self
● PAUSE ID: JIRO
http://search.cpan.org/~jiro/
● フリーランスのエンジニア
● Image::ObjectDetect
● Text::Migemo
● http://d.hatena.ne.jp/spiritloose/
- 4. Parserとは?
● なんらかの意味を持ったテキストを、その後の処理に適した
形にする。
● 例(YAML):foo: bar → { foo => 'bar’ }
● 「正しく」「動作」して「あたりまえ」という期待
● ソフトウェアの中での重要度は非常に高いはずだが…
● 空気のような存在
● でもベンチはとられまくり
● かわいそうな子
- 5. Kind of parser?
● HTML / XML
● CSV / TSV
● JSON
● YAML
● Program (Perl, Ruby, Template-Toolkit, etc)
● Protocol (HTTP, SMTP, Memcached, etc)
● 業務データ
- 6. Perlでよく使うパーサ
● HTML::Parser
● XML::LibXML
● JSON, JSON::XS
● HTTP::Parser::XS
● Template-Toolkit
● Cache::Memcached
- 7. HTML::Parser
● Since 1996
● Depended on by 408 modules
● ex. HTML::FIllInForm
● Written in XS
● 手書き(自前で1バイトずつ読み進める)
- 8. XML::LibXML
● Perl binding for libxml2
● XMLの代表的なパーサ
● Depended on by 267 modules
● eg. Plagger
- 9. JSON(::PP)
● Standard JSON module
● Wrapper of JSON::PP and JSON::XS
● Pure Perlで手書き
- 14. Why XS?
● Perlのテキスト処理は遅い(Cに比べて)
● 1バイトずつ読み進める処理はCが高速
- 16. 手書き?
● 自由度は最も高い
● プログラマの腕によっては最高速になることも
● 一方で…
●
バッファオーバーラン
●
漏れ
● (私のような) 怠惰なプログラマには向かない
- 17. 既存のモジュールを使う
● 重要!
● 既知のフォーマットで、ライブラリもそろっているのに自作す
るといいことがあまりない
● バグを生みやすい
● 可能な限り新しいフォーマットを作らないのが重要
- 18. 正規表現ベース
● 書き捨て
● 規則が小さい、シンプル
● 誰にでも(Perlをかける人なら)分かりやすい
● Perlの正規表現は十分に速い
● 規則が大きくなってくるとメンテナンスが大変
- 19. Regexp::Assemble
my $ra = Regexp::Assemble->new;
$ra->add('^(incr|decr) ([^ ]+) (d+)( noreply)?$');
$ra->add('^(delete) ([^ ]+)( noreply)?$');
$ra->add('^(gets?) (.+)$');
$ra->re; # Optimized Regexp
- 20. Parser generator とは?
● 文法などの定義情報からパーサを生成する
● 怠惰なプログラマにうってつけ
● yacc(bison)
● Parse::Yapp
● Perse::Eyapp (Extended yapp)
● Perse::RecDecent
● Pegex
● Ragel
- 21. 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/
- 22. Ragel + XS
● ステートマシンの定義を書く
● パースする関数をCセクションに書く
● XSセクションではその関数を呼び出すだけ
- 23. 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
- 24. XSいやなんですけど…
● たいしたことしないので大丈夫です
● データ構造を作って返すだけ
● 文字列結合や、配列、ハッシュが触れればOK
● Perlでデータを加工したい場合は中間表現を返したり
- 25. 例:ログ解析(正規表現)
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;
}
- 26. 例:ログ解析(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;
- 27. Benchmark
Ragel
Pure Perl
0 50000 100000 150000 200000 250000 300000
Process per seconds
- 30. Whitespace(Hello world)
[S][S][S][T][S][S][T][S][S][S][LF] [S][S][S][S][S][T][T][S][T][T][T][T][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][T][S][S][T][S][T][LF] [S][S][S][S][S][T][T][T][S][S][T][S][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][T][S][T][T][S][S][LF] [S][S][S][S][S][T][T][S][T][T][S][S][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][T][S][T][T][S][S][LF] [S][S][S][S][S][T][T][S][S][T][S][S][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][T][S][T][T][T][T][LF] [S][S][S][S][S][T][S][S][S][S][T][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][S][T][T][S][S][LF] [S][S][S][S][S][T][S][T][S][LF]
[T][LF] [T][LF]
[S][S][S][S][S][T][S][S][S][S][S][LF] [S][S][LF]
[T][LF] [LF]
[S][S][S][S][S][T][T][T][S][T][T][T][LF] [LF]
[T][LF]
- 31. Whitespace(disasm)
PUSH 72 PUSH 111
PUTC PUTC
PUSH 101 PUSH 114
PUTC PUTC
PUSH 108 PUSH 108
PUTC PUTC
PUSH 108 PUSH 100
PUTC PUTC
PUSH 111 PUSH 33
PUTC PUTC
PUSH 44 PUSH 10
PUTC PUTC
PUSH 32 EXIT
PUTC
PUSH 119
PUTC
- 32. Whitespace
add = 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;
- 33. Graphviz
%%{
machine test_parser;
main := 'a' ('b' | 'c') 'd'+;
}%%
$ ragel -Vp test.rl | dot -Tpng > test.png
- 34. Perl6
● パーサのための専用構文が用意された
● grammer
● 前述のRagelの例のようなことが built-inでできる
● Perl6のパーサも Perl6 の grammer で書かれている
● http://github.com/perl6/std/blob/master/STD.pm6
- 35. まとめ
● よく使われるPerlのパーサライブラリを紹介した
● パーサを書く場合の手法をいくつか紹介した
● 速度が求められる場合はCのパーサジェネレータ+XSを検
討してもよい
●
保守性と速度のトレードオフ
- 36. 【未承諾広告】求人
● コミュニティサイト 2001年~
● 5億pv / month
● 100 servers
● mod_perl + Sledge + MySQL
● Subversion + Redmine + Capistrano
● TheSchwartz, Memcached, Solr
● perlbrew, cpanm
● 私 <jiro@cpan.org> まで
- 37. END { thank_you(); }
● References
● http://www.slideshare.net/spiritloose
● http://github.com/spiritloose/
● http://d.hatena.ne.jp/spiritloose/