PHP引擎实现(二)
上一次说到Zend的词法分析,现在该轮到语法分析和中间代码生成部分了。一般情况下,词法分析和语法分析是在一起的过程,所以一般词法分析器和语法分析器是交织在一起的,共同运行。
PHP的语法分析器使用的是Bison
。具体的语法分析器定义在 Zend/zend_language_parser.y
文件中。
在文件中,我们可以比较容易的找到针对 T_ECHO
的语法规则:
...
| T_ECHO echo_expr_list ';'
...
即必须满足T_ECHO
+ 一个echo_expr_list
,后面加;
的语法,如果不满足,则直接就会报错了。我们还能继续找到echo_expr_list
的定义:
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
文件中:
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全部执行完成推出。当然执行部分,是下面要说的东西了。
现在在回头看一下上篇说的那个例子:
<?php
echo 'Hello ' . 'World';
echo 'Hello ', 'World';
?>
编译后的结果。
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
返回返回值。
参考: