定制自己的PHP语法-在PHP中实现unless
首先 ,插一句题外话: 从我发表第一篇PHP执行原理的文章到现在, 已经快2年了, 我很高兴的看到这俩年里PHP中文圈里 ,越来越多的人开始研究PHP源码, 也有越来越多的人开始从事PHP扩展开发.
关于PHP的执行原理, 我想我BLOG的读者都能脱口而出:
1. 词法分析, 去掉注释, 空白, 得到TOKEN 2. 语法分析, 在这个过程中生成Opcode array (op_array) 3. 解释执行, 执行op_array, 一条一条的解释执行Opline(SWITCH, CALL, GOTO)
今天有人问我, 说他看到有PHPer在说unless语句, 我很是纳闷, 后来才知道, 原来是一个国外的PHP大牛自己Hack了PHP的源码, 加入了一个unless语句.
很有意思, 今天, 我也就在这里为大家演示, 如何为我们自己的PHP加入unless语句..
如果你是不了解PHP的执行过程, 请先花点时间看看我之前的文章深入理解PHP原理之Opcodes:
我们的目标, 是要实现如下的语法(以php 5.2.11为基础):
<?php unless(TRUE) { //这不会被执行 } unless(FALSE) { //这会被执行 }
好得, 看起来unless是if的反义词了.. 那么就好办了, if怎么来, 我们相反的来就可以了..
首先, 词法分析阶段, 我们需要添加unless的TOKEN定义, 编辑Zend/zend_language_scanner.l:
//添加unless的Token定义 <ST_IN_SCRIPTING>"unless" { return T_UNLESS; } <ST_IN_SCRIPTING>"if" { return T_IF; } <ST_IN_SCRIPTING>"elseif" { return T_ELSEIF; }
这样, 词法分析器遇到unless的时候, 就会报告发现了一个T_UNLESS Token.
接下来就需要在语法分析阶段, 定义T_UNLESS的语法动作了, 编辑Zend/zend_language_parse.y
unticked_statement: '{' inner_statement_list '}' | T_IF '(' expr ')' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); } statement { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); } //添加T_UNLESS的语法动作 | T_UNLESS '(' expr ')' { zend_do_unless_cond(&$3, &$4 TSRMLS_CC);} statement {zend_do_if_after_statement(&$4, 1 TSRMLS_CC);} {zend_do_if_end(TSRMLS_C)};
对的, 因为if和unless只是在条件的真假上不同, 所以我在if的基础上, 做了这个简单的hack, 接下来, 就应该定义zend_do_unless_cond了, 这个逻辑是用来生成OPCODE的. 还是从zend_do_if_cond为基础来做修改, Zend/zend_compile.c:
void zend_do_if_cond(znode *cond, znode *closing_bracket_token TSRMLS_DC) { int if_cond_op_number = get_next_op_number(CG(active_op_array)); zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = ZEND_JMPZ; //如果为0则跳转 opline->op1 = *cond; closing_bracket_token->u.opline_num = if_cond_op_number; //跳转地址 SET_UNUSED(opline->op2); INC_BPC(CG(active_op_array)); }
OK, 那么我们的zend_do_unless_cond就可以这样定义:
void zend_do_unless_cond(znode *cond, znode *closing_bracket_token TSRMLS_DC) { int if_cond_op_number = get_next_op_number(CG(active_op_array)); zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = ZEND_JMPNZ; //如果为不为0则跳转 opline->op1 = *cond; closing_bracket_token->u.opline_num = if_cond_op_number; SET_UNUSED(opline->op2); INC_BPC(CG(active_op_array)); }
注意上面的OPCODE, 我们把ZEND_JMPZ变成了JMPNZ…
大功告成, 现在需要重新编译PHP了:
cd Zend; rm zend_language_*.c; cd phpsrc_dir; make
编写测试脚本:
<?php unless(FALSE) { echo "Laruence"; }
运行…..
最后, 举的这个例子,相对来说是简单的, 因为有if可以参照, 有兴趣的同学, 可以自己玩玩, 用PHP实现其它语言中的各种语法~~
本文出自 传播、沟通、分享,转载时请注明出处及相应链接。
本文永久链接: https://www.nickdd.cn/?p=889