Skip to content

Commit c6983b6

Browse files
committed
add defer
1 parent 70108cd commit c6983b6

File tree

3 files changed

+24
-1
lines changed

3 files changed

+24
-1
lines changed

img/defer.png

19.7 KB
Loading

try/break.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ for(...){
7979
__(1) 编译循环语句__
8080

8181
```c
82-
void zend_compile_for(zend_ast *ast) /* {{{ */
82+
void zend_compile_for(zend_ast *ast)
8383
{
8484
zend_ast *init_ast = ast->child[0];
8585
zend_ast *cond_ast = ast->child[1];

try/defer.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# 附录2:defer推迟执行语法的实现
2+
3+
使用过Go语言的应该都知道defer这个语法,它用来推迟一个函数的执行,在函数执行返回前首先检查当前函数内是否有推迟执行的函数,如果有则执行,然后再返回。defer是一个非常有用的语法,这个功能可以很方便的在函数结束前执行一些清理工作,比如关闭打开的文件、关闭连接、释放资源、解锁等等。这样延迟一个函数有以下两个好处:
4+
5+
* (1) 靠近使用位置,避免漏掉清理工作,同时比放在函数结尾要清晰
6+
* (2) 如果有多处返回的地方可以避免代码重复,比如函数中有很多处return
7+
8+
在一个函数中可以使用多个defer,其执行顺序与栈类似:后进先出,先定义的defer后执行。另外,在返回之后定义的defer将不会被执行,只有返回前定义的才会执行,通过exit退出程序的情况也不会执行任何defer。
9+
10+
在PHP中并没有实现类似的语法,接下来我们介绍下如何在PHP中实现类似Go语言中defer的功能。此功能的实现需要对PHP的语法解析、抽象语法树/opcode的编译、opcode指令的执行等环节进行改造,涉及的地方比较多,但是改动点比较简单,可以很好的帮助大家完整的理解PHP编译、执行两个核心阶段的实现。整体实现思路:
11+
12+
* (1) 语法解析:defer本质上还是函数调用,只是将调用时机移到了函数的最后,所以编译时可以复用调用函数的规则,但是需要与普通的调用区分开,所以我们新增一个AST节点类型,其子节点为为正常函数调用编译的AST,语法我们定义为:`defer function_name()`;
13+
* (2) AST编译为opcode:编译opcode时也复用调用函数的编译逻辑,不同的地方在于把defer放在最后编译,另外需要在编译return前新增一条opcode,用于执行return前跳转到defer开始的位置,在defer的最后也需要新增一条opcode,用于执行完defer后跳回return的位置;
14+
* (3) 执行阶段:执行时如果发现是return前新增的opcode则跳转到defer开始的位置,同时把return的位置记录下来,执行完defer后再跳回return。
15+
16+
编译后的opcode指令如下图所示:
17+
18+
![](../img/defer.png)
19+
20+
1、修改词法解析文件,将defer解析为token:T_DEFER
21+
2、修改语法解析文件,支持语法:T_DEFER function_call,解析为AST:ZEND_AST_DEFER_CALL
22+
3、编译ZEND_AST_DEFER_CALL时加入defer栈
23+
4、编译结束时继续编译defer栈中的function_call,最后编译一条ZEND_JMP,用于跳回return/exit/die的位置

0 commit comments

Comments
 (0)