Advent Calendar 22日目 scanner.l と parser.y の差分を解説してみる

この記事は、 PHP5.4 Advent Calendar 2011 (http://atnd.org/events/22473) 22日目です。
そして同時に do_aki Advent Calendar 2011 (http://atnd.org/events/22834) の 22日目でもあります ;-p

PHP5.4 Advent Calendar 2011 の 21日目は [twitter:@kokkekun] さんの http://k-holy.sakura.ne.jp/wordpress/2011/12/21/callable_typehint/ でした。
23日目は [twitter:@Hiraku] さんの http://blog.tojiru.net/article/242054875.html です。

ぺちぺち *1 を作るに当たって、PHP の Parser や Lexer をそれなりに深く読み込んでいたこともあり、PHP5.4 での変更を詳しく追ってみました。

字句解析器、構文解析器 の差分

https://gist.github.com/1431980 (zend_language_scanner.l.diff) と https://gist.github.com/1431981 (zend_language_parser.y.diff) は、それぞれ php の lexer / parser の、 5.3.8 と 5.4.0RC2 との差分です。 *2

当初 PHP5.4 Advent Calendar でこの差分をすべて解説するつもりだったのですが、思ったよりもボリュームがあり、zend_language_scanner.l の先頭にある大きな差分*3について追うだけで時間切れになっていたのでした。


再び PHP5.4 Advent Calendar を書く機会に恵まれましたので、そのときには解説しきれなかったものを列挙します。

Zend Multibyte

zend_language_scanner.l を見てぱっと目に付くのが、 Zend Multibyte 関連による差分です。
この辺は Advent Calendar 8日目 Zend Multibyte - do_akiの徒然想記 で書いたとおり、 Zend Multibyte が標準で組み込まれたことによる変更で、多くの UNICODE関連の関数が zend_multibyte.c からこちらに移動してきました。

実は、この辺で utf8 だけでなく utf16 や utf32 を判別するコードや BOM を取り除くコードがあり、mbstring なしに使えるように見えるのですが、実際には必要となるようです。
この辺は時間あったら整理してみたいと思っています。

interned string

zend_language_scanner.l の zend_prepare_string_for_scanning 関数内に変更がありました。
if (IS_INTERNED ... のくだりです。


interned strings というのは、不変な文字列を特定の共有メモリ領域に保持しておき、必要となったときにはそこから参照する仕組みです。
これにより、使用するメモリ量を抑えられるようです。

詳しくは PHP: rfc:performanceimprovements を参照あれ。

0b[01]*

ビット列を直接表現する構文が追加されました。

例えば、 0b1010 は int(10) となります。

int に収まりきれない場合は、整数リテラル同様 float として扱われます。
単体ではマイナスの表現は出来ませんので、int(-10) を表現する場合は -0b1010 となります。 (たぶん使うことはないでしょうが)

TRAIT 関連のキーワード追加

trait / insteadof / __TRAIT__ が追加されています。

TRAIT そのものの詳細な説明は他の記事任せるとして、少々面白いのが __CLASS__ の変更です。

// This is a hack, we abuse IS_NULL to indicate an invalid value
// if __CLASS__ is encountered in a trait, however, we also not that we
// should fix it up when we copy the method into an actual class

こんなコメントが PHP: Revision 313997 で追加され、この場所ではクラス名を割り当てていません。

これは、 trait 内で __CLASS__ が呼ばれた際、trait 名ではなく、利用しているクラス名を返すための仕掛けです。

<?php
trait T {
  function callT() {
    echo __CLASS__, PHP_EOL;
  }
}

class C1 {
  use T;
}
class C2 {
  use T;
}

(new C1())->callT();  // C1 と表示
(new C2())->callT();  // C2 と表示

trait 内において、 __CLASS__ は可変です。

callable キーワード追加

タイプヒンティング用に追加されました。
詳しくは http://k-holy.sakura.ne.jp/wordpress/2011/12/21/callable_typehint/ を参照してください。


ちと注意が必要なのが、

function call(callable $f) {
	$f();
}

call('phpinfo');

このコード、何のエラーも警告もなく phpinfo() が実行されます。
当然と言えば当然ですけどね。

<?= が常に有効に

lexer 的には if 文が一つ無くなっただけ ;-p

%token の明示的な指定

さて、ここからは zend_language_parser.y の話。

パーサの差分で大きいのは %token の明示的な指定です。PHP: Revision 312424 のコミット。
パースエラーの改善が目的だったようです。

内部構造の変化

$$.u.opline_num -> $$.u.op.opline_num
$$.u.EA.type -> $$.EA

といった変更が目につきます。 zend_op や znode の構造変更に伴う修正のようです。(zend_compile.h を参照)
拡張書く人にはこの辺の変更は結構重要かも。
僕は深く追っていませんが。

short array syntax

みんな大好き short array syntax。もうこれなしにはphp 書けません。 [twitter:@rsky] さん++


Parser としては、 expr_without_variable の定義の一つ

'[' array_pair_list ']' { $$ = $2; }

と、 static_scalar の定義の一つ

'[' static_array_pair_list ']' { $$ = $2; Z_TYPE($$.u.constant) = IS_CONSTANT_ARRAY; }

これの追加です。php は、 コンパイル時に評価可能なモノはなるべくコンパイル時に処理しようとしていて、
static_scalar の方は、そのための定義となっています。

コミットは PHP: Revision 313641

array dereference

これはもう、他の方がたくさん解説してるので解説は割愛。

<?php
function f() {
  return ['x','y','z'];
}

echo f()[0];  // x

コミットは PHP: Revision 300266
詳細は PHP: rfc:functionarraydereferencing

instance_call

これももう知ってますよね。

<?php
class A {
  function hello() {
    echo 'hello world';
  }
}

(new A())->hello();  // hello world

コミットは http://svn.php.net/viewvc?view=revision&revision=318823

Class::{expr}() 構文の追加

静的メソッドの呼び出しに、評価式が使えるようになりました。 (PHP: Revision 314025)

通常のメソッドは $cls->{expr}() で呼べましたが、
これにより静的メソッドでも同様のことが可能になっています。

<?php
class Cls {
  function __call($name, $args) {
    echo $name, PHP_EOL;
  }
  static function __callStatic($name, $args) {
    echo '[static]',$name, PHP_EOL;
  }
}


$hoge = 'hoge';
(new Cls())->{$hoge}();
Cls::{$hoge}();

ところで、 PHP5.4.0RC3 で、

Cls::{0}();

これが Segmentation fault 引き起こすのは僕だけでしょうか?

end script のエラーがわかりやすく?

<?php
   if (1) {  // 閉じてない!

こんなスクリプトを喰わせると、php5.3 系では

Parse error: syntax error, unexpected $end in invalid_end.php on line 4

$end ってなんだよ!? って思われていた方も多いと思いますが、php5.4 では

Parse error: syntax error, unexpected end of file in invalid_end.php on line 4

こうなります。
分かりやすくなった!

まとめ

zend_language_scanner.l と zend_language_parser.y の差分からphp5.4 での変更点を追ってみました。 (後半だいぶ端折りましたが^^;)

ソースコード全体の変更はもっとたくさんありますが、字句解析、構文解析だけでもこれだけの変化があります。

すごい!

*1:http://d.hatena.ne.jp/do_aki/20110912/1315800142 - PHPカンファレンス2011 でLTしてきました

*2:以前置いたモノなので RC3ではなく RC2です

*3:zend_multibyte 関連