深入理解PHP原理之实现自己的PHP语法
发布日期:2021-10-05 13:28:29 浏览次数:2 分类:技术文章

本文共 2741 字,大约阅读时间需要 9 分钟。

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

  其执行过程如下:

  这里写图片描述
  该过程为词法分析–>语法分析–>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上,上面的两条规则分别对于类似:

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。

参考:

  

转载地址:https://blog.csdn.net/lml200701158/article/details/52502517 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Socket编程与示例
下一篇:看风云变幻,自云淡风轻

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年03月27日 09时30分39秒