深入理解PHP原理之实现自己的PHP语法

往期精选(欢迎转发~~)

前面的文章中已经讲过PHP的词法分析、语法分析、opcodes编译,有了上面的基础,我们可以通过修改PHP源码,实现自己的PHP语法,示例如下:

<?php
	$demo = 'tipi';
	echo var_name($demo); //执行结果,输出:demo
?>

其执行过程如下:
  这里写图片描述
  该过程为词法分析–>语法分析–>opcodes编译–>执行,下面我们看看每一步对源码有哪些修改。
1.词法分析和语法分析
  我们知道词法分析和语法分析的文件分别为zend_language_scanner.l和zend_language_parser.y。首先我们需要加入新的Token,即在文件zend_language_scanner.l中加入以下内容:

"var_name" {
	return T_VARIABLE_NAME;
}

也就是在词分析阶段遇到var_name这个字符串的时候会被标记为我们定义的T_VARIABLE_NAME token。同样,在 zend_language_parser.y 也需要加入对这个token进行响应的逻辑处理。我们要实现的语法和PHP内置的echo print结构类似,所以我们把这个处理放到 internal_functions_in_yacc规则里面:

| T_VARIABLE_NAME '(' T_VARIABLE ')' { zend_do_variable_name(&$$, &$3 TSRMLS_CC); }
| T_VARIABLE_NAME T_VARIABLE { zend_do_variable_name(&$$, &$2 TSRMLS_CC); }

第一个参数是当前表达式的返回值(编辑器不能连续打两个美元符号),&$3表是第三个表达式的值,也就是T_VARIABLE上,上面的两条规则分别对于类似:

<?php
echo var_name($varname);
echo var_name $varname;

2.opcodes编译
  opcode在PHP中通常是一个数字唯一标识,首先,我们在Zend/zend_vm_opcodes.h 为我们的新opcode 加入一个宏定义,这个数字要求在0-255之间,并且不能与现有opcode重复:

#define ZEND_VARIABLE_NAME 154

第二步,在Zend/zend_compile.c中加入我们对opcode的处理,也就是将代码操作转化为op_array放入到opline中:

void zend_do_variable_name(znode *result, znode *variable TSRMLS_DC)
{
	// 生成一条zend_op
	zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
	// 因为我们需要有返回值,并且返回值只作为中间值.所以就是一个临时变量
	opline->result.op_type = IS_TMP_VAR;
	opline->result.u.var = get_temporary_variable(CG(active_op_array));
	opline->opcode = ZEND_VARIABLE_NAME;
	opline->op1 = *variable;
	// 我们只需要一个操作数就好了
	SET_UNUSED(opline->op2);
	*result = opline->result;
}

这样,我们就完成了对opcode的编译。
3.内部处理逻辑的编写
  前面只是基本语法处理与编译,这部分才是核心,包括如何处理自定义的opcode,以及编写具体的代码逻辑。前面我们提到 Zend/zend_vm_execute.h中的zend_vm_get_opcode_handler()函数,这个函数是用来获取opcode的执行函数,其对应关系通过公式计算,公式如下:

return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];

从这个公式我们可以看出,最终的处理函数与参数类型有关,根据计算,我们要满足所有类型的映射,尽管我们可以可以使用同一函数进行处理, 于是我们在zend_opcode_handlers这个数组的结尾,加上25个相同的函数定义:

void zend_init_opcodes_handlers(void)
{
	static const opcode_handler_t labels[] = {
	....
	ZEND_VARIABLE_NAME_HANDLER,
	....
	ZEND_VARIABLE_NAME_HANDLER
}

如果我们不想支持某类型的数据,只需要将类型代入公式计算出的数字做为索引,使
opcode_handler_t中相应的项为:ZEND_NULL_HANDLER。最后,我们在Zend/zend_vm_def.h 中增加相应的处理函数,增加代码如下:

static int ZEND_FASTCALL ZEND_VARIABLE_NAME_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	zend_op *opline = EX(opline);
	// PHP中所有的变量在内部都是存储在zval结构中的.
	zval *result = &EX_T(opline->result.u.var).tmp_var;
	// 把变量的名字赋给临时返回值
	Z_STRVAL(*result) = estrndup(opline->op1.u.constant.value.str.val, opline->op1.u.constant.value.str.len);
	Z_STRLEN(*result) = opline->op1.u.constant.value.str.len;
	Z_TYPE(EX_T(opline->result.u.var).tmp_var) = IS_STRING;
	ZEND_VM_NEXT_OPCODE();
}

进行完上面的修改之后,我们要删除r2ec&flex已经编译好的原文件,即删除Zend/zend_language*.c文件以使新的语法规则生效。 这样我们再次对PHP源码进行make时,会自动生成新的编译好的语法规则处理程序,不过编译环境要安装有lex&yacc和re2c。

参考: http://www.php-internals.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值