根据文法画出语法树_PHP7 的抽象语法树(AST)带来的变化
点击蓝字关注我们!每天获取最新的编程小知识!源 /php中文网 源 /www.php.cnPHP7 的抽象语法树(AST)带来的变化(查看原文请点击本文末尾左下角:什么是抽象语法树?抽象语法树(abstract syntaxtree,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出...
点击蓝字关注我们!每天获取最新的编程小知识!
源 / php中文网 源 / www.php.cn
PHP7 的抽象语法树(AST)带来的变化
(查看原文请点击本文末尾左下角:
什么是抽象语法树?
抽象语法树(abstract syntax tree,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。
抽象语法树并不依赖于源语言的语法,也就是说语法分析阶段所采用的上下文无文法【文法是用于描述语言的语法结构的形式规则。任何一种语言都有它自己的文法,不管它是机器语言还是自然语言。】,因为在写文法时,经常会对文法进行等价的转换(消除左递归,回溯,二义性等),这样会给文法分析引入一些多余的成分,对后续阶段造成不利影响,甚至会使合个阶段变得混乱。因些,很多编译器经常要独立地构造语法分析树,为前端,后端建立一个清晰的接口
PHP-Parser的项目主页是https://github.com/nikic/PHP-Parser。可以对多版本的PHP进行完美解析,生成一颗抽象语法树。
新的执行过程
PHP7 的内核中有一个重要的变化是加入了 AST。在 PHP5中,从 php 脚本到 opcodes 的执行的过程是:
1.Lexing:词法扫描分析,将源文件转换成 token 流;
2.Parsing:语法分析,在此阶段生成 op arrays。
PHP7 中在语法分析阶段不再直接生成 op arrays,而是先生成 AST,所以过程多了一步:
1.Lexing:词法扫描分析,将源文件转换成 token 流;
2.Parsing:语法分析,从 token 流生成抽象语法树;
3.Compilation:从抽象语法树生成 op arrays。
执行时间和内存消耗
从以上的步骤来看,这比之前的过程还多了一步,所以按常理来说这反而会增加程序的执行时间和内存的使用。但事实上内存的使用确实增加了,但是执行时间上却有所降低。
以下结果是使用小(代码大约 100 行)、中(大约 700 行)、大(大约 2800 行)三个脚本分别进行测试得到的,测试脚本: https://gist.github.com/nikic/289b0c7538b46c2220bc.
每个文件编译 100 次的执行时间(注意文章的测试结果时间是 14 年,PHP7 还叫 PHP-NG 的时候):
单次编译中的内存峰值: 单次编译的测试结果可能并不能代表实际使用的情况,以下是使用 PhpParser 进行完整项目测试得到的结果:测试表明,使用 AST 之后程序的执行时间整体上大概有 10% 到 15% 的提升,但是内存消耗也有增加,在大文件单次编译中增加明显,但是在整个项目执行过程中并不是很严重的问题。
还有注意的是以上的结果都是在没有 Opcache 的情况下,生产环境中打开 Opcache 的情况下,内存的消耗增加也不是很大的问题。
语义上的改变
如果仅仅是时间上的优化,似乎也不是使用 AST 的充足理由。其实实现 AST 并不是基于时间优化上的考虑,而是为了解决语法上的问题。下面来看一下语义上的一些变化。
yield 不需要括号
在 PHP5 的实现中,如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用 yield,你必须在 yield 申明两边使用括号:
$result
= yield fn();
// 不合法的
$result
= (yield fn());
// 合法的
$result
= yield;
$result
= yield
$v
;
$result
= yield
$k
=>
$v
;
当然了,还得遵循 yield 的应用场景才行。
括号不影响行为
在 PHP5 中,
(
$foo
)[
'bar'
] =
'baz'
;
# PHP Parse error: Syntax error, unexpected
'['
on line 1
但是在 PHP7 中,两种写法表示同样的意思。
同样,如果函数的参数被括号包裹,类型检查存在问题,在 PHP7 中这个问题也得到了解决:
function
func() {
return
[];
}
function
byRef(
array
&
$a
) {
}
byRef((func()));
PHP Strict standards: Only variables should be passed by reference ...
list() 的变化
list 关键字的行为改变了很多。list 给变量赋值的顺序(等号左右同时的顺序)以前是从右至左,现在是从左到右:
list(
$array
[],
$array
[],
$array
[]) = [1, 2, 3];
var_dump(
$array
);
// PHP5: $array = [3, 2, 1]
// PHP7: $array = [1, 2, 3]
# 注意这里的左右的顺序指的是等号左右同时的顺序,
# list(
$a
,
$b
) = [1, 2] 这种使用中
$a
== 1,
$b
== 2 是没有疑问的。
产生上面变化的原因正是因为在 PHP5 的赋值过程中,3 会最先被填入数组,1 最后,但是现在顺序改变了。
同样的变化还有:
$a
= [1, 2];
list(
$a
,
$b
) =
$a
;
// PHP5: $a = 1, $b = 2
// PHP7: $a = 1, $b = null + "Undefined index 1"
这是因为在以前的赋值过程中 $b 先得到 2,然后 $a 的值才变成1,但是现在 $a 先变成了 1,不再是数组,所以 $b 就成了null。
list 现在只会访问每个偏移量一次
list(list(
$a
,
$b
)) =
$array
;
// PHP5:
$b
=
$array
[0][1];
$a
=
$array
[0][0];
// PHP7:
// 会产生一个中间变量,得到 $array[0] 的值
$_tmp
=
$array
[0];
$a
=
$_tmp
[0];
$b
=
$_tmp
[1];
list() =
$a
;
// 不合法
list(
$b
, list()) =
$a
;
// 不合法
foreach
(
$a
as
list())
// 不合法 (PHP5 中也不合法)
引用赋值的顺序
引用赋值的顺序在 PHP5 中是从右到左的,现在时从左到右:
$obj
=
new
stdClass;
$obj
->a = &
$obj
->b;
$obj
->b = 1;
var_dump(
$obj
);
// PHP5:
object(stdClass)#1 (2) {
[
"b"
] => &int(1)
[
"a"
] => &int(1)
}
// PHP7:
object(stdClass)#1 (2) {
[
"a"
] => &int(1)
[
"b"
] => &int(1)
}
__clone 方法可以直接调用
现在可以直接使用 $obj->__clone() 的写法去调用 __clone 方法。 __clone 是之前唯一一个被禁止直接调用的魔术方法,之前你会得到一个这样的错误:
Fatal error:Cannot call __clone() method on objects -
use
'clone $obj'
instead in...
变量语法一致性
AST 也解决了一些语法一致性的问题,这些问题是在另外一个 RFC 中被提出的:https://wiki.php.net/rfc/uniform_variable_syntax.
在新的实现上,以前的一些语法表达的含义和现在有些不同,具体的可以参照下面的表格:
整体上还是以前的顺序是从右到左,现在从左到右,同时也遵循括号不影响行为的原则。这些复杂的变量写法是在实际开发中需要注意的。-END-
声明:本文选自「 php中文网 」,搜索「 phpcnnew 」即可关注!
▼
更多推荐
所有评论(0)