PHPのサブセットみたいな言語(*.zep
)からPHPの拡張モジュール(Cソースコード経由で最終的には*.so
になる)を出力するZephirという言語があります。PHPフレームワークPhalconの実装言語としても有名ですね。
このZephirがどうやって*.zep
を解釈して*.c
を吐き出してるのか気になったので確認してみました。
Zephir入門
Zephirのインストールは「Installation — Zephir 0.9.4 documentation」に沿って行いました。
ドキュメント通りにインストールするとなぜかrootを要求されますが、install-nosudo
を使えば$HOME/bin/
以下にzephir
コマンドがインストールされます。
チュートリアルに沿って下記のように*.zep
ファイルを作成します。
$ zephir init utils
$ cd utils
$ vi utils/greeting.zep
namespace Utils;
class Greeting
{
public static function say()
{
var_dump("hello world!");
}
}
では、さっそくビルドしてみましょう。
$ zephir build
これでエクステンションutils.so
がビルドされます。また、sudo権限があれば勝手にPHP拡張モジュールのインストールまでやってくれます。
途中経過で作られたCソースコードext/utils/greeting.zep.c
の中身を見てみましょう。
PHP_METHOD(Utils_Greeting, say) {
zval _0;
zval *this_ptr = getThis();
ZVAL_UNDEF(&_0);
ZEPHIR_MM_GROW();
ZEPHIR_INIT_VAR(&_0);
ZEPHIR_INIT_NVAR(&_0);
ZVAL_STRING(&_0, "hello world!");
zephir_var_dump(&_0 TSRMLS_CC);
ZEPHIR_MM_RESTORE();
}
独自マクロだらけですが、確かにPHP拡張モジュールっぽいCソースコードが出力されています。
コンパイラの本体を探す
さて、上記Cソースコードが出力されるまでの処理を追ってみましょう。
zephir
コマンドの中身はシェルスクリプトで、最終的にPHPスクリプトcompiler.php
を呼び出しています。その後の処理でビルド専用のPHP拡張モジュールzephir_parser.so
を指定してPHPを起動するようになっており、構文解析などはこの拡張モジュール内で実装されていることがわかります。
逆に言うと、zephir
コマンドはZephirコンパイル用のPHP拡張に依存しているため、PHPのバージョンが変わるとZephir自体の再インストールが必要になります。
字句解析・構文解析の本体
Zephirの字句解析・構文解析の本体は下記ファイルです。
字句解析にはre2cを、構文解析にはLEMONを利用しています。
*.re
ファイルを見るとZephirの対応しているトークンが確認できます。
たとえば、下記の定義からZephirでは浮動小数点数の指数形式(1e10のような形式)をサポートしていないことがわかります。
DOUBLE = ([\-]?[0-9]+[\.][0-9]+);
DOUBLE {
token->opcode = ZEPHIR_T_DOUBLE;
token->value = estrndup(start, YYCURSOR - start);
token->len = YYCURSOR - start;
s->active_char += (YYCURSOR - start);
q = YYCURSOR;
return 0;
}
構文解析機本体はruntime/base.c
のzephir_parse_program()
です。zephir_get_token()
1で字句解析器からトークンを取り出し、zephir_()
2で構文解析を行っています。構文解析の結果、*.zep
ファイルに対応する構文木がPHPの配列の形で(!)帰ってきます。
Cソースコードの生成
構文木からCソースコードを出力する部分はピュアPHPで記述されています。Library/CompilerFile.php
のpreCompile()
が処理の本体になります。
そこから先の処理はあまり追っていませんが、Library/
以下が構文ごとにCソースコードを出力するような処理に対応していると思います。
これだけでもすごい量ですが、自動生成されるCコードから呼び出されるPHP APIのラッパー(Zephir Kernel)もかなりの実装量です。
どういうモチベーションでこんなことをする気になったのか気になりますね。
LLVMベースのJITコンパイル実装の夢
runtime/
以下のソースコードを眺めると、LLVMに関係しそうな関数呼び出しがチラホラ存在します。
if (!ZEPHIRT_GLOBAL(module)) {
ZEPHIRT_GLOBAL(module) = LLVMModuleCreateWithName("zephir");
ZEPHIRT_GLOBAL(builder) = LLVMCreateBuilder();
LLVMInitializeNativeTarget();
LLVMLinkInJIT();
if (LLVMCreateExecutionEngineForModule(&ZEPHIRT_GLOBAL(engine), ZEPHIRT_GLOBAL(module), &msg) == 1) {
fprintf(stderr, "%s\n", msg);
LLVMDisposeMessage(msg);
return;
}
ZEPHIRT_GLOBAL(pass_manager) = LLVMCreateFunctionPassManagerForModule(ZEPHIRT_GLOBAL(module));
LLVMAddTargetData(LLVMGetExecutionEngineTargetData(ZEPHIRT_GLOBAL(engine)), ZEPHIRT_GLOBAL(pass_manager));
}
これは*.zep
のJITコンパイル実装のようです。どうやらZephirはZend Engine上で動くスクリプト言語としての野望も持っているようですね。開発状況としては実験段階のようですが、ロマンを感じます。
まとめ
ZephirはPHPに文法が近いという話を聞いていたので、どうせ言語といっても文字列置換程度かと想像していたんですが、思ったより言語っぽい実装になっていたのは意外でした。
個人的にはLEMONの存在を知ったのが収穫でした。小さいパーサーを作るときに便利かもしれません。