refactor(parser): extract array expression handling to dedicated trait

- Move array parsing logic from CompilerBase to new ArrayExpressionTrait
- Extract globals array dimension fetch handling to the trait
- Separate writable identifier parsing into the trait
- Move array dimension fetch read/update logic to the trait
- Extract mixed array parsing functionality to the trait
- Add proper trait usage in CompilerBase class
pull/17/head
韩天峰 2 months ago
parent 516ec29dfa
commit 23d7f02117
  1. 380
      src/CompilerBase.php
  2. 231
      src/Parser/ArrayExpressionTrait.php

@ -36,6 +36,7 @@ use TypePhp\Optimizer\SsaTypeOptimizer;
use TypePhp\Optimizer\LoopVarOptimizer;
use TypePhp\Parser\StdContainerTrait;
use TypePhp\Parser\AssignOpTrait;
use TypePhp\Parser\ArrayExpressionTrait;
use TypePhp\Parser\BinaryOpTrait;
use TypePhp\Parser\ClassConstantFetchTrait;
use TypePhp\Parser\ConditionalControlTrait;
@ -114,6 +115,7 @@ class CompilerBase implements PropertyAccessContext
use TypeConversionTrait;
use TypeDetectionTrait;
use AssignOpTrait;
use ArrayExpressionTrait;
use UniversalMethodCall;
use Utils;
use TypeCheckGenerator;
@ -2851,233 +2853,6 @@ class CompilerBase implements PropertyAccessContext
return self::TYPE_VAR;
}
protected function parseArray(Expr\Array_ $node): string
{
$items = $node->items;
// 优化代码风格,空数组直接返回{},否则会产生一些空洞内容
if (count($items) === 0) {
return self::TYPE_ARRAY . '{}';
}
$hasKey = false;
$hasIntKey = false;
$hasStrKey = false;
$hasUnpack = false;
$hasVarKey = false;
$hasNextInsert = false;
foreach ($items as $item) {
if ($item->unpack) {
$hasUnpack = 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 ($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 . ', ' . self::TYPE_VAR . '(' . $value . ') }';
} else {
$list[] = $this->getIndent() . self::TYPE_VAR . '(' . $value . ')';
}
}
$this->indentLevel--;
return self::TYPE_ARRAY . '{' . PHP_EOL .
implode(', ' . PHP_EOL, $list) . PHP_EOL .
$this->getIndent() .
'}';
}
/**
* 获取包含路径
*/
protected function getIncludePaths(): array
{
$platform = $this->getPlatform();
$includePaths = [
$this->getPhpxDir() . '/include',
$this->getBuildDir() . '/include',
$this->getPhpxDir() . '/src/misc',
];
// 根据平台添加 PHP 包含路径
if ($platform instanceof Windows) {
$phpSdkPaths = $platform->buildPhpSdkIncludePaths($this->getPhpDir());
$includePaths = array_merge($includePaths, $phpSdkPaths);
} else {
// Linux/macOS
$phpPaths = $platform->buildPhpIncludePaths($this->getPhpDir());
$includePaths = array_merge($includePaths, $phpPaths);
// 内置 mpdecimal 头文件目录
$includePaths[] = $this->getPhpxDir() . '/thirdparty/mpdecimal/libmpdec';
$includePaths[] = $this->getPhpxDir() . '/thirdparty/mpdecimal/libmpdec++';
}
return $includePaths;
}
/**
* 解析包含路径
*/
protected function parseIncludes(): string
{
return $this->getPlatform()->getIncludeFlags($this->getIncludePaths());
}
protected function getLibraryPaths(): array
{
$platform = $this->getPlatform();
$libraryPaths = [
$this->getPhpxDir() . '/lib',
];
// 根据平台添加 PHP 库路径
if ($platform instanceof Windows) {
$phpLibPaths = $platform->buildPhpSdkLibPaths($this->getPhpDir());
$libraryPaths = array_merge($libraryPaths, $phpLibPaths);
} else {
// Linux/macOS
$phpLibPaths = $platform->buildPhpLibPaths($this->getPhpDir());
$libraryPaths = array_merge($libraryPaths, $phpLibPaths);
}
return $libraryPaths;
}
protected function parseLdflags(): string
{
$flags = $this->getPlatform()->getLibraryPathFlags($this->getLibraryPaths());
// 添加用户自定义的 ldflags
if (!empty($this->ldflags)) {
$flags .= ' ' . $this->ldflags;
}
return $flags;
}
/**
* 获取库文件
*/
protected function getLibraries(): array
{
$platform = $this->getPlatform();
$libraries = [];
// phpx 库(根据平台使用不同的文件名格式)
if ($platform instanceof Windows) {
// Windows: phpx.lib (无 lib 前缀)
$phpxLibPath = $this->getPhpxDir() . '\\lib\\phpx.lib';
if (file_exists($phpxLibPath)) {
$libraries[] = $phpxLibPath; // 不添加引号,由 getLibraryFlags() 统一处理
} else {
$this->error('phpx.lib not found at: ' . $phpxLibPath);
}
} else {
// Linux/macOS: libphpx.so 或 libphpx.a
$sharedLibExt = $platform->getSharedLibraryExtension();
// getSharedLibraryExtension() 返回的值可能带点或不带点,需要统一处理
$extWithoutDot = ltrim($sharedLibExt, '.');
$phpxLibPath = $this->getPhpxDir() . '/lib/libphpx.' . $extWithoutDot;
if (file_exists($phpxLibPath)) {
$libraries[] = $phpxLibPath;
} else {
// 尝试静态库
$phpxStaticPath = $this->getPhpxDir() . '/lib/libphpx.a';
if (file_exists($phpxStaticPath)) {
$libraries[] = $phpxStaticPath;
} else {
$this->error('libphpx library not found');
}
}
}
// extension 和 bin 模式都需要链接 PHP 库
if ($platform instanceof Windows) {
// Windows: 根据构建模式选择不同的库
if ($this->isBuildModeEmbed()) {
// bin 模式:需要同时链接 php8ts.lib 和 php8embed.lib
// 注意:php8ts.lib 必须在 php8embed.lib 之前,因为 embed 依赖 core
// php8ts.lib 提供 PHP 核心全局符号(executor_globals, compiler_globals, sapi_globals)
if (!empty($this->windowsPhpCoreLib)) {
$libraries[] = $this->windowsPhpCoreLib; // 不添加引号
}
// php8embed.lib 提供嵌入 API
if (!empty($this->windowsPhpEmbedLib)) {
$libraries[] = $this->windowsPhpEmbedLib; // 不添加引号
}
} else {
// ext 模式:只使用 php8ts.lib 或 php8.lib(PHP 扩展)
if (!empty($this->windowsPhpCoreLib)) {
$libraries[] = $this->windowsPhpCoreLib; // 不添加引号
}
}
// 添加 Windows API 库(Win32 GUI 程序需要)
$libraries[] = 'user32.lib'; // Windows UI 函数(CreateWindow, MessageBox 等)
$libraries[] = 'gdi32.lib'; // GDI 图形函数
$libraries[] = 'kernel32.lib'; // 核心 Windows API
$libraries[] = 'gmp.lib';
$libraries[] = 'gmpxx.lib';
$libraries[] = 'mpfr.lib';
$libraries[] = 'libmpdec-4.0.1.dll.lib';
$libraries[] = 'libmpdec++-4.0.1.dll.lib';
} else {
// Linux/macOS: extension 和 bin 模式都需要添加 php 库
$libraries[] = 'php';
$libraries[] = 'gmp';
$libraries[] = 'gmpxx';
$libraries[] = 'mpfr';
}
return $libraries;
}
/**
* 解析库文件
*/
protected function parseLibs(): string
{
return $this->getPlatform()->getLibraryFlags($this->getLibraries());
}
protected function getTargetFileName(): string
{
$targetFile = $this->targetName;
$extension = $this->getPlatform()->getTargetExtension($this->buildMode);
if ($extension !== '' && !str_ends_with($targetFile, $extension)) {
$targetFile .= $extension;
}
if ($this->outputDir !== '') {
$targetFile = rtrim($this->outputDir, '/\\') . '/' . $targetFile;
}
return $targetFile;
}
protected function genDynamicPropIncDec($var, string $op, bool $isPre): ?string
{
if (!$this->isPropertyFetch($var)) {
@ -3185,133 +2960,6 @@ class CompilerBase implements PropertyAccessContext
* $GLOBALS['var'] 等价于 global $var; $var ,将字符串常量转为变量名称即可
* 仅限于字面量字符串可以转为变量名称,其他则使用 php::global() 函数获取
*/
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, self::TYPE_VAR);
}
if (!$this->hasScopeGlobalVar($name)) {
$this->addScopeGlobalVar($name, self::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, self::TYPE_ARRAY);
} else {
$this->errorUndefinedVariable($node->var);
}
} else {
$type = $this->getVarType($var);
if ($type === self::TYPE_BOOL || $type === self::TYPE_INT || $type === self::TYPE_FLOAT) {
$this->fatalError($node, 'Cannot use [] for numbers');
}
}
if ($this->getVarType($var) === self::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) . ')';
}
}
/**
* 查找原生函数.
*/
protected function findNativeFunction(string $funcName): string|false
{
// 绝对命名空间的函数
@ -5034,28 +4682,4 @@ class CompilerBase implements PropertyAccessContext
/**
* 混杂数组赋值,需要拆分为多行插入
*/
private function parseArrayMixed(Expr\Array_ $node): string
{
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::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');
$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;
}
}

