diff --git a/src/CompilerBase.php b/src/CompilerBase.php index aef33d88..c8a9bd2a 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.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; - } } diff --git a/src/Parser/ArrayExpressionTrait.php b/src/Parser/ArrayExpressionTrait.php new file mode 100644 index 00000000..4a6728f2 --- /dev/null +++ b/src/Parser/ArrayExpressionTrait.php @@ -0,0 +1,231 @@ +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; + } +} +