diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 6bc2dadb..9aad82cf 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -70,6 +70,9 @@ class CompilerBase extends \PhpAot\Core\Translator public const string TYPE_ARRAY = 'php::Array'; public const string TYPE_RESOURCE = 'php::Resource'; public const string TYPE_STREAM = 'php::Stream'; + public const string TYPE_BIGINT = 'php::BigInt'; + public const string TYPE_DECIMAL = 'php::Decimal'; + public const string TYPE_BIGFLOAT = 'php::BigFloat'; private const array STREAM_FUNCTIONS = [ 'fopen', @@ -296,7 +299,10 @@ class CompilerBase extends \PhpAot\Core\Translator protected Parser $parser; protected PrettyPrinter $printer; protected bool $isPhpZts = false; // PHP 是否为线程安全版本 - + protected bool $usesBigInt = false; + protected bool $usesDecimal = false; + protected bool $usesBigFloat = false; + // Windows 平台:保存检测到的 PHP lib 文件路径 protected string $windowsPhpEmbedLib = ''; // php8embed.lib 路径 protected string $windowsPhpCoreLib = ''; // php8ts.lib 或 php8.lib 路径 @@ -1193,6 +1199,14 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Scalar_Int': return $expr->value . $this->getPlatform()->getIntegerLiteralSuffix(); case 'Scalar_Float': + if ($this->isBigIntLiteral($expr)) { + $this->usesBigInt = true; + return 'php::newBigInt(' . $this->getLiteralString($this->getBigIntLiteralString($expr)) . ')'; + } + if ($this->isDecimalLiteral($expr)) { + $this->usesDecimal = true; + return 'php::newDecimal(' . $this->getLiteralString($this->getDecimalLiteralString($expr)) . ')'; + } return $this->parseScalarFloat($expr); case 'Scalar_String': return $expr->hasAttribute('noLiteralString') ? $this->genCharPtr($expr->value, true) : $this->getLiteralString($expr->value); @@ -1202,6 +1216,57 @@ class CompilerBase extends \PhpAot\Core\Translator } } + /** + * Check if a numeric literal's rawValue represents an integer that exceeds int64 range. + * PHP's parser converts such literals to float (Scalar_Float) when they overflow. + */ + private function isBigIntLiteral(Node\Scalar $expr): bool + { + $rawValue = $expr->getAttribute('rawValue'); + if ($rawValue === null) { + return false; + } + // Remove underscores + $clean = str_replace('_', '', $rawValue); + // Must look like a decimal integer (no dot, no hex/oct/bin prefix, all digits) + if (!preg_match('/^\d+$/', $clean)) { + return false; + } + // 19+ decimal digits exceed int64 range + return strlen(ltrim($clean, '0')) >= 19; + } + + /** + * Check if a Scalar_Float literal should be treated as Decimal. + * Only "long" floats (>= 16 significant digits) that would lose precision + * as native PHP float (double) are auto-converted. + */ + private function isDecimalLiteral(Node\Scalar $expr): bool + { + $rawValue = $expr->getAttribute('rawValue'); + if ($rawValue === null) { + return false; + } + $clean = str_replace('_', '', $rawValue); + // Must have a decimal point or exponent (not a pure integer) + if (!preg_match('/[\.eE]/', $clean)) { + return false; + } + // Count significant digits (exclude ., e, E, +, -) + $digits = preg_replace('/[^0-9]/', '', $clean); + return strlen(ltrim($digits, '0')) >= 16; + } + + private function getBigIntLiteralString(Node\Scalar $expr): string + { + return str_replace('_', '', $expr->getAttribute('rawValue')); + } + + private function getDecimalLiteralString(Node\Scalar $expr): string + { + return str_replace('_', '', $expr->getAttribute('rawValue')); + } + protected function parseSuperGlobalVar(string $name): string { if (!$this->hasGlobalVar($name)) { @@ -1822,6 +1887,74 @@ class CompilerBase extends \PhpAot\Core\Translator $leftType = $this->detectTypeOfExpr($left); $rightType = $this->detectTypeOfExpr($right); + if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) { + // BigFloat cannot implicitly mix with BigInt or Decimal — risk of precision loss + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->fatalError($left, 'Cannot mix BigFloat and BigInt implicitly. Use std::bigFloat() to convert explicitly.'); + } + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + $this->fatalError($left, 'Cannot mix BigFloat and Decimal implicitly. Use std::bigFloat() to convert explicitly.'); + } + $this->usesBigFloat = true; + if ($leftType !== self::TYPE_BIGFLOAT) { + $leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGFLOAT) { + $rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType); + } + $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div']; + $method = $arithOpMap[$op] ?? null; + if ($method) { + return 'php::BigFloat::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; + } + $cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0']; + if (isset($cmpOpMap[$op])) { + return 'php::toBool(php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')'; + } + } + + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + // BigInt and Decimal cannot implicitly mix — risk of precision loss + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->fatalError($left, 'Cannot mix BigInt and Decimal implicitly. Use std::decimal() or std::bigInt() to convert explicitly.'); + } + $this->usesDecimal = true; + if ($leftType !== self::TYPE_DECIMAL) { + $leftExpr = $this->convertDecimalExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_DECIMAL) { + $rightExpr = $this->convertDecimalExpr($rightExpr, $rightType); + } + $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']; + $method = $arithOpMap[$op] ?? null; + if ($method) { + return 'php::Decimal::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; + } + $cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0']; + if (isset($cmpOpMap[$op])) { + return 'php::toBool(php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')'; + } + } + + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->usesBigInt = true; + if ($leftType !== self::TYPE_BIGINT) { + $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGINT) { + $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); + } + $arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']; + $method = $arithOpMap[$op] ?? null; + if ($method) { + return 'php::BigInt::' . $method . '(' . $leftExpr . ', ' . $rightExpr . ')'; + } + $cmpOpMap = ['<' => '< 0', '>' => '> 0', '<=' => '<= 0', '>=' => '>= 0']; + if (isset($cmpOpMap[$op])) { + return 'php::toBool(php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') ' . $cmpOpMap[$op] . ')'; + } + } + if ($leftType === self::TYPE_FLOAT) { $rightExpr = $this->convertExprType($rightExpr, self::TYPE_FLOAT, $rightType); } elseif ($rightType === self::TYPE_FLOAT) { @@ -2236,12 +2369,22 @@ class CompilerBase extends \PhpAot\Core\Translator { $exprType = $expr->getType(); switch ($exprType) { + case 'Expr_UnaryMinus': + return $this->detectTypeOfExpr($expr->expr); case 'Expr_Cast_Int': + return self::TYPE_INT; case 'Scalar_Int': return self::TYPE_INT; case 'Expr_Cast_Float': case 'Expr_Cast_Double': + return self::TYPE_FLOAT; case 'Scalar_Float': + if ($this->isBigIntLiteral($expr)) { + return self::TYPE_BIGINT; + } + if ($this->isDecimalLiteral($expr)) { + return self::TYPE_DECIMAL; + } return self::TYPE_FLOAT; case 'Expr_Cast_Bool': case 'Scalar_Bool': @@ -2263,6 +2406,19 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_BinaryOp_BooleanAnd': $leftType = $this->detectTypeOfExpr($expr->left); $rightType = $this->detectTypeOfExpr($expr->right); + if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) { + return self::TYPE_BIGFLOAT; + } + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + return self::TYPE_DECIMAL; + } + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + if ($exprType === 'Expr_BinaryOp_Div') { + // BigInt division produces BigInt (integer division); BigDecimal in future + return self::TYPE_BIGINT; + } + return self::TYPE_BIGINT; + } if ($leftType === self::TYPE_FLOAT || $rightType === self::TYPE_FLOAT) { return self::TYPE_FLOAT; } @@ -2333,6 +2489,15 @@ class CompilerBase extends \PhpAot\Core\Translator return self::TYPE_OBJECT; } $className = $this->parseIdentifier($expr->class); + if ($className === 'std') { + $method = $this->parseIdentifier($expr->name); + return match ($method) { + 'bigInt' => self::TYPE_BIGINT, + 'decimal' => self::TYPE_DECIMAL, + 'bigFloat' => self::TYPE_BIGFLOAT, + default => self::TYPE_VAR, + }; + } if ($className === 'self') { $className = $this->getFullClassName(); } elseif ($className === 'parent') { @@ -2620,6 +2785,23 @@ class CompilerBase extends \PhpAot\Core\Translator $libraries[] = 'php'; } + // GMP — BigInt arbitrary-precision arithmetic (only when actually used) + if ($this->usesBigInt && !$platform instanceof Windows) { + $libraries[] = 'gmp'; + $libraries[] = 'gmpxx'; + } + + // libmpdec — Decimal arbitrary-precision decimal (only when actually used) + if ($this->usesDecimal && !$platform instanceof Windows) { + $libraries[] = 'mpdec++'; + $libraries[] = 'mpdec'; + } + + // MPFR — BigFloat arbitrary-precision float (only when actually used) + if ($this->usesBigFloat && !$platform instanceof Windows) { + $libraries[] = 'mpfr'; + } + return $libraries; } @@ -3566,9 +3748,19 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpPow(Expr\BinaryOp\Pow $expr): string { + $leftType = $this->detectTypeOfExpr($expr->left); + if ($leftType === self::TYPE_BIGINT) { + $this->usesBigInt = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + $rightType = $this->detectTypeOfExpr($expr->right); + if ($rightType !== self::TYPE_BIGINT) { + $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); + } + return 'php::BigInt::pow(' . $leftExpr . ', ' . $rightExpr . ')'; + } $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'php::math::pow(' . $left . ', ' . $right . ')'; } @@ -3637,11 +3829,87 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpEqual(Expr\BinaryOp\Equal $expr): string { + $leftType = $this->detectTypeOfExpr($expr->left); + $rightType = $this->detectTypeOfExpr($expr->right); + if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) { + $this->usesBigFloat = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGFLOAT) { + $leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGFLOAT) { + $rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType); + } + return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0'; + } + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->usesBigInt = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGINT) { + $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGINT) { + $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); + } + return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0'; + } + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + $this->usesDecimal = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_DECIMAL) { + $leftExpr = $this->convertDecimalExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_DECIMAL) { + $rightExpr = $this->convertDecimalExpr($rightExpr, $rightType); + } + return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0'; + } return 'php::equals(' . $this->parseCompareExpr($expr->left) . ', ' . $this->parseCompareExpr($expr->right) . ')'; } protected function parseBinaryOpNotEqual(Expr\BinaryOp\NotEqual $expr): string { + $leftType = $this->detectTypeOfExpr($expr->left); + $rightType = $this->detectTypeOfExpr($expr->right); + if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) { + $this->usesBigFloat = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGFLOAT) { + $leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGFLOAT) { + $rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType); + } + return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0'; + } + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->usesBigInt = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGINT) { + $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGINT) { + $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); + } + return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0'; + } + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + $this->usesDecimal = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_DECIMAL) { + $leftExpr = $this->convertDecimalExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_DECIMAL) { + $rightExpr = $this->convertDecimalExpr($rightExpr, $rightType); + } + return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0'; + } return '!php::equals(' . $this->parseCompareExpr($expr->left) . ', ' . $this->parseCompareExpr($expr->right) . ')'; } @@ -3709,6 +3977,45 @@ class CompilerBase extends \PhpAot\Core\Translator return $expr; } + protected function convertDecimalExpr(string $expr, string $fromType = ''): string + { + if ($fromType === self::TYPE_INT || $fromType === self::TYPE_FLOAT) { + return 'php::newDecimal(php::toString(' . $this->trimBrackets($expr) . '))'; + } + if ($fromType === self::TYPE_BIGINT) { + return 'php::newDecimal(php::BigInt::toString(' . $this->trimBrackets($expr) . '))'; + } + return $expr; + } + + protected function convertBigIntExpr(string $expr, string $fromType = ''): string + { + if ($fromType === self::TYPE_INT) { + return 'php::newBigInt(' . $this->trimBrackets($expr) . ')'; + } + if ($fromType === self::TYPE_FLOAT) { + return 'php::newBigInt((php::Int)' . $this->trimBrackets($expr) . ')'; + } + return $expr; + } + + protected function convertBigFloatExpr(string $expr, string $fromType = ''): string + { + if ($fromType === self::TYPE_INT) { + return 'php::newBigFloat(' . $this->trimBrackets($expr) . ')'; + } + if ($fromType === self::TYPE_FLOAT) { + return 'php::newBigFloat(' . $this->trimBrackets($expr) . ')'; + } + if ($fromType === self::TYPE_BIGINT) { + return 'php::BigFloat::newInstance(php::BigInt::toString(' . $this->trimBrackets($expr) . '))'; + } + if ($fromType === self::TYPE_DECIMAL) { + return 'php::BigFloat::newInstance(php::Decimal::toString(' . $this->trimBrackets($expr) . '))'; + } + return $expr; + } + protected function convertStringExpr(string $expr): string { if (!$this->isClosedExpr($expr, 'php::toString')) { @@ -3777,9 +4084,46 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpSpaceship(Expr\BinaryOp\Spaceship $expr): string { + $leftType = $this->detectTypeOfExpr($expr->left); + $rightType = $this->detectTypeOfExpr($expr->right); + if ($leftType === self::TYPE_BIGFLOAT || $rightType === self::TYPE_BIGFLOAT) { + $this->usesBigFloat = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGFLOAT) { + $leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGFLOAT) { + $rightExpr = $this->convertBigFloatExpr($rightExpr, $rightType); + } + return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ')'; + } + if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) { + $this->usesBigInt = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_BIGINT) { + $leftExpr = $this->convertBigIntExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_BIGINT) { + $rightExpr = $this->convertBigIntExpr($rightExpr, $rightType); + } + return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ')'; + } + if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) { + $this->usesDecimal = true; + $leftExpr = $this->parseExpr($expr->left); + $rightExpr = $this->parseExpr($expr->right); + if ($leftType !== self::TYPE_DECIMAL) { + $leftExpr = $this->convertDecimalExpr($leftExpr, $leftType); + } + if ($rightType !== self::TYPE_DECIMAL) { + $rightExpr = $this->convertDecimalExpr($rightExpr, $rightType); + } + return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ')'; + } $left = $this->parseIdentifier($expr->left); $right = $this->parseIdentifier($expr->right); - return 'php::compare(' . $left . ', ' . $right . ')'; } @@ -4001,6 +4345,19 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseUnaryMinus(Expr\UnaryMinus $expr): string { + $type = $this->detectTypeOfExpr($expr->expr); + if ($type === self::TYPE_BIGFLOAT) { + $this->usesBigFloat = true; + return 'php::BigFloat::neg(' . $this->parseExpr($expr->expr) . ')'; + } + if ($type === self::TYPE_BIGINT) { + $this->usesBigInt = true; + return 'php::BigInt::neg(' . $this->parseExpr($expr->expr) . ')'; + } + if ($type === self::TYPE_DECIMAL) { + $this->usesDecimal = true; + return 'php::Decimal::neg(' . $this->parseExpr($expr->expr) . ')'; + } $code = $this->parseExpr($expr->expr); return '-' . $code; @@ -4013,10 +4370,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpDiv(Expr\BinaryOp\Div $expr): string { - $left = $this->parseIdentifier($expr->left); - $right = $this->parseIdentifier($expr->right); - - return $left . ' / (' . $right . ')'; + return $this->parseBinaryOp($expr->left, $expr->right, '/'); } protected function parseBinaryOpMinus(Expr\BinaryOp\Minus $expr): string @@ -5702,12 +6056,46 @@ class CompilerBase extends \PhpAot\Core\Translator 'int' => self::TYPE_INT, 'float' => self::TYPE_FLOAT, 'bool' => self::TYPE_BOOL, + 'bigInt' => self::TYPE_BIGINT, + 'decimal' => self::TYPE_DECIMAL, + 'bigFloat' => self::TYPE_BIGFLOAT, default => '', }; if ($type) { $expr->setAttribute('nativeType', $type); $valueExpr = $this->parseExpr($expr->args[0]->value); - return $this->convertExprFromType($type, $valueExpr); + if (in_array($type, [self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL])) { + return $this->convertExprFromType($type, $valueExpr); + } + $argType = $this->detectTypeOfExpr($expr->args[0]->value); + if ($argType === $type) { + return $valueExpr; + } + if ($type === self::TYPE_BIGINT) { + $this->usesBigInt = true; + if ($argType === self::TYPE_INT) { + return 'php::newBigInt(' . $this->trimBrackets($valueExpr) . ')'; + } + return 'php::BigInt::newInstance(' . $valueExpr . ')'; + } + if ($type === self::TYPE_DECIMAL) { + $this->usesDecimal = true; + if ($argType === self::TYPE_INT) { + return 'php::newDecimal(' . $this->trimBrackets($valueExpr) . ')'; + } + return 'php::Decimal::newInstance(' . $valueExpr . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + $this->usesBigFloat = true; + if ($argType === self::TYPE_INT) { + return 'php::newBigFloat(' . $this->trimBrackets($valueExpr) . ')'; + } + if ($argType === self::TYPE_FLOAT) { + return 'php::newBigFloat(' . $this->trimBrackets($valueExpr) . ')'; + } + return 'php::BigFloat::newInstance(' . $valueExpr . ')'; + } + return $valueExpr; } else { $this->fatalError($expr, 'Unknown std method: ' . $func); } @@ -5807,7 +6195,7 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $code .= $info['decl'] . ' ' . $name . '{};'; } - } elseif ($type === self::TYPE_STREAM) { + } elseif ($type === self::TYPE_STREAM || $type === self::TYPE_BIGINT || $type === self::TYPE_DECIMAL || $type === self::TYPE_BIGFLOAT) { $code .= self::TYPE_VAR . ' ' . $name . ';'; } else { $code .= $type . ' ' . $name; diff --git a/src/Php/UniversalMethodCall.php b/src/Php/UniversalMethodCall.php index 7b798a87..6728a44a 100644 --- a/src/Php/UniversalMethodCall.php +++ b/src/Php/UniversalMethodCall.php @@ -286,11 +286,51 @@ trait UniversalMethodCall 'appendFilter' => ['handler' => 'php_fn', 'fn' => 'stream_filter_append', 'return_type' => self::TYPE_VAR, 'min_args' => 1, 'max_args' => 3], 'prependFilter' => ['handler' => 'php_fn', 'fn' => 'stream_filter_prepend', 'return_type' => self::TYPE_VAR, 'min_args' => 1, 'max_args' => 3], ], + self::TYPE_BIGINT => [ + 'add' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::add', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::sub', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::mul', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'div' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::div', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'mod' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::mod', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'pow' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::pow', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::neg', 'return_type' => self::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0], + 'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1], + 'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::abs', 'return_type' => self::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0], + 'gcd' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::gcd', 'return_type' => self::TYPE_BIGINT, 'min_args' => 1, 'max_args' => 1], + 'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0], + 'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0], + 'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0], + ], + self::TYPE_DECIMAL => [ + 'add' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::add', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], + 'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::sub', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], + 'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::mul', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], + 'div' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::div', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], + 'mod' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::mod', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 1, 'max_args' => 1], + 'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::neg', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0], + 'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1], + 'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::abs', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0], + 'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0], + 'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0], + 'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0], + ], + self::TYPE_BIGFLOAT => [ + 'add' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::add', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1], + 'sub' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::sub', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1], + 'mul' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::mul', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1], + 'div' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::div', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 1, 'max_args' => 1], + 'neg' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::neg', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0], + 'cmp' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::cmp', 'return_type' => self::TYPE_INT, 'min_args' => 1, 'max_args' => 1], + 'abs' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::abs', 'return_type' => self::TYPE_BIGFLOAT, 'min_args' => 0, 'max_args' => 0], + 'toString' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toString', 'return_type' => self::TYPE_STR, 'min_args' => 0, 'max_args' => 0], + 'toInt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0], + 'toFloat' => ['handler' => 'cpp_fn', 'fn' => 'php::BigFloat::toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0], + ], ]; private const array MUTATING_HANDLERS = ['direct_method_mutate', 'php_fn_ref']; - private const array TYPE_SEARCH_ORDER = [self::TYPE_STR, self::TYPE_ARRAY, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL, self::TYPE_STREAM]; + private const array TYPE_SEARCH_ORDER = [self::TYPE_STR, self::TYPE_ARRAY, self::TYPE_INT, self::TYPE_FLOAT, self::TYPE_BOOL, self::TYPE_STREAM, self::TYPE_BIGINT, self::TYPE_DECIMAL, self::TYPE_BIGFLOAT]; protected function detectUniversalMethodReturnType(string $type, string $method): ?string { @@ -313,6 +353,9 @@ trait UniversalMethodCall self::TYPE_STR => 'str', self::TYPE_ARRAY => 'array', self::TYPE_STREAM => 'stream', + self::TYPE_BIGINT => 'bigint', + self::TYPE_DECIMAL => 'decimal', + self::TYPE_BIGFLOAT => 'bigfloat', ]; protected function camelToSnake(string $name): string diff --git a/tests/aot/big_number_operators.phpt b/tests/aot/big_number_operators.phpt new file mode 100644 index 00000000..86b3e2b2 --- /dev/null +++ b/tests/aot/big_number_operators.phpt @@ -0,0 +1,100 @@ +--TEST-- +BigInt and Decimal operator overloading (+, -, *, /, %, **, <, >, <=, >=, ==, !=, <=>) +--FILE-- +toString(); echo "\n"; + // Int + BigInt + $d = 300 + $a; + echo $d->toString(); echo "\n"; + // BigInt - Int + $e = $a - 30; + echo $e->toString(); echo "\n"; + // BigInt * Int + $f = $a * 5; + echo $f->toString(); echo "\n"; + // BigInt / Int + $g = $a / 3; + echo $g->toString(); echo "\n"; + // BigInt % Int + $h = $a % 7; + echo $h->toString(); echo "\n"; + // BigInt ** Int + $i = std::bigInt(2) ** 10; + echo $i->toString(); echo "\n"; + + // === BigInt comparison operators === + echo (int)($a < $b); echo "\n"; + echo (int)($a > $b); echo "\n"; + echo (int)($a <= 100); echo "\n"; + echo (int)($a >= 100); echo "\n"; + echo (int)($a == 100); echo "\n"; + echo (int)($a != 50); echo "\n"; + + // BigInt <=> (spaceship) + $sp1 = $a <=> $b; + echo (int)$sp1; echo "\n"; + $sp2 = $a <=> 100; + echo (int)$sp2; echo "\n"; + + // === Decimal arithmetic operators === + $dec = std::decimal("50.25"); + + // Decimal + Int + $j = $dec + 100; + echo $j->toString(); echo "\n"; + // Decimal - Float + $k = $dec - 0.25; + echo $k->toString(); echo "\n"; + // Decimal * Int + $l = $dec * 4; + echo $l->toString(); echo "\n"; + // Decimal / Int + $m = $dec / 5; + echo $m->toString(); echo "\n"; + + // === Decimal comparison operators === + echo (int)($dec > 10); echo "\n"; + echo (int)($dec < 100); echo "\n"; + echo (int)($dec == 50.25); echo "\n"; + echo (int)($dec != 100); echo "\n"; + + // Decimal <=> + $sp3 = $dec <=> 60; + echo (int)$sp3; echo "\n"; +} +?> +--EXPECT-- +300 +400 +70 +500 +33 +2 +1024 +1 +0 +1 +1 +1 +1 +-1 +0 +150.25 +50.00 +201.00 +10.05 +1 +1 +1 +1 +-1 diff --git a/tests/aot/bigfloat_operators.phpt b/tests/aot/bigfloat_operators.phpt new file mode 100644 index 00000000..8372b0ac --- /dev/null +++ b/tests/aot/bigfloat_operators.phpt @@ -0,0 +1,78 @@ +--TEST-- +BigFloat operator overloading (+, -, *, /) and comparisons (<, >, <=, >=, ==, !=, <=>) +--FILE-- +toString(); echo "\n"; + // Int + BigFloat + $d = 300 + $a; + echo $d->toString(); echo "\n"; + // BigFloat - Int + $e = $a - 30; + echo $e->toString(); echo "\n"; + // BigFloat * Int + $f = $a * 5; + echo $f->toString(); echo "\n"; + // BigFloat / Int + $g = $a / 5; + echo $g->toString(); echo "\n"; + // BigFloat + Float + $h = $a + 0.5; + echo $h->toString(); echo "\n"; + + // === BigFloat constructed from int === + $i = std::bigFloat(42); + echo $i->toString(); echo "\n"; + + // === Unary minus === + $neg = -$a; + echo $neg->toString(); echo "\n"; + + // === BigFloat comparison operators === + echo (int)($a < $b); echo "\n"; + echo (int)($a > $b); echo "\n"; + echo (int)($a <= 100.5); echo "\n"; + echo (int)($a >= 100.5); echo "\n"; + echo (int)($a == 100.5); echo "\n"; + echo (int)($a != 50); echo "\n"; + + // BigFloat <=> (spaceship) + $sp1 = $a <=> $b; + echo (int)$sp1; echo "\n"; + $sp2 = $a <=> 100.5; + echo (int)$sp2; echo "\n"; + + // === Universal method calls === + $j = std::bigFloat(3.14); + echo $j->toInt(); echo "\n"; + echo $j->abs()->toString(); echo "\n"; +} +?> +--EXPECT-- +300.5 +400.5 +70.5 +502.5 +20.100000000000001 +101 +42 +-100.5 +1 +0 +1 +1 +1 +1 +-1 +0 +3 +3.1400000000000001 diff --git a/tests/aot/bigint/arithmetic.phpt b/tests/aot/bigint/arithmetic.phpt new file mode 100644 index 00000000..8a133fa8 --- /dev/null +++ b/tests/aot/bigint/arithmetic.phpt @@ -0,0 +1,27 @@ +--TEST-- +BigInt arithmetic operations +--FILE-- +add($b)->toString(); echo "\n"; + echo "b-a="; echo $b->sub($a)->toString(); echo "\n"; + echo "a*b="; echo $a->mul($b)->toString(); echo "\n"; + echo "b/a="; echo $b->div($a)->toString(); echo "\n"; + echo "b%a="; echo $b->mod($a)->toString(); echo "\n"; + echo "neg(a)="; echo $a->neg()->toString(); echo "\n"; + echo "abs(neg(a))="; echo $a->neg()->abs()->toString(); echo "\n"; +} +?> +--EXPECT-- +a+b=111111111011111111100 +b-a=86419753208641975320 +a*b=1219326311370217952237463801111263526900 +b/a=8 +b%a=900000000090 +neg(a)=-12345678901234567890 +abs(neg(a))=12345678901234567890 diff --git a/tests/aot/bigint/compare.phpt b/tests/aot/bigint/compare.phpt new file mode 100644 index 00000000..00e0bc3a --- /dev/null +++ b/tests/aot/bigint/compare.phpt @@ -0,0 +1,20 @@ +--TEST-- +BigInt comparison +--FILE-- +b) + echo "cmp(a,b)="; echo $a->cmp($b); echo "\n"; + echo "cmp(b,a)="; echo $b->cmp($a); echo "\n"; + echo "cmp(a,a)="; echo $a->cmp($a); echo "\n"; +} +?> +--EXPECT-- +cmp(a,b)=-1 +cmp(b,a)=1 +cmp(a,a)=0 diff --git a/tests/aot/bigint/literal.phpt b/tests/aot/bigint/literal.phpt new file mode 100644 index 00000000..c84a829b --- /dev/null +++ b/tests/aot/bigint/literal.phpt @@ -0,0 +1,18 @@ +--TEST-- +BigInt literal parsing and output +--FILE-- +toString(); + echo "\n"; + $b = 99999999999999999999; + echo $b->toString(); +} +?> +--EXPECT-- +12345678901234567890 +99999999999999999999 diff --git a/tests/aot/bigint/mixed.phpt b/tests/aot/bigint/mixed.phpt new file mode 100644 index 00000000..70d1c43b --- /dev/null +++ b/tests/aot/bigint/mixed.phpt @@ -0,0 +1,37 @@ +--TEST-- +BigInt mixed operations with Int +--FILE-- +add(100); + echo $c->toString(); echo "\n"; + // BigInt * Int + $d = $a->mul(2); + echo $d->toString(); echo "\n"; + // BigInt - Int + $e = $a->sub(7890); + echo $e->toString(); echo "\n"; + // BigInt / Int + $f = $a->div(10); + echo $f->toString(); echo "\n"; + // BigInt % Int + $g = $a->mod(1000000); + echo $g->toString(); echo "\n"; + // BigInt cmp with Int + echo $a->cmp(100); echo "\n"; + echo $a->cmp(99999999999999999999); echo "\n"; +} +?> +--EXPECT-- +12345678901234567990 +24691357802469135780 +12345678901234560000 +1234567890123456789 +567890 +1 +-1 diff --git a/tests/aot/std_builtin.phpt b/tests/aot/std_builtin.phpt new file mode 100644 index 00000000..54a31a8d --- /dev/null +++ b/tests/aot/std_builtin.phpt @@ -0,0 +1,50 @@ +--TEST-- +std::bigInt() and std::decimal() builtin functions +--FILE-- +toString(); echo "\n"; + + // std::bigInt from plain int + $b = std::bigInt(42); + echo $b->toString(); echo "\n"; + + // std::bigInt arithmetic + $c = $a->add($b); + echo $c->toString(); echo "\n"; + + // std::bigInt from string + $d = std::bigInt("99999999999999999999"); + echo $d->toString(); echo "\n"; + + // std::decimal from int + $e = std::decimal(12345); + echo $e->toString(); echo "\n"; + + // std::decimal from string + $f = std::decimal("3.14159265358979323"); + echo $f->toString(); echo "\n"; + + // std::decimal arithmetic + $g = $e->add($f); + echo $g->toString(); echo "\n"; + + // std::decimal from float literal (explicit, stays decimal) + $h = std::decimal(2.5); + echo $h->toString(); echo "\n"; +} +?> +--EXPECT-- +12345678901234567890 +42 +12345678901234567932 +99999999999999999999 +12345 +3.14159265358979323 +12348.14159265359 +2.5