TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
8.2 KiB
239 lines
8.2 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP.
|
|
*
|
|
* Lowers PHP array literals, dimensions, writable targets, and mixed array initialization.
|
|
*/
|
|
|
|
namespace TypePhp\Parser;
|
|
|
|
use TypePhp\Type;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\NodeAbstract;
|
|
|
|
trait ArrayExpressionTrait
|
|
{
|
|
protected function parseArray(Expr\Array_ $node): string
|
|
{
|
|
$items = $node->items;
|
|
// 优化代码风格,空数组直接返回{},否则会产生一些空洞内容
|
|
if (count($items) === 0) {
|
|
return Type::ARRAY . '{}';
|
|
}
|
|
|
|
$hasKey = false;
|
|
$hasIntKey = false;
|
|
$hasStrKey = false;
|
|
$hasUnpack = false;
|
|
$hasVarKey = false;
|
|
$hasNextInsert = false;
|
|
$hasReference = false;
|
|
foreach ($items as $item) {
|
|
if ($item->unpack) {
|
|
$hasUnpack = true;
|
|
}
|
|
if ($item->byRef) {
|
|
$hasReference = true;
|
|
}
|
|
if ($item->key) {
|
|
if ($item->key instanceof Node\Scalar\LNumber) {
|
|
$hasIntKey = true;
|
|
} elseif ($item->key instanceof Node\Scalar\String_) {
|
|
$hasStrKey = true;
|
|
} else {
|
|
$hasVarKey = true;
|
|
}
|
|
$hasKey = true;
|
|
} else {
|
|
$hasNextInsert = true;
|
|
}
|
|
}
|
|
|
|
// 存在混合键,则需要拆分为多行插入
|
|
if ($hasReference or $hasUnpack or $hasVarKey or ($hasNextInsert && $hasKey) or ($hasIntKey and $hasStrKey)) {
|
|
return $this->parseArrayMixed($node);
|
|
}
|
|
|
|
$list = [];
|
|
$this->indentLevel++;
|
|
foreach ($items as $item) {
|
|
$this->assertExprCanBeUsedAsValue($item->value, 'array value');
|
|
$value = $this->parseIdentifier($item->value);
|
|
if ($item->key) {
|
|
$this->assertExprCanBeUsedAsValue($item->key, 'array key');
|
|
$key = $this->parseArrayKey($item->key);
|
|
$list[] = $this->getIndent() . '{ ' . $key . ', ' . Type::VAR . '(' . $value . ') }';
|
|
} else {
|
|
$list[] = $this->getIndent() . Type::VAR . '(' . $value . ')';
|
|
}
|
|
}
|
|
$this->indentLevel--;
|
|
|
|
return Type::ARRAY . '{' . PHP_EOL .
|
|
implode(', ' . PHP_EOL, $list) . PHP_EOL .
|
|
$this->getIndent() .
|
|
'}';
|
|
}
|
|
|
|
/**
|
|
* 获取包含路径
|
|
*/
|
|
|
|
protected function parseGlobalsArrayDimFetch(Expr\ArrayDimFetch $node): string
|
|
{
|
|
if ($node->dim === null) {
|
|
$this->fatalError($node, 'Cannot use [] for GLOBALS');
|
|
}
|
|
if ($this->isScalarString($node->dim)) {
|
|
$name = $node->dim->value;
|
|
if (!$this->hasGlobalVar($name)) {
|
|
$this->addGlobalVar($name, Type::VAR);
|
|
}
|
|
if (!$this->hasScopeGlobalVar($name)) {
|
|
$this->addScopeGlobalVar($name, Type::VAR);
|
|
}
|
|
return $name;
|
|
}
|
|
return 'php::global(' . $this->parseIdentifier($node->dim) . ')';
|
|
}
|
|
|
|
protected function parseWritableIdentifier(NodeAbstract $expr): string
|
|
{
|
|
if ($expr instanceof Expr\ArrayDimFetch) {
|
|
return $this->parseArrayDimFetchUpdate($expr);
|
|
}
|
|
|
|
if ($expr instanceof Expr\PropertyFetch) {
|
|
return $this->parsePropertyFetchUpdate($expr);
|
|
}
|
|
|
|
if ($expr instanceof Expr\NullsafePropertyFetch) {
|
|
return $this->parseNullsafePropertyFetchUpdate($expr);
|
|
}
|
|
|
|
return $this->parseIdentifier($expr);
|
|
}
|
|
|
|
protected function parseNodeWithUpdateAttribute(NodeAbstract $node, string $attribute, bool $update, callable $parser): string
|
|
{
|
|
$hadAttribute = $node->hasAttribute($attribute);
|
|
$previousValue = $node->getAttribute($attribute);
|
|
$node->setAttribute($attribute, $update);
|
|
try {
|
|
return $parser();
|
|
} finally {
|
|
if ($hadAttribute) {
|
|
$node->setAttribute($attribute, $previousValue);
|
|
} else {
|
|
$attributes = $node->getAttributes();
|
|
unset($attributes[$attribute]);
|
|
$node->setAttributes($attributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function parseArrayDimFetchRead(Expr\ArrayDimFetch $node): string
|
|
{
|
|
return $this->parseArrayDimFetchWithUpdate($node, false);
|
|
}
|
|
|
|
protected function parseArrayDimFetchUpdate(Expr\ArrayDimFetch $node): string
|
|
{
|
|
return $this->parseArrayDimFetchWithUpdate($node, true);
|
|
}
|
|
|
|
protected function parseArrayDimFetchWithUpdate(Expr\ArrayDimFetch $node, bool $update): string
|
|
{
|
|
return $this->parseNodeWithUpdateAttribute(
|
|
$node,
|
|
self::ATTR_ARRAY_DIM_FETCH_UPDATE,
|
|
$update,
|
|
fn() => $this->parseArrayDimFetch($node)
|
|
);
|
|
}
|
|
|
|
protected function isArrayDimFetchUpdate(Expr\ArrayDimFetch $node): bool
|
|
{
|
|
return $node->getAttribute(self::ATTR_ARRAY_DIM_FETCH_UPDATE, false) === true;
|
|
}
|
|
|
|
protected function parseArrayDimFetch(Expr\ArrayDimFetch $node): string
|
|
{
|
|
$write = $this->isArrayDimFetchUpdate($node);
|
|
if ($this->isStdContainerExpr($node)) {
|
|
if ($write && $node->dim === null) {
|
|
return $this->parseIdentifier($node->var);
|
|
}
|
|
return $this->parseStdContainerDimFetch($node);
|
|
}
|
|
|
|
$var = $write ? $this->parseWritableIdentifier($node->var) : $this->parseIdentifier($node->var);
|
|
if ($this->isVarExpr($node->var)) {
|
|
if ($var === 'GLOBALS') {
|
|
return $this->parseGlobalsArrayDimFetch($node);
|
|
}
|
|
if (!$this->hasVar($var)) {
|
|
if ($write) {
|
|
$this->addLocalVar($var, Type::ARRAY);
|
|
} else {
|
|
$this->errorUndefinedVariable($node->var);
|
|
}
|
|
} else {
|
|
$type = $this->getVarType($var);
|
|
if ($type === Type::BOOL || $type === Type::INT || $type === Type::FLOAT) {
|
|
$this->fatalError($node, 'Cannot use [] for numbers');
|
|
}
|
|
}
|
|
if ($this->getVarType($var) === Type::STR) {
|
|
if ($node->dim === null) {
|
|
$this->fatalError($node, 'Cannot use [] for strings');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($node->dim === null) {
|
|
if (!$write) {
|
|
$this->fatalError($node, 'Cannot use [] for reading');
|
|
} else {
|
|
return $var . '.newItem()';
|
|
}
|
|
} else {
|
|
$dim = $this->parseIdentifier($node->dim);
|
|
return $var . '.item(' . $dim . ', ' . $this->escapeBool($write) . ')';
|
|
}
|
|
}
|
|
|
|
private function parseArrayMixed(Expr\Array_ $node): string
|
|
{
|
|
$tmpVar = $this->genTmpVarName();
|
|
$this->addLocalVar($tmpVar, Type::ARRAY);
|
|
// 释放临时变量,避免修改数组产生数组复制操作
|
|
$this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.clean();';
|
|
|
|
$items = $node->items;
|
|
foreach ($items as $item) {
|
|
$this->assertExprCanBeUsedAsValue($item->value, $item->unpack ? 'array unpack value' : 'array value');
|
|
if ($item->byRef) {
|
|
if ($item->unpack) {
|
|
$this->fatalError($item, 'Cannot unpack references in array literals');
|
|
}
|
|
$value = $this->convertToRef($item->value);
|
|
} else {
|
|
$value = $this->parseIdentifier($item->value);
|
|
}
|
|
if ($item->unpack) {
|
|
$this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.merge(' . $value . ');';
|
|
} elseif ($item->key) {
|
|
$this->assertExprCanBeUsedAsValue($item->key, 'array key');
|
|
$key = $this->parseArrayKey($item->key);
|
|
$this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.set(' . $key . ', ' . $value . ');';
|
|
} else {
|
|
$this->context->beforeStmtLines[] = $this->getIndent() . $tmpVar . '.append(' . $value . ');';
|
|
}
|
|
}
|
|
|
|
return $tmpVar;
|
|
}
|
|
}
|
|
|