C0reFast记事本

to inspire confidence in somebody.

0%

PHP引擎实现(二)

上一次说到Zend的词法分析,现在该轮到语法分析和中间代码生成部分了。一般情况下,词法分析和语法分析是在一起的过程,所以一般词法分析器和语法分析器是交织在一起的,共同运行。
PHP的语法分析器使用的是Bison。具体的语法分析器定义在 Zend/zend_language_parser.y文件中。

在文件中,我们可以比较容易的找到针对 T_ECHO的语法规则:

1
2
3
...
| T_ECHO echo_expr_list ';'
...

即必须满足T_ECHO + 一个echo_expr_list,后面加;的语法,如果不满足,则直接就会报错了。我们还能继续找到echo_expr_list的定义:

1
2
3
4
echo_expr_list:
echo_expr_list ',' expr { zend_do_echo(&$3 TSRMLS_CC); }
| expr { zend_do_echo(&$1 TSRMLS_CC); }
;

这里我们看到了一个递归的定义,即一个 echo_expr_list 可以包含一个 echo_expr_list + , + expr(表达式) 或者直接就是一个expr(表达式),当只有一个表达式时,就直接调用zend_do_echo(),并将第一个参数(也就是expr的值)传给该函数,如果是递归的,也是一样,先递归调用,然后再将第三个参数也是就是,后面的expr传给zend_do_echo()

在Bison中,$$代表规则的结果,$1代表规则的第一个值,$2代表第二个,依次类推。

下面很自然的就要开始看一下 zend_do_echo()这个函数是干什么的了,这个函数的实现在Zend/zend_compile.c文件中:

1
2
3
4
5
6
7
8
9
void zend_do_echo(const znode *arg TSRMLS_DC) /* {{{ */
{
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

opline->opcode = ZEND_ECHO;
SET_NODE(opline->op1, arg);
SET_UNUSED(opline->op2);
}
/* }}} */

这个函数很简单,首先,从CG(active_op_array)中找到下一个opline,然后将这个OP的opcode设置为ZEND_ECHO,opline的第一个参数设置为传入的arg,设置第二个参数为UNUSED,也就是说,ZEND_ECHO这个OP,实际执行的时候,只会用到一个参数。

到目前为止。PHP编译部分算是基本理清了,语法分析器和词法分析器互相配合,从第一行开始,不断地将代码转化为一个个的opline存到CG(active_op_array)中,等到编译完成,再从CG(active_op_array)中一个个取出OP,然后执行。对应到实际的计算机,编译完成后的CG(active_op_array)就好比是内存中的代码段,在执行中也有个类似PC寄存器的指针指向这个代码段,然后不停的执行当前的OP,直到所有的OP全部执行完成推出。当然执行部分,是下面要说的东西了。

现在在回头看一下上篇说的那个例子:

1
2
3
4
<?php
echo 'Hello ' . 'World';
echo 'Hello ', 'World';
?>

编译后的结果。

1
2
3
4
5
6
7
line   #* E I O op    fetch  ext  return  operands
---------------------------------------------------------
2 0 E > CONCAT ~0 'Hello+', 'World'
1 ECHO ~0
3 2 ECHO 'Hello+'
3 ECHO 'World'
5 4 > RETURN 1

同样是echo,当由,分隔的两个expr时,生成了2个ECHO OP,而中间用.操作符连接的两个字符串,是先通过CONCAT连接后,再调用一次ECHO输出,最后,调用RETURN 返回返回值。

参考:

  1. http://www.php-internals.com/book/
  2. http://www.gnu.org/software/bison/manual/html_node/Action-Features.html#Action-Features
Chen Fu wechat
新注册了公众号,同步更新,长按关注,可以第一时间收到推送