Zephirの中身を少し眺めたメモ

PHPのサブセットみたいな言語（`*.zep`）からPHPの拡張モジュール（Cソースコード経由で最終的には`*.so`になる）を出力するZephirという言語があります。PHPフレームワーク[Phalcon](https://phalconphp.com/ja/)の実装言語としても有名ですね。

- https://zephir-lang.com/

このZephirがどうやって`*.zep`を解釈して`*.c`を吐き出してるのか気になったので確認してみました。

# Zephir入門

Zephirのインストールは「[Installation — Zephir 0\.9\.4 documentation](https://docs.zephir-lang.com/en/latest/install.html)」に沿って行いました。

ドキュメント通りにインストールするとなぜかrootを要求されますが、`install-nosudo`を使えば`$HOME/bin/`以下に`zephir`コマンドがインストールされます。

[チュートリアル](https://docs.zephir-lang.com/en/latest/tutorial.html)に沿って下記のように`*.zep`ファイルを作成します。

```
$ zephir init utils
$ cd utils
$ vi utils/greeting.zep
```

```php: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`の中身を見てみましょう。

```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の字句解析・構文解析の本体は下記ファイルです。

- [phalcon/zephir/runtime/scanner.re](https://github.com/phalcon/zephir/blob/master/runtime/scanner.re)
- [phalcon/zephir/runtime/parser.lemon](https://github.com/phalcon/zephir/blob/master/runtime/parser.lemon)

字句解析には[re2c](http://re2c.org/)を、構文解析には[LEMON](http://www.hwaci.com/sw/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の配列の形で(!)帰ってきます。

[^1]: re2cが生成する関数。
[^2]: LEMONが生成する関数。変な関数名になっているのはコード生成時にこのプレフィックスを指定したため。

# Cソースコードの生成

構文木からCソースコードを出力する部分はピュアPHPで記述されています。`Library/CompilerFile.php`の`preCompile()`が処理の本体になります。

そこから先の処理はあまり追っていませんが、`Library/`以下が構文ごとにCソースコードを出力するような処理に対応していると思います。

- [phalcon/zephir/Library](https://github.com/phalcon/zephir/tree/master/Library)

これだけでもすごい量ですが、自動生成されるCコードから呼び出されるPHP APIのラッパー（Zephir Kernel）もかなりの実装量です。

- PHP5用: [phalcon/zephir/kernels/ZendEngine2](https://github.com/phalcon/zephir/tree/master/kernels/ZendEngine2)
- PHP7用: [phalcon/zephir/kernels/ZendEngine3](https://github.com/phalcon/zephir/tree/master/kernels/ZendEngine3)

どういうモチベーションでこんなことをする気になったのか気になりますね。

# LLVMベースのJITコンパイル実装の夢

`runtime/`以下のソースコードを眺めると、LLVMに関係しそうな関数呼び出しがチラホラ存在します。

```c:zephir.c
        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の存在を知ったのが収穫でした。小さいパーサーを作るときに便利かもしれません。