feat(compiler): 支持 bigint_types 和 decimal_types 声明指令

- 添加了 bigint_types 和 decimal_types 的 use 指令支持
- 实现了整数字面量自动转换为 BigInt 类型的功能
- 实现了浮点数字面量自动转换为 Decimal 类型的功能
- 添加了新的数学运算方法如 divmod、powmod、sqrt 等
- 更新了类型转换逻辑以支持大数运算
- 添加了相应的测试用例验证功能正确性
- 修改了编译器内部类型检测和转换机制
pull/1/head
韩天峰 3 months ago
parent 6b5e8ad2e0
commit 144b60a3e3
  1. 172
      src/Php/CompilerBase.php
  2. 8
      src/Php/Generator/Utils.php
  3. 10
      src/Php/UniversalMethodCall.php
  4. 30
      tests/aot/bigint/bigint_types.phpt
  5. 37
      tests/aot/bigint/both_types.phpt
  6. 19
      tests/aot/bigint/pow.phpt
  7. 20
      tests/aot/bigint/use.phpt
  8. 36
      tests/aot/decimal/decimal_types.phpt
  9. 16
      tests/aot/decimal/literal.phpt
  10. 21
      tests/aot/decimal/use.phpt

@ -175,6 +175,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected array $globalHeaders = [
'phpx.h',
'phpx_helper.h',
'phpx_big_int.h',
'phpx_big_float.h',
'phpx_decimal.h',
'php_aot_helper.h',
];
protected array $localHeaders = [];
@ -289,6 +292,8 @@ class CompilerBase extends \PhpAot\Core\Translator
protected array $globalVars = [];
protected bool $strictTypes = false;
protected bool $nativeTypes = false;
protected bool $decimalTypes = false;
protected bool $bigintTypes = false;
protected string $rootPath;
protected string $buildDir;
protected int $debugLine = 0;
@ -299,9 +304,6 @@ 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 路径
@ -859,6 +861,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->indentLevel = 0;
$this->strictTypes = false;
$this->nativeTypes = false;
$this->decimalTypes = false;
$this->bigintTypes = false;
$this->classesDefineInFile = [];
$this->interfacesDefineInFile = [];
$this->functionDefineInFile = [];
@ -1197,15 +1201,18 @@ class CompilerBase extends \PhpAot\Core\Translator
$type = $expr->getType();
switch ($type) {
case 'Scalar_Int':
if ($this->bigintTypes) {
return 'php::newBigInt(' . $expr->value . ')';
}
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)) . ')';
if ($this->isDecimalLiteral($expr) || $this->decimalTypes) {
$rawValue = $expr->getAttribute('rawValue');
$clean = $rawValue !== null ? $this->stripNumericUnderscores($rawValue) : (string) $expr->value;
return 'php::newDecimal(' . $this->getLiteralString($clean) . ')';
}
return $this->parseScalarFloat($expr);
case 'Scalar_String':
@ -1226,8 +1233,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($rawValue === null) {
return false;
}
// Remove underscores
$clean = str_replace('_', '', $rawValue);
$clean = $this->stripNumericUnderscores($rawValue);
// Must look like a decimal integer (no dot, no hex/oct/bin prefix, all digits)
if (!preg_match('/^\d+$/', $clean)) {
return false;
@ -1247,7 +1253,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($rawValue === null) {
return false;
}
$clean = str_replace('_', '', $rawValue);
$clean = $this->stripNumericUnderscores($rawValue);
// Must have a decimal point or exponent (not a pure integer)
if (!preg_match('/[\.eE]/', $clean)) {
return false;
@ -1259,12 +1265,12 @@ class CompilerBase extends \PhpAot\Core\Translator
private function getBigIntLiteralString(Node\Scalar $expr): string
{
return str_replace('_', '', $expr->getAttribute('rawValue'));
return $this->stripNumericUnderscores($expr->getAttribute('rawValue'));
}
private function getDecimalLiteralString(Node\Scalar $expr): string
{
return str_replace('_', '', $expr->getAttribute('rawValue'));
return $this->stripNumericUnderscores($expr->getAttribute('rawValue'));
}
protected function parseSuperGlobalVar(string $name): string
@ -1895,8 +1901,7 @@ class CompilerBase extends \PhpAot\Core\Translator
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) {
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGFLOAT) {
@ -1918,12 +1923,11 @@ class CompilerBase extends \PhpAot\Core\Translator
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 ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType, $left);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType, $right);
}
$arithOpMap = ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod'];
$method = $arithOpMap[$op] ?? null;
@ -1937,8 +1941,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
if ($leftType !== self::TYPE_BIGINT) {
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
}
if ($rightType !== self::TYPE_BIGINT) {
@ -2373,7 +2376,7 @@ class CompilerBase extends \PhpAot\Core\Translator
case 'Expr_Cast_Int':
return self::TYPE_INT;
case 'Scalar_Int':
return self::TYPE_INT;
return $this->bigintTypes ? self::TYPE_BIGINT : self::TYPE_INT;
case 'Expr_Cast_Float':
case 'Expr_Cast_Double':
return self::TYPE_FLOAT;
@ -2381,7 +2384,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->isBigIntLiteral($expr)) {
return self::TYPE_BIGINT;
}
if ($this->isDecimalLiteral($expr)) {
if ($this->isDecimalLiteral($expr) || $this->decimalTypes) {
return self::TYPE_DECIMAL;
}
return self::TYPE_FLOAT;
@ -2784,20 +2787,20 @@ class CompilerBase extends \PhpAot\Core\Translator
$libraries[] = 'php';
}
// GMP — BigInt arbitrary-precision arithmetic (only when actually used)
if ($this->usesBigInt && !$platform instanceof Windows) {
// GMP — BigInt arbitrary-precision arithmetic
if (!$platform instanceof Windows) {
$libraries[] = 'gmp';
$libraries[] = 'gmpxx';
}
// libmpdec — Decimal arbitrary-precision decimal (only when actually used)
if ($this->usesDecimal && !$platform instanceof Windows) {
// libmpdec — Decimal arbitrary-precision decimal
if (!$platform instanceof Windows) {
$libraries[] = 'mpdec++';
$libraries[] = 'mpdec';
}
// MPFR — BigFloat arbitrary-precision float (only when actually used)
if ($this->usesBigFloat && !$platform instanceof Windows) {
// MPFR — BigFloat arbitrary-precision float
if (!$platform instanceof Windows) {
$libraries[] = 'mpfr';
}
@ -3086,7 +3089,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->convertVarType($tmpVar, $var) . ', ' .
$this->convertExprType($expr, $type, $rightType) . ');';
} elseif ($type === self::TYPE_BIGINT || $type === self::TYPE_DECIMAL || $type === self::TYPE_BIGFLOAT) {
$bigAssign = $this->parseBigAssignOpExpr($var, $type, $expr, $rightType, $binaryOp, $node->var);
$bigAssign = $this->parseBigAssignOpExpr($var, $type, $expr, $rightType, $binaryOp, $node->var, $node->expr);
$this->context->beforeStmtLines[] = "{$tmpVar} = {$bigAssign};";
} else {
$this->context->beforeStmtLines[] = "{$tmpVar} = " .
@ -3114,7 +3117,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseBigAssignOp(Expr\AssignOp $node, string $var, string $type, string $expr, string $rightType, string $op): string
{
$binaryOp = $this->removeAssignOp($op);
$bigExpr = $this->parseBigAssignOpExpr($var, $type, $expr, $rightType, $binaryOp, $node->var);
$bigExpr = $this->parseBigAssignOpExpr($var, $type, $expr, $rightType, $binaryOp, $node->var, $node->expr);
return $var . ' = ' . $bigExpr;
}
@ -3123,7 +3126,7 @@ class CompilerBase extends \PhpAot\Core\Translator
*
* @param NodeAbstract $errorNode node to blame on unsupported operators
*/
protected function parseBigAssignOpExpr(string $leftExpr, string $leftType, string $rightExpr, string $rightType, string $binaryOp, NodeAbstract $errorNode): string
protected function parseBigAssignOpExpr(string $leftExpr, string $leftType, string $rightExpr, string $rightType, string $binaryOp, NodeAbstract $errorNode, ?NodeAbstract $rightNode = null): string
{
[$class, $opMap] = match ($leftType) {
self::TYPE_BIGINT => ['BigInt', ['+' => 'add', '-' => 'sub', '*' => 'mul', '/' => 'div', '%' => 'mod']],
@ -3136,18 +3139,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->fatalError($errorNode, "Unsupported compound assignment operator '{$binaryOp}' for type {$leftType}");
}
// Set uses flag
if ($leftType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
} elseif ($leftType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
} elseif ($leftType === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
}
$convertedRight = match ($leftType) {
self::TYPE_BIGINT => $this->convertBigIntExpr($rightExpr, $rightType),
self::TYPE_DECIMAL => $this->convertDecimalExpr($rightExpr, $rightType),
self::TYPE_DECIMAL => $this->convertDecimalExpr($rightExpr, $rightType, $rightNode),
self::TYPE_BIGFLOAT => $this->convertBigFloatExpr($rightExpr, $rightType),
};
@ -3821,8 +3815,7 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$leftType = $this->detectTypeOfExpr($expr->left);
if ($leftType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
$rightType = $this->detectTypeOfExpr($expr->right);
if ($rightType !== self::TYPE_BIGINT) {
@ -3907,8 +3900,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$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);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
@ -3919,8 +3911,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
@ -3931,14 +3922,13 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType, $expr->left);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType, $expr->right);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') == 0';
}
@ -3950,8 +3940,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$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);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
@ -3962,8 +3951,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
@ -3974,14 +3962,13 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType, $expr->left);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType, $expr->right);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ') != 0';
}
@ -4052,9 +4039,17 @@ class CompilerBase extends \PhpAot\Core\Translator
return $expr;
}
protected function convertDecimalExpr(string $expr, string $fromType = ''): string
protected function convertDecimalExpr(string $expr, string $fromType = '', ?NodeAbstract $node = null): string
{
if ($fromType === self::TYPE_INT || $fromType === self::TYPE_FLOAT) {
if ($fromType === self::TYPE_FLOAT) {
if ($node instanceof Node\Scalar\Float_) {
$rawValue = $node->getAttribute('rawValue');
$clean = $rawValue !== null ? $this->stripNumericUnderscores($rawValue) : (string) $node->value;
return 'php::newDecimal(' . $this->getLiteralString($clean) . ')';
}
$this->fatalError($node, 'Cannot convert float expression to Decimal, use a literal value or string instead');
}
if ($fromType === self::TYPE_INT) {
return 'php::newDecimal(php::toString(' . $this->trimBrackets($expr) . '))';
}
if ($fromType === self::TYPE_BIGINT) {
@ -4069,7 +4064,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::newBigInt(' . $this->trimBrackets($expr) . ')';
}
if ($fromType === self::TYPE_FLOAT) {
return 'php::newBigInt((php::Int)' . $this->trimBrackets($expr) . ')';
$this->fatalError(null, 'Cannot convert float to BigInt, use string or int instead');
}
return $expr;
}
@ -4162,8 +4157,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$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);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGFLOAT) {
$leftExpr = $this->convertBigFloatExpr($leftExpr, $leftType);
@ -4174,8 +4168,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigFloat::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
if ($leftType === self::TYPE_BIGINT || $rightType === self::TYPE_BIGINT) {
$this->usesBigInt = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_BIGINT) {
$leftExpr = $this->convertBigIntExpr($leftExpr, $leftType);
@ -4186,14 +4179,13 @@ class CompilerBase extends \PhpAot\Core\Translator
return 'php::BigInt::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
if ($leftType === self::TYPE_DECIMAL || $rightType === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
$leftExpr = $this->parseExpr($expr->left);
$leftExpr = $this->parseExpr($expr->left);
$rightExpr = $this->parseExpr($expr->right);
if ($leftType !== self::TYPE_DECIMAL) {
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType);
$leftExpr = $this->convertDecimalExpr($leftExpr, $leftType, $expr->left);
}
if ($rightType !== self::TYPE_DECIMAL) {
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType);
$rightExpr = $this->convertDecimalExpr($rightExpr, $rightType, $expr->right);
}
return 'php::Decimal::cmp(' . $leftExpr . ', ' . $rightExpr . ')';
}
@ -4422,16 +4414,13 @@ class CompilerBase extends \PhpAot\Core\Translator
{
$type = $this->detectTypeOfExpr($expr->expr);
if ($type === self::TYPE_BIGFLOAT) {
$this->usesBigFloat = true;
return 'php::BigFloat::neg(' . $this->parseExpr($expr->expr) . ')';
return 'php::BigFloat::neg(' . $this->parseExpr($expr->expr) . ')';
}
if ($type === self::TYPE_BIGINT) {
$this->usesBigInt = true;
return 'php::BigInt::neg(' . $this->parseExpr($expr->expr) . ')';
return 'php::BigInt::neg(' . $this->parseExpr($expr->expr) . ')';
}
if ($type === self::TYPE_DECIMAL) {
$this->usesDecimal = true;
return 'php::Decimal::neg(' . $this->parseExpr($expr->expr) . ')';
return 'php::Decimal::neg(' . $this->parseExpr($expr->expr) . ')';
}
$code = $this->parseExpr($expr->expr);
@ -4871,6 +4860,12 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected function getNativeType(string $type): string
{
if ($type === self::TYPE_INT && $this->bigintTypes) {
return self::TYPE_BIGINT;
}
if ($type === self::TYPE_FLOAT && $this->decimalTypes) {
return self::TYPE_DECIMAL;
}
return $this->nativeTypes ? $type : self::TYPE_VAR;
}
@ -5941,6 +5936,10 @@ class CompilerBase extends \PhpAot\Core\Translator
} else {
if ($id === 'native_types') {
$this->nativeTypes = true;
} elseif ($id === 'decimal_types') {
$this->decimalTypes = true;
} elseif ($id === 'bigint_types') {
$this->bigintTypes = true;
} else {
$this->useNamespaces[] = $id;
if ($use->alias) {
@ -6147,22 +6146,31 @@ class CompilerBase extends \PhpAot\Core\Translator
return $valueExpr;
}
if ($type === self::TYPE_BIGINT) {
$this->usesBigInt = true;
if ($argType === self::TYPE_INT) {
if ($argType === self::TYPE_FLOAT) {
$this->fatalError($expr, 'Cannot construct BigInt from float, use string or int instead');
}
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) {
if ($argType === self::TYPE_FLOAT) {
$argNode = $expr->args[0]->value;
if ($argNode instanceof Node\Scalar\Float_) {
$rawValue = $argNode->getAttribute('rawValue');
$clean = $rawValue !== null ? $this->stripNumericUnderscores($rawValue) : (string)$argNode->value;
return 'php::newDecimal(' . $this->getLiteralString($clean) . ')';
}
$this->fatalError($expr, 'Cannot construct Decimal from float variable, use string or int instead');
}
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) {
if ($argType === self::TYPE_INT) {
return 'php::newBigFloat(' . $this->trimBrackets($valueExpr) . ')';
}
if ($argType === self::TYPE_FLOAT) {

@ -167,6 +167,14 @@ trait Utils
return false;
}
/**
* Strip PHP numeric literal underscores (e.g. 1_000_000 → 1000000).
*/
protected function stripNumericUnderscores(string $rawValue): string
{
return str_replace('_', '', $rawValue);
}
protected function trimBrackets(string $str): string
{
if ($this->isClosedExpr($str, '')) {

@ -296,6 +296,9 @@ trait UniversalMethodCall
'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],
'divmod' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::divmod', 'return_type' => self::TYPE_ARRAY, 'min_args' => 1, 'max_args' => 1],
'powmod' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::powmod', 'return_type' => self::TYPE_BIGINT, 'min_args' => 2, 'max_args' => 2],
'sqrt' => ['handler' => 'cpp_fn', 'fn' => 'php::BigInt::sqrt', 'return_type' => self::TYPE_BIGINT, 'min_args' => 0, 'max_args' => 0],
'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],
@ -306,9 +309,16 @@ trait UniversalMethodCall
'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],
'pow' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::pow', '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],
'divmod' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::divmod', 'return_type' => self::TYPE_ARRAY, 'min_args' => 1, 'max_args' => 1],
'powmod' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::powmod', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 2, 'max_args' => 2],
'sqrt' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::sqrt', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0],
'floor' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::floor', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0],
'ceil' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::ceil', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 0],
'round' => ['handler' => 'cpp_fn', 'fn' => 'php::Decimal::round', 'return_type' => self::TYPE_DECIMAL, 'min_args' => 0, 'max_args' => 1],
'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],

@ -0,0 +1,30 @@
--TEST--
use bigint_types — integer literals auto-converted to BigInt
--FILE--
<?php
declare(strict_types=1);
use bigint_types;
function main(): void {
// Simple int literal → BigInt
$a = 42;
echo $a->toString(); echo "\n";
// BigInt + Int literal (auto BigInt)
$b = $a + 10;
echo $b->toString(); echo "\n";
// BigInt * BigInt
$c = $a * 3;
echo $c->toString(); echo "\n";
// Int literal in arithmetic
$d = 100 + 200;
echo $d->toString(); echo "\n";
}
?>
--EXPECT--
42
52
126
300

@ -0,0 +1,37 @@
--TEST--
use bigint_types + use decimal_types together
--FILE--
<?php
declare(strict_types=1);
use bigint_types;
use decimal_types;
function main(): void {
// Int literal → BigInt
$a = 100;
echo $a->toString(); echo "\n";
// Float literal → Decimal
$b = 2.5;
echo $b->toString(); echo "\n";
// BigInt + Int (auto BigInt)
$c = $a + 50;
echo $c->toString(); echo "\n";
// Decimal + Float (auto Decimal)
$d = $b + 1.5;
echo $d->toString(); echo "\n";
// BigInt + Float → should fail (can't mix)
// But BigInt * BigInt works
$e = $a * 3;
echo $e->toString(); echo "\n";
}
?>
--EXPECT--
100
2.5
150
4.0
300

@ -0,0 +1,19 @@
--TEST--
BigInt: pow
--FILE--
<?php
use native_types;
function main()
{
require __DIR__ . '/../../../src/Assert.php';
$a = 3;
$b = $a->pow(3);
Assert::eq($b, 27);
$d = any(5);
$c = $a->pow($d);
Assert::eq($c, 243);
}
?>
--EXPECT--

@ -0,0 +1,20 @@
--TEST--
BigInt: use
--FILE--
<?php
use bigint_types;
function main()
{
require __DIR__ . '/../../../src/Assert.php';
$a = 3;
$b = $a->pow(80);
var_dump($b->toString());
$c = $a ** 80;
var_dump($b->toString());
}
?>
--EXPECT--
string(39) "147808829414345923316083210206383297601"
string(39) "147808829414345923316083210206383297601"

@ -0,0 +1,36 @@
--TEST--
use decimal_types — float literals auto-converted to Decimal
--FILE--
<?php
declare(strict_types=1);
use decimal_types;
function main(): void {
// Simple float literal → Decimal
$a = 3.1;
echo $a->toString(); echo "\n";
// Float literal → Decimal + Decimal ops
$b = 2.5;
$c = $a->add($b);
echo $c->toString(); echo "\n";
// Binary op: Decimal + Float literal (auto Decimal)
$d = $a + 1.5;
echo $d->toString(); echo "\n";
// Int + Float literal (auto Decimal)
$e = 10 + 0.5;
echo $e->toString(); echo "\n";
// Assignment from existing Decimal
$f = $a;
echo $f->toString(); echo "\n";
}
?>
--EXPECT--
3.1
5.6
4.6
10.5
3.1

@ -0,0 +1,16 @@
--TEST--
decimal: literal
--FILE--
<?php
use native_types;
function main()
{
require __DIR__ . '/../../../src/Assert.php';
$a = std::decimal(3.1);
$b = $a + 1.2;
var_dump($b->toString());
}
?>
--EXPECT--
string(3) "4.3"

@ -0,0 +1,21 @@
--TEST--
decimal: literal
--FILE--
<?php
use decimal_types;
function main()
{
require __DIR__ . '/../../../src/Assert.php';
$a = 3.1;
$b = $a + 1.2;
var_dump($b->toString());
$c = 0.0;
$d = ($c + 3.1) * 5.331;
var_dump($d->toString());
}
?>
--EXPECT--
string(3) "4.3"
string(7) "16.5261"
Loading…
Cancel
Save