diff --git a/examples/decimal/decimal.php b/examples/decimal/decimal.php new file mode 100644 index 00000000..51dc65f4 --- /dev/null +++ b/examples/decimal/decimal.php @@ -0,0 +1,10 @@ +fatalError($expr, 'Cannot echo assign expression'); } else { - $lines[] = 'php::echo(' . $this->parseExpr($expr) . ');'; + $type = $this->detectTypeOfExpr($expr); + $parsed = $this->parseExpr($expr); + if ($type === self::TYPE_BIGINT) { + $parsed = 'php::BigInt::toString(' . $parsed . ')'; + } elseif ($type === self::TYPE_BIGFLOAT) { + $parsed = 'php::BigFloat::toString(' . $parsed . ')'; + } elseif ($type === self::TYPE_DECIMAL) { + $parsed = 'php::Decimal::toString(' . $parsed . ')'; + } + $lines[] = 'php::echo(' . $parsed . ');'; } } @@ -2460,6 +2469,25 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Expr_FuncCall': if ($this->isNameExpr($expr->name)) { $name = $this->parseIdentifier($expr->name); + // Math function optimization: propagate Big* return types + if (in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) && !empty($expr->args)) { + $argType = $this->detectTypeOfExpr($expr->args[0]->value); + if ( + $argType === self::TYPE_BIGINT + && in_array($name, ['abs', 'pow', 'sqrt'], true) + ) { + return self::TYPE_BIGINT; + } + if ( + $argType === self::TYPE_DECIMAL + && in_array($name, ['abs', 'pow', 'sqrt', 'floor', 'ceil', 'round'], true) + ) { + return self::TYPE_DECIMAL; + } + if ($argType === self::TYPE_BIGFLOAT && $name === 'abs') { + return self::TYPE_BIGFLOAT; + } + } if (in_array($name, self::STREAM_FUNCTIONS) || $name === 'stream_cast') { return self::TYPE_STREAM; } @@ -2976,15 +3004,29 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBinaryOpConcat(Expr\BinaryOp\Concat $expr): string { - $left = $this->parseIdentifier($expr->left); - $right = $this->parseIdentifier($expr->right); + $left = $this->parseExpr($expr->left); + $right = $this->parseExpr($expr->right); $leftType = $this->detectTypeOfExpr($expr->left); $rightType = $this->detectTypeOfExpr($expr->right); if ($leftType === self::TYPE_VOID or $rightType === self::TYPE_VOID) { $this->fatalError($expr, 'Cannot concat void'); } - return Symbol::concat() . '(' . $this->convertStringExpr($left) . ', ' . $this->convertStringExpr($right) . ')'; + return Symbol::concat() . '(' . $this->convertExprToStringByType($left, $leftType) . ', ' . $this->convertExprToStringByType($right, $rightType) . ')'; + } + + protected function convertExprToStringByType(string $expr, $type): string + { + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::toString(' . $expr . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + return 'php::BigFloat::toString(' . $expr . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::toString(' . $expr . ')'; + } + return $this->convertStringExpr($expr); } protected function parseFor(Node\Stmt\For_ $v): string @@ -4348,7 +4390,18 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseCastString(Expr\Cast\String_ $node): string { - return $this->convertStringExpr($this->parseExpr($node->expr)); + $type = $this->detectTypeOfExpr($node->expr); + $expr = $this->parseExpr($node->expr); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::toString(' . $expr . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + return 'php::BigFloat::toString(' . $expr . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::toString(' . $expr . ')'; + } + return $this->convertStringExpr($expr); } protected function parseCastBool(Expr\Cast\Bool_ $node): string diff --git a/src/Php/FuncCallOptimizer.php b/src/Php/FuncCallOptimizer.php index edc124c6..278c22c0 100644 --- a/src/Php/FuncCallOptimizer.php +++ b/src/Php/FuncCallOptimizer.php @@ -69,7 +69,19 @@ trait FuncCallOptimizer case 'boolval': return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value)); case 'strval': - return $this->convertStringExpr($this->parseExpr($expr->args[0]->value)); + $arg = $expr->args[0]->value; + $type = $this->detectTypeOfExpr($arg); + $parsed = $this->parseExpr($arg); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::toString(' . $parsed . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + return 'php::BigFloat::toString(' . $parsed . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::toString(' . $parsed . ')'; + } + return $this->convertStringExpr($parsed); default: break; } @@ -91,11 +103,59 @@ trait FuncCallOptimizer } } if ($name === 'abs') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::abs(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + if ($type === self::TYPE_BIGFLOAT) { + return 'php::BigFloat::abs(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::abs(' . $this->parseExpr($expr->args[0]->value) . ')'; + } return 'php::math::abs(' . $getArg(0) . ')'; } if ($name === 'pow') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')'; + } return 'php::math::pow(' . $getArg(0) . ', ' . $getArg(1) . ')'; } + if ($name === 'sqrt') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_BIGINT) { + return 'php::BigInt::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + } + if ($name === 'floor') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::floor(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + } + if ($name === 'ceil') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_DECIMAL) { + return 'php::Decimal::ceil(' . $this->parseExpr($expr->args[0]->value) . ')'; + } + } + if ($name === 'round') { + $type = $this->detectTypeOfExpr($expr->args[0]->value); + if ($type === self::TYPE_DECIMAL) { + $arg0 = $this->parseExpr($expr->args[0]->value); + if (count($expr->args) >= 2) { + return 'php::Decimal::round(' . $arg0 . ', ' . $this->parseExpr($expr->args[1]->value) . ')'; + } + return 'php::Decimal::round(' . $arg0 . ')'; + } + } if ($name === 'strlen') { return 'php::fn::strlen(' . $getArg(0) . ')'; } diff --git a/tests/aot/bigint/math_func_opt.phpt b/tests/aot/bigint/math_func_opt.phpt new file mode 100644 index 00000000..70526db9 --- /dev/null +++ b/tests/aot/bigint/math_func_opt.phpt @@ -0,0 +1,33 @@ +--TEST-- +Big* types: math function optimization (abs/pow/sqrt/floor/ceil/round) +--FILE-- + +--EXPECT-- +OK diff --git a/tests/aot/bigint/toString_cast.phpt b/tests/aot/bigint/toString_cast.phpt new file mode 100644 index 00000000..7de4513f --- /dev/null +++ b/tests/aot/bigint/toString_cast.phpt @@ -0,0 +1,37 @@ +--TEST-- +Big* types: (string) cast, strval() and echo → toString() +--FILE-- + +--EXPECT-- +string(20) "12345678901234567890" +string(20) "12345678901234567890" +12345678901234567890 +string(18) "3.1415926535897931" +string(18) "3.1415926535897931" +3.1415926535897931 +string(11) "0.123456789" +string(11) "0.123456789" +0.123456789 diff --git a/tests/aot/decimal/floor-02.phpt b/tests/aot/decimal/floor-02.phpt new file mode 100644 index 00000000..5c4abf42 --- /dev/null +++ b/tests/aot/decimal/floor-02.phpt @@ -0,0 +1,17 @@ +--TEST-- +decimal: literal +--FILE-- + +--EXPECT-- +1.79 \ No newline at end of file diff --git a/tests/aot/decimal/round-02.phpt b/tests/aot/decimal/round-02.phpt new file mode 100644 index 00000000..fb1610c9 --- /dev/null +++ b/tests/aot/decimal/round-02.phpt @@ -0,0 +1,25 @@ +--TEST-- +Decimal: round +--FILE-- + +--EXPECT-- +小计: 89.97 +税额: 7.1976 +总计: 97.17