RPythonで正規表現を使えるようにする。 PyPy Advent Calendar 19日目

PyPy Advent Calendar 19日目の担当として、ピンチヒッター[twitter:@yanolab]行きます!本来2週目は24日に担当ということでのんびりしてたら前の方々が忙しいらしく急遽回って参りました。精一杯がんばるので、どうかいじめないでください。。。 > pypyjaな方々。

今回のテーマ

RPythonではrsreという正規表現インタプリタに提供するための実装はありますが、RPython自身で使用するための正規表現実装がありません。そこで、今回はタイトルにもあるようにRPythonで正規表現を使えるようにするところまでを書きたいと思います。

ことの発端

PyPy Advent Calendar 15日目 - 低レベルっぽいことをやってみるで[twitter:@shoma4a]さんがRPython Toolchain側の低レベルを紹介していることでした。
ちょっとだけ記事を引用してみます。

ffi とは、Foreign Function Interface と呼ばれるもので、日本語に訳すと他言語関数インタフェイスというらしいです。
要は別の言語の関数を呼び出すための仕組みです。
clibffi の場合は Python から C の関数を呼ぶものです。
そもそも C の関数を呼べないことにはソケット通信もできませんし、スレッドも立てられません。
この clibffi を使うことで ctypes やスレッド、ソケットなどのライブラリ実装したりしているんですね。多分。

そーなんですよね、Cの関数が呼べないと何もできないはず。これと、前に自分もツイッターで呟いたんだけども、

http://twitter.com/#!/yanolab/status/125560987129618435:twitter
http://twitter.com/#!/yanolab/status/125561313270312960:twitter
http://twitter.com/#!/yanolab/status/125561997302562816:twitter

PyPyをビルドするときはlibffi.aを静的リンクしたりしているし、他のライブラリを使って正規表現を実装できないなんてことはないはず!この仮説が正しいことを証明しちゃる!ってことで書きます。

アプローチ

上述しているように、低レベルなAPIを提供するC言語用ライブラリをリンクしffiを使ってラップし、それを使ってRPythonに正規表現の機能を提供します。

使用ライブラリについて

上記のツイッターのつぶやきには鬼車を使用するとありますが、筆者の独断と偏見により、PCREというライブラリを選ばせていただきました。ライブラリ選定の際には最近googleからリリースされた?re2というライブライと鬼車とPCREを比較しましたが、ubuntuのパッケージになっており、C言語用のインターフェースがあるということでPCREを使うことにしました。PCREとはC言語においてPerlと互換な正規表現をライブラリレベルで提供するものらしいです。詳しくは本家サイトをご覧ください。http://www.pcre.org/

準備

前置きが長くなりましたが、ライブラリの準備はubuntuであればlibpcre3-devパッケージをインストールするだけです!楽ちん!

sudo aptitude install libpcre3-dev

次にこれは飛ばしてもよいですが、やっておくと楽な設定です。

vi ~/.bashrc
alias rpython='PYTHONPATH=/pypy-src-path/ python'
alias translate='rpython /pypy-src-path/translator/goal/translate.py'

これをやっておくと、いちいちPYTHONPATHを設定したりしなくていいので何回も実行するときに楽です。

次に筆者作のpypy用正規表現ライブラリをクローンします。

hg clone https://bitbucket.org/yanolab/pypypcre

このレポジトリにはライブラリ本体(pypypcre.py)とsample.pyの2点だけが含まれています。(適当ライブラリですいません。。。モジュールの構造にすらなってなくてすいません。。。)
確認ができたら準備は終了です。

実行

translate sample.py

としてみてください。sample-cができるはずです。usage出力も実装しておいたので、sample-cと実行して試してみてください。

解説

sample.pyの解説はいいですよね?pythonのとほぼ同じですから。ということで、ライブラリ本体の解説をします。
追記:すいません、Gistの行出てないですね・・・。gist右下のpypyprce.pyを開いて見てもらえればと思います。

  • line:17 PC環境の中からlibpcre.aまたはlibpcre.soを探します。完全にlinux決め打ちです。macやその他のディストリビューションでは適宜ライブラリの検索先を変更するといいと思います。libpcre.aが見つかればトランスレートしたときに静的リンクし、見つからない場合は、libpcre.soを動的リンクします。どちらも見つからないときはpypypcreを利用することはできません。ここはPyPyで使っているlibffiと同じです。
  • line:43 - 92 C言語のライブラリ用にインターフェースを定義しています。ctypesと同じような感じです。おもしろいのは70-71行目のループです。CConfigの値を実際にC言語ソースをはき出して、コンパイル+実行(ダンプ)し、PCRE_ERROR_NOMATCHの値を取得します。PCRE_ERROR_NOMATCHはリンクするライブラリのヘッダに書かれている定数です。./configureを実行しているような感じですね。
  • line:94pcre_externalでC言語のインターフェースをRPythonレベルまで昇華させています。実際にPythonで実行するとここはctypesが使われ、translateするとインタフェース使用部分がC言語に落とされます。
  • line:113 - Python正規表現クラスに合わせるための実装です。現在はmatch,compileの2つのみサポートです。もうちょっと完成度あげたかったけど時間がないのでしかたありませんw

もし低レベルなライブラリを作成しようと思ったら

基本Pythonみたいなので、読みやすそうですが、気をつけなければならない点があります。まず、変換後のC言語ソースを想像しながらかかないと間違いなくtype missmatchになります。また、ポインタの扱いがrffiの場合はひじょーーーに難しいです。nullを渡したいところでNoneをしたりするとだめですし、参照渡しも書けません(知らないだけかも)。また、意識しなければならない型が、C言語の型、rffiの型、RPythonの型と3種類(PyPyはさらにそれをラップしてるので4種類)を意識しなければなりません。どれか一つでもずれていると、rpythonでは動くけどtranslateはできないといった自体になります。また、rffiの層で確保したメモリ等は自分で解放しないといけません。

感想

慣れないと正直きついです。つらいです。でもだんだん楽しくなってきます。きっとwしかし、このエントリを読んでうれしい人っているのかどうか・・・たぶんいても日本に2〜3人w
まー誰かの知識の糧となれれば幸いです。

それでは、enjoy your pypy and python life!