SitePoint素晴らしい記事の推奨事項:PHPレンジオペレーターの実装の改善
この記事は、著者の承認とともにSitePointで再現されています。次のコンテンツはThomas Puntによって作成され、PHPレンジオペレーターの改善された実装方法を紹介します。 PHPの内部に興味があり、お気に入りのプログラミング言語に機能を追加する場合は、学ぶのに良い時期です!
この記事は、読者がソースコードからPHPを構築できると想定しています。そうでない場合は、まずPHP内部メカニズムの本の「Building PHP」の章を読んでください。
前の記事(ヒント:読んでいることを確認してください)では、PHPに範囲オペレーターを実装する方法を示しました。ただし、初期の実装がめったに最適ではないため、この記事は以前の実装を改善する方法を探ることを目的としています。
この記事を校正してくれたNikita Popovに再び感謝します!
例:
それでは、それ以上のADOなしで、範囲演算子を再実装しましょう。
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
語彙アナライザーの実装はまったく変更されていません。トークンはZend/zend_language_scanner.l(約1200行)に最初に登録されています:
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
トークン剤拡張機能は、ext/トークナイザーディレクトリを入力し、tokenizer_data_gen.shファイルを実行することにより、再生成する必要があります。
パーサーの実装は以前と同じです。繰り返しますが、次の行の最後にT_RANGEトークンを追加することにより、オペレーターの優先度と結合を宣言します。
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
%token T_RANGE "|> (T_RANGE)"
今回は、バイナリ操作を処理するために必要なロジックが既に含まれているため、Zend/zend_compile.cファイルを更新する必要はありません。したがって、オペレーターをZend_ast_binary_opノードに設定することにより、このロジックを再利用する必要があります。
ご覧のとおり、前回作成したzend_compile_range関数に非常に似ています。 2つの重要な違いは、オペコードタイプを取得する方法と、両方のオペランドがリテラルである場合に何が起こるかです。
ZEND_AST_BINARY_OPノードはバイナリ操作を区別するためにこの値を(新しい生産ルールのセマンティックアクションに示す)を保存するため、
opCodeタイプはASTノード(前回のようにハードコードではなく)から取得されます。両方のオペランドがリテラルである場合、zend_try_ct_eval_binary_op関数が呼び出されます。この関数は次のようになります:%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
この関数は、zend/zend_opcode.cのget_binary_op関数(ソースコード)からオペコードタイプに従ってコールバックを取得します。これは、Zend_Range OpCodeに合わせてこの関数を次に更新する必要があることを意味します。次のケースステートメントをget_binary_op関数に追加します(約750行):
range_function関数を定義する必要があります。これは、Zend/zend_operators.cファイルで他のすべての演算子を使用します。
| expr T_RANGE expr { $$ = zend_ast_create_binary_op(ZEND_RANGE, , ); }
関数プロトタイプには、2つの新しいマクロが含まれています:Zend_apiとZend_fastCall。 Zend_Apiは、共有オブジェクトの拡張機能にコンパイルできるようにすることにより、関数の可視性を制御するために使用されます。 Zend_fastCallは、より効率的な呼び出しコンベンションが使用されることを保証するために使用されます。ここで、最初の2つのパラメーターがスタックの代わりにレジスタに渡されます(32ビットビルドよりもX86の64ビットビルドに関連します)。
関数本体は、前の記事のZend/Zend_Vm_Def.hファイルにあるものと非常に似ています。 handle_exceptionマクロコール(返品障害に置き換えられた;)、zend_vvm_next_opcode_check_exceptionマクロコールなど、VM固有のコンテンツは存在しなくなりました。 VMコードから)。さらに、前述のように、get_opn_zval_ptr pseudo-macro(get_opn_zval_ptr_deref)を使用してVMで参照を処理することを避けます。
もう1つの顕著な違いは、参照が正しく処理されることを確認するために、両方のオペランドにZVAL_DEFEFを適用していることです。これは、以前にVM内の擬似-Macro get_opn_zval_ptr_derefを使用して行われましたが、この関数に転送されました。これは、コンパイルする必要があるために行われません(コンパイル時間処理の場合、両方のオペランドがリテラルである必要があり、参照できないため)が、参照処理について心配することなく、コードベースの他の場所で安全に呼び出される可能性があるため。したがって、ほとんどの演算子関数(パフォーマンスが重要な場合を除く)は、VMオペコード定義ではなく、参照処理を実行します。
最後に、zend/zend_operators.hファイルにrange_functionプロトタイプを追加する必要があります。
// 作为常量定义 const AN_ARRAY = 1 |> 100; // 作为初始属性定义 class A { private $a = 1 |> 2; } // 作为可选参数的默认值: function a($a = 1 |> 2) { // }
<st_in_scripting>"|>" { </st_in_scripting> RETURN_TOKEN(T_RANGE); }
すべての作業がrange_functionで処理されるため、
今回の定義ははるかに短くなります。この関数を呼び出して、計算された値を保存するために現在のOplineの結果オペランドを渡すだけです。 rience_functionから削除され、次のオペコードへのスキップがZend_vm_next_opcode_check_exceptionに呼び出されてVMで処理されます。さらに、前述のように、get_opn_zval_ptr pseudo-macro(get_opn_zval_ptr_deref)を使用してVMで参照を処理することを避けます。Zend/zend_vm_gen.phpファイルを実行してVMを再生します。
最後に、美しいプリンターはZend/zend_ast.cファイルを再度更新する必要があります。優先順位のテーブルコメント(約520行)を更新します:
%token T_RANGE "|> (T_RANGE)"
結論
この記事は、計算ロジックがVMから移動された範囲演算子を実装する代替案を示しています。これには、一定の表現のコンテキストで範囲演算子を使用できるという利点があります。%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP T_RANGE
この一連の記事の第3部は、この実装に基づいて構築され、このオペレーターを過負荷する方法を説明します。これにより、オブジェクトをオペランド(GMPライブラリのオブジェクトや__tostringメソッドを実装するオブジェクトなど)として使用できます。また、文字列に適切なサポートを追加する方法も示します(PHPの現在の範囲関数で見られるものとは異なります)。しかし、今のところ、これがオペレーターをPHPに実装する際に、ZEのより深い側面の良いデモンストレーションであることを願っています。
以上がPHPの範囲演算子の再実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。