@ -0,0 +1,231 @@
<?php
/**
* This file is part of TypePHP.
*
* Lowers PHP array literals, dimensions, writable targets, and mixed array initialization.
*/
namespace TypePhp\Parser;
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 self::TYPE_ARRAY . '{}';
}
$hasKey = false;
$hasIntKey = false;
$hasStrKey = false;
$hasUnpack = false;
$hasVarKey = false;
$hasNextInsert = false;
foreach ($items as $item) {
if ($item->unpack) {
$hasUnpack = 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 ($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 . ', ' . self::TYPE_VAR . '(' . $value . ') }';
} else {
$list[] = $this->getIndent() . self::TYPE_VAR . '(' . $value . ')';
}
}
$this->indentLevel--;
return self::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, self::TYPE_VAR);
}
if (!$this->hasScopeGlobalVar($name)) {
$this->addScopeGlobalVar($name, self::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, self::TYPE_ARRAY);
} else {
$this->errorUndefinedVariable($node->var);
}
} else {
$type = $this->getVarType($var);
if ($type === self::TYPE_BOOL || $type === self::TYPE_INT || $type === self::TYPE_FLOAT) {
$this->fatalError($node, 'Cannot use [] for numbers');
}
}
if ($this->getVarType($var) === self::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, self::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');
$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;
}
}
Loading…
Cancel
Save