Zend Engineをハックしてみた

勢いに任せてVM構文解析器・コア関数をハックして以下の機能を実装してみました。

  • 無名関数リテラル (PHPの仕様上、実際は自動で名前が割り振られ、リクエスト終了まで関数は生き続ける)
  • 角括弧による配列リテラル
  • Callable Object


Zend Extensionにするのは面倒なので、まずはパッチとして公開してみます。このパッチを当ててPHPをビルドする際に zend_language_parser.c と zend_language_parser.h を再生成するので、Bisonの特定のバージョンがインストールされていないといけません。PHP: Unix システムへのインストール - Manualにあるものを揃えてください。僕の場合は bison 1.75, flex 2.5.4, re2c 0.9.12, autoconf 2.13, automake 1.5, libtool 1.4.3 を /opt/php/build 以下にインストールし、PHPのビルド時は /opt/php/build/bin をコマンド検索パスの先頭にしています。

無名関数リテラル

http://pastebin.ca/400871を参考に実装しました。
サンプル:

<?php
$array = range(1, 5);
shuffle($array);
print_r($array);
usort($array, function($a,$b){return $a-$b;});
print_r($array);

// 参照を返す無名関数も書けるけど、"function" と "(" の間に "&" があるのがキモい
$lambda = function&(){
    static $a = [];
    return $a;
}; // ブレースの後にセミコロンをつけないとエラーになるので注意
$a = &$lambda();
$a[] = 1;
print_r($a);
$b = &$lambda();
$b[] = 2;
print_r($b);
var_dump($a === $b);

結果:

Array
(
    [0] => 3
    [1] => 1
    [2] => 4
    [3] => 2
    [4] => 5
)
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
)
Array
(
    [0] => 1
)
Array
(
    [0] => 1
    [1] => 2
)
bool(true)

角括弧による配列リテラル

相変わらずキーを指定するときは"=>"が必要ですが、arrayを書かずにすむだけ楽かと。
サンプル:

<?php
var_dump([0, 1, 2]);
var_dump(['foo': 'orange', 'bar': 'apple', 'baz': 'lemon']);

結果:

array(3) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
}
array(3) {
  ["foo"]=>
  string(6) "orange"
  ["bar"]=>
  string(5) "apple"
  ["baz"]=>
  string(5) "lemon"
}

Callable Object

Callableインターフェイスを実装したクラスのインスタンスを関数のように扱うことができます。Callableインターフェイスを実装するクラスはpublicな関数callを実装しなければなりません。サンプルでは単にHello, Worldを出力するだけですが、任意の引数をとったり、$thisにアクセスしたり、コールバック関数として引数にすることも可能ですcall_user_func, call_user_func_arrayには対応していますが、usortやpreg_replace_callaback等には使えませんでした
サンプル:

<?php

class Proc implements Callable
{
    public function call()
    {
        echo "Hello, World!\n";
    }
}

$proc = new Proc;
$proc();

結果:

Hello, World!