好文章! nikic 介绍了如何向PHP添加新的语法特性,原文 写的非常精彩,具体是添加in
语法功能,使最终实现:
1 2 3 4 5 6 7 8 $words = ['hello' , 'world' , 'foo' , 'bar' ];var_dump ('hello' in $words ); var_dump ('foo' in $words ); var_dump ('blub' in $words ); $string = 'PHP is fun!' ;var_dump ('PHP' in $string ); var_dump ('Python' in $string );
我进行了一下实践,根据PHP7进行了些修改,具体记录下自己的实践过程和心得。
环境准备 从Github 上下载PHP7的源码,准备好PHP的开发编译环境,我是基于ubuntu,所以运行如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ apt-get -y install build-essential autoconf bison re2c 下一步,编译PHP7 $ cd php-src $ git checkout -b addInOperator $ ./buildconf $ ./configure --disable-all --enable-debug --enable-maintainer-zts $ make -j4 $ ./sapi/cli/php -v $ ./sapi/cli/php -r 'echo "Hallo World!";'
PHP脚本的生命历程 PHP脚本运行经过3个主要阶段:
词法分析
语法分析&编译
执行
词法分析 这个阶段是将源码根据规则分解成称为token
更小的单元,为后面的语法分析提供材料。 修改位于Zend/
目录的zend_language_scanner.l
,这个文件定义了词法记号规则,为了让PHP能识别in
作为关键词。
1 2 3 <ST_IN_SCRIPTING>"in" { RETURN_TOKEN(T_IN); }
为了让Zend引擎能识别T_IN
这个记号,在zend_language_parser.y
中加入:
重新生成tokenizer
系列源文件
1 2 $ cd ext/tokenizer $ ./tokenizer_data_gen.sh
语法分析&编译 向语法分析增加in
所应用的表达式规则,在zend_language_parser.y
加入:
1 2 3 4 expr_without_variable: ... | expr T_IN expr { $$ = zend_ast_create_binary_op(ZEND_IN, $1, $3); } ...
再设置in
的操作符优先级,在语法分析文件中找到以下行,并在行尾加入T_IN
:
1 %nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
执行 最后在zend_vm_def.h
中添加具体的执行逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ZEND_VM_HANDLER (173 , ZEND_IN, CONST|TMPVAR|CV, CONST|TMPVAR|CV){ USE_OPLINE zend_free_op free_op1, free_op2; zval *op1, *op2; SAVE_OPLINE (); op1 = GET_OP1_ZVAL_PTR_UNDEF (BP_VAR_R); op2 = GET_OP2_ZVAL_PTR_UNDEF (BP_VAR_R); FREE_OP1 (); FREE_OP2 (); CHECK_EXCEPTION (); ZEND_VM_NEXT_OPCODE (); }
在PHP源码根目录运行:
1 2 $ ./sapi/cli/php ./Zend/zend_vm_gen.php $ make -j4
zend_vm_gen.php
是根据zend_vm_def.h
中的定义生成zend_vm_*.*
系列的文件。
之后测试编译后的PHP
:
1 $ ./sapi/cli/php -r 'var_dump("s" in "str");'
会出现Segmentation fault
,原因是我之前在zend_language_parser.y
中加入的操作是zend_ast_create_binary_op
,所以在编译时会调用位于zend_opcode.c
的get_binary_op
方法,这个方法返回一个函数指针,用于处理expr in expr
这个操作两个参数的语句。
1 2 binary_op_type op = get_binary_op(ast->attr); ret = op(result, &op1, &op2);
所以之后我在3处添加代码:
zend_opcode.c
中添加查询ZEND_IN
操作函数的代码
1 2 3 4 5 6 7 8 ZEND_API binary_op_type get_binary_op (int opcode) { switch (opcode) { ... case ZEND_IN: return (binary_op_type) in_function; ... }
zend_operators.c
中添加处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 ZEND_API int ZEND_FASTCALL in_function (zval *result, zval *op1, zval *op2) { zval op1_copy, op2_copy; int use_copy1 = 0 , use_copy2 = 0 ; switch (Z_TYPE_P (op2)) { case IS_STRING: use_copy1 = zend_make_printable_zval (op1, &op1_copy); if (use_copy1) { op1 = &op1_copy; } if (Z_STRLEN_P (op1) == 0 ) { ZVAL_TRUE (result); } else { const char *found = zend_memnstr_ex ( Z_STRVAL_P (op2), Z_STRVAL_P (op1), Z_STRLEN_P (op1), Z_STRVAL_P (op2) + Z_STRLEN_P (op2) ); ZVAL_BOOL (result, found != NULL ); } break ; case IS_ARRAY: use_copy1 = zend_make_printable_zval (op1, &op1_copy); if (use_copy1) { op1 = &op1_copy; } ZVAL_BOOL (result, zend_hash_exists (Z_ARR_P (op2), Z_STR_P (op1))); break ; default : zend_error (E_EXCEPTION | E_ERROR, "Unsupported operand types" ); ZVAL_FALSE (result); return FAILURE; } if (use_copy1) { zval_dtor (&op1_copy); } return SUCCESS; }
zend_vm_def.h
中补上TODO
该做的处理逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ZEND_VM_HANDLER (173 , ZEND_IN, CONST|TMPVAR|CV, CONST|TMPVAR|CV){ USE_OPLINE zend_free_op free_op1, free_op2; zval *op1, *op2; SAVE_OPLINE (); op1 = GET_OP1_ZVAL_PTR_UNDEF (BP_VAR_R); op2 = GET_OP2_ZVAL_PTR_UNDEF (BP_VAR_R); in_function (EX_VAR (opline->result.var), op1, op2); ZVAL_TRUE (EX_VAR (opline->result.var)); FREE_OP1 (); FREE_OP2 (); CHECK_EXCEPTION (); ZEND_VM_NEXT_OPCODE (); }
依旧是在PHP源码根目录下运行:
1 2 $ ./sapi/cli/php ./Zend/zend_vm_gen.php $ make -j4
现在再测试生成的php
,应该会满足:
1 2 3 4 5 6 7 8 9 10 $ ./sapi/cli/php -r 'var_dump("s" in "str");' bool(true ) $ ./sapi/cli/php -r 'var_dump("s" in "xxx");' bool(false ) $ ./sapi/cli/php -r 'var_dump("a" in ["a"=>1]);' bool(true ) $ ./sapi/cli/php -r 'var_dump("a" in ["b"=>1]);' bool(false ) $ ./sapi/cli/php -r 'var_dump("s" in 123);' Fatal error: Unsupported operand types in Command line code on line 1
小结 实际进行了一番操作之后,对于PHP
的运行机制会落实到更小的单位,从解释阶段到文件级别。PHP7
版本引入了ast
这个中间结构,比起nikic 那个版本来说,需要修改更多文件,下一步要剖析一下ast
在PHP7
中的作用和在执行过程中的角色。