自己动手为PHP7添加新的语法特性
好文章!
nikic介绍了如何向PHP添加新的语法特性,原文写的非常精彩,具体是添加in
语法功能,使最终实现:
<?php
$words = ['hello', 'world', 'foo', 'bar'];
var_dump('hello' in $words); // true
var_dump('foo' in $words); // true
var_dump('blub' in $words); // false
$string = 'PHP is fun!';
var_dump('PHP' in $string); // true
var_dump('Python' in $string); // false
我进行了一下实践,根据PHP7进行了些修改,具体记录下自己的实践过程和心得。
环境准备
从Github上下载PHP7的源码,准备好PHP的开发编译环境,我是基于ubuntu,所以运行如下:
$ apt-get -y install build-essential autoconf bison re2c
下一步,编译PHP7
$ cd php-src
// create new branch for in operator
$ git checkout -b addInOperator
// build ./configure script
$ ./buildconf
// configure PHP in debug mode and with thread safety
$ ./configure --disable-all --enable-debug --enable-maintainer-zts
// compile (4 is the number of cores you have)
$ make -j4
PHP的二进制就是sapi/cli/php
,可以运行以下命令测试是否编译成功:
$ ./sapi/cli/php -v
$ ./sapi/cli/php -r 'echo "Hallo World!";'
PHP脚本的生命历程
PHP脚本运行经过3个主要阶段:
- 词法分析
- 语法分析&编译
- 执行
词法分析
这个阶段是将源码根据规则分解成称为token
更小的单元,为后面的语法分析提供材料。
修改位于Zend/
目录的zend_language_scanner.l
,这个文件定义了词法记号规则,为了让PHP能识别in
作为关键词。
<ST_IN_SCRIPTING>"in" {
RETURN_TOKEN(T_IN);
}
为了让Zend引擎能识别T_IN
这个记号,在zend_language_parser.y
中加入:
%token T_IN "in (T_IN)"
重新生成tokenizer
系列源文件
$ cd ext/tokenizer
$ ./tokenizer_data_gen.sh
语法分析&编译
向语法分析增加in
所应用的表达式规则,在zend_language_parser.y
加入:
expr_without_variable:
...
| expr T_IN expr { $$ = zend_ast_create_binary_op(ZEND_IN, $1, $3); }
...
再设置in
的操作符优先级,在语法分析文件中找到以下行,并在行尾加入T_IN
:
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
执行
最后在zend_vm_def.h
中添加具体的执行逻辑:
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);
// TODO
FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
在PHP源码根目录运行:
$ ./sapi/cli/php ./Zend/zend_vm_gen.php
$ make -j4
zend_vm_gen.php
是根据zend_vm_def.h
中的定义生成zend_vm_*.*
系列的文件。
之后测试编译后的PHP
:
$ ./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
这个操作两个参数的语句。
binary_op_type op = get_binary_op(ast->attr);
ret = op(result, &op1, &op2);
所以之后我在3处添加代码:
zend_opcode.c
中添加查询ZEND_IN
操作函数的代码
ZEND_API binary_op_type get_binary_op(int opcode)
{
switch (opcode) {
...
case ZEND_IN:
return (binary_op_type) in_function;
...
}
zend_operators.c
中添加处理函数
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
该做的处理逻辑
// 我在系统里的最新操作号是173,根据实际为准
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源码根目录下运行:
$ ./sapi/cli/php ./Zend/zend_vm_gen.php
$ make -j4
现在再测试生成的php
,应该会满足:
$ ./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
中的作用和在执行过程中的角色。
自己动手为PHP7添加新的语法特性的更多相关文章
- 关于Java 8新引入语法特性的简要说明
Java 8在语法上的主要改进就是新增了Lambda Expression以及Method Reference.由于官方网站的介绍稍显罗嗦,而且例子也有些复杂.我这里将提供一些更为浅显.直观的例子来帮 ...
- php7.0 和 php7.1新特性
PHP7.1 新特性 1.可为空(Nullable)类型 类型现在允许为空,当启用这个特性时,传入的参数或者函数返回的结果要么是给定的类型,要么是 null .可以通过在类型前面加上一个问号来使之成为 ...
- C#新语法特性前瞻
今天逛微软的UserVoice site发现了几个有很有用,也很可能被添加到新版C#中的语法,当然也可能被推迟到下一版,拿出来给大家分享一下. 另外还没投票的可以去为自己最想要的新特性投票,有兴趣的可 ...
- spring AOP Bean添加新方法
目的:为studentAdditionalDetails中添加Student的showDetails()和ExtraShowDetails()两个方法 spring 中AOP能够为现有的方法添加额外 ...
- Mysql下在某一列后即表的某一位置添加新列的sql语句
Mysql简介 MySQL是一个开放源码的小型关联式数据库管理系统,开发者为瑞典MySQL AB公司.MySQL被广泛地应用在Internet上的中小型网站中.由于其体积小.速度快.总体拥有成本低,尤 ...
- mssql sqlserver 给已存在表添加新的字段及字段备注的方法
转自:http://www.maomao365.com/?p=8102 摘要: 下文讲述向已存在表上添加新字段及字段备注的方法,如下所示: 实验环境:sql server 2008 R2 1. 添加新 ...
- SQL Server 分区表上建立ColumnStore Index 如何添加新分区方法与步骤
在生产环境中会遇到这样的场景,一个表随着时间的推移,越来越大,这个时候我们开始动手为这个表建立分区来改进查询性能. 但是表过大上百个G的时候,在数据仓库中,为了改进查询性能,我们可以添加在分区表的基础 ...
- 从认识面向对象到构造函数的标准写法(构造函数的继承、多态、ECMA6中新代替语法class) - 下
笔记一个包含:认识面向对象.构造函数的封装.继承.多态.ECMA6中新代替语法class 下:包括构造函数的继承.多态.ECMA6中新代替语法class 构造函数的继承 从父一级延续下来的属性和功能( ...
- 使用Flexbox:新旧语法混用实现最佳浏览器兼容
Flexbox非常的棒,肯定是未来布局的一种主流.在过去的几年这之中,语法改变了不少,这里有一篇“旧”和“新”新的语法区别教程(如果你对英文不太感兴趣,可以移步阅读中文版本).但是,如果我们把Flex ...
随机推荐
- dubbo架构演变之路
背景 (#) 随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进. 单一应用架构 当网站流量很小时, ...
- Android 开发笔记 “android调试遇到ADB server didn't ACK以及顽固的sjk_daemon进程 ”
资源来源:http://blog.csdn.net/wangdong20/article/details/20839533 做Android调试的时候经常会遇到,程序写好了,准备接上手机调试,可不一会 ...
- SQL中 and or优先级问题
资源来源:http://www.linuxidc.com/Linux/2012-03/56267.htm 刚刚在项目中遇到这样一个问题,SQL语句如下: select * from LOAN_BACK ...
- python函数any()与all()
any(iterable) all(iterable) any()与all()函数的区别,any是任意,而all是全部. 版本:该函数适用于2.5以上版本,兼容python3版本. any(itera ...
- 关于 free() 函数用法的若干疑问
<C语言参考手册>中关于 free() 函数有如下描述. (1)free() 函数的原型 void free(void *ptr); (2)free 函数对以前由 malloc.callo ...
- 【图文教程】用“iz3d”软件将您的游戏打造为红蓝3D游戏。
iz3d是一款能将普通3D游戏转换为红蓝3D游戏的软件.基本上支持所有游戏,或许没用过的人会认为这只是类似于播放器中的一个小功能,将平面图形做成“伪3D”红蓝效果. 实际上不是的,游戏与平面图的结构不 ...
- hql中不能写count(1)能够写count(a.id)
hql中不能写count(1)能够写count(a.id)里面写详细的属性 String hql="select new com.haiyisoft.vo.entity.cc.repo.Bu ...
- JS浏览器类型推断方法
在网站的前端开发,浏览器兼容性问题这已经让我们抢,Chrome但也生出不知道多少麻烦,我们增加. 浏览器兼容性将由前端开发框架解决的第一个问题.要解决的兼容性问题必须首先准确推断浏览器的类型和它的版本 ...
- 【Spring】手动获取spring容器对象时,报no qualifying bean of type is defined
手动获取容器对象时,报no qualifying bean of type is defined, 经过调查,发现手动获取的时候,该类所在的包必须经过spring容器初始化. 1.SpringConf ...
- iOS开发中遇到的bug
报错:The operation couldn’t be completed. (LaunchServicesError error 0.) 解决办法:重置模拟器