From c9c558103cdeb9a53b94747d884a9064a83de7cd Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sun, 4 Jan 2026 19:48:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E7=BB=84=E6=93=8D=E4=BD=9C=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/fannkuchredux.php | 10 +- examples/simple.php | 12 +- src/Core/Translator.php | 1 - src/Php/Translator.php | 191 +++++++++++++++------ src/functions.php | 3 +- tests/aot/array_assignment_edge_cases.phpt | 153 +++++++++++++++++ tests/aot/array_assignment_operators.phpt | 124 +++++++++++++ tests/aot/complex_array_operations.phpt | 143 +++++++++++++++ 8 files changed, 563 insertions(+), 74 deletions(-) create mode 100644 tests/aot/array_assignment_edge_cases.phpt create mode 100644 tests/aot/array_assignment_operators.phpt create mode 100644 tests/aot/complex_array_operations.phpt diff --git a/examples/fannkuchredux.php b/examples/fannkuchredux.php index 91950d1e..636e693e 100644 --- a/examples/fannkuchredux.php +++ b/examples/fannkuchredux.php @@ -25,15 +25,11 @@ function fannkuch($n) while ($i < $n) { $perm[$i] = $perm1[$i]; $i += 1; -// -// var_dump($perm, $perm1); -// var_dump('[i] i=' . $i); } // Count flips and update max and checksum $f = 0; $k = $perm[0]; -// var_dump('[xxx] k=' . $k); while ($k != 0) { $i = 0; @@ -46,11 +42,8 @@ function fannkuch($n) $k = $perm[0]; $f += 1; -// var_dump('[--] r=' . $r . ', checksum=' . $checksum . ', i=' . $i . ', k=' . $k . ', f=' . $f . ', r=' . $r); } - var_dump('[2] r=' . $r . ', nperm=' . $nperm . ', checksum=' . $checksum . ', i=' . $i . ', k=' . $k . ', f=' . $f . ', r=' . $r); - if ($f > $flips) $flips = $f; if (($nperm & 0x1) == 0) $checksum += $f; else $checksum -= $f; @@ -82,6 +75,7 @@ function main() { global $argc, $argv; $n = $argc > 2 ? $argv[2] : 7; + $begin = microtime(true); printf("Pfannkuchen(%d) = %d\n", $n, fannkuch($n)); + echo "Time: ", microtime(true) - $begin, "\n"; } - diff --git a/examples/simple.php b/examples/simple.php index 6e1eee41..45d05158 100644 --- a/examples/simple.php +++ b/examples/simple.php @@ -1,13 +1,9 @@ indentStr, $this->indentLevel); } - } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index c31a8e38..b02239a5 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -26,6 +26,9 @@ class Translator extends \PhpAot\Core\Translator const string VALUE_NAN = 'std::numeric_limits::quiet_NaN()'; const string VALUE_INF = 'std::numeric_limits::infinity()'; + const string LITERAL_STRINGS = '_literal_strings'; + const string EXPR_VARIABLE = 'Expr_Variable'; + const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch'; private string $phpxDir = '~/workspace/projects/phpx'; protected string $lang = 'PHP'; @@ -91,6 +94,7 @@ class Translator extends \PhpAot\Core\Translator private int $optimizeLevel = 0; private int $floatPrecision = 17; private bool $debugInfo = true; + private bool $noLiteralStrings = false; private bool $verbose = false; private string $file; private string $dir; @@ -151,6 +155,12 @@ class Translator extends \PhpAot\Core\Translator 'required' => false, 'noValue' => true, ], + 'noLiteralStrings' => [ + 'longPrefix' => 'no-literal-strings', + 'description' => 'Disable literal strings optimization', + 'required' => false, + 'noValue' => true, + ], ]); $this->preprocessArgvAdvanced(); @@ -159,6 +169,8 @@ class Translator extends \PhpAot\Core\Translator $this->showUsage(); exit(0); } +// $this->noLiteralStrings = $climate->arguments->get('no-literal-strings'); + $this->noLiteralStrings = true; $this->optimizeLevel = $climate->arguments->get('optimize'); $this->definedFunctions = array_flip(get_defined_functions()['internal']); } @@ -183,6 +195,7 @@ class Translator extends \PhpAot\Core\Translator $climate->tab()->out('-o, --output Output binary name (default: input basename)'); $climate->tab()->out('-v, --verbose Verbose output'); $climate->tab()->out('-h, --help Show this help message'); + $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); $climate->br(); $climate->bold('EXAMPLES:'); @@ -234,7 +247,7 @@ class Translator extends \PhpAot\Core\Translator } $literalStringsCount = count($this->literalStrings); - $lines[] = 'extern php::Var _literal_strings[' . $literalStringsCount . '];' . PHP_EOL; + $lines[] = 'extern php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; } @@ -414,7 +427,7 @@ class Translator extends \PhpAot\Core\Translator { $type = $expr->getType(); switch ($type) { - case 'Expr_Variable': + case self::EXPR_VARIABLE: return $this->escapeVarName($expr->name); case 'Name': case 'Identifier': @@ -424,8 +437,12 @@ class Translator extends \PhpAot\Core\Translator case 'Scalar_Float': return $this->parseScalarFloat($expr); case 'Scalar_String': - $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); - return '_literal_strings[' . $index . ']'; + if ($this->noLiteralStrings) { + return '"' . $this->escapeString($expr->value) . '"'; + } else { + $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); + return self::LITERAL_STRINGS . '[' . $index . ']'; + } case 'Expr_ConstFetch': return $this->parseConstFetch($expr); default: @@ -534,6 +551,8 @@ class Translator extends \PhpAot\Core\Translator return $this->parseIsset($expr); case 'Expr_Assign': return $this->parseAssign($expr); + case 'Expr_AssignRef': + return $this->parseAssignRef($expr); case 'Expr_Print': return $this->parsePrint($expr); case 'Expr_BinaryOp_Equal': @@ -601,7 +620,7 @@ class Translator extends \PhpAot\Core\Translator return $this->parseBinaryOpMinus($expr); case 'Expr_Array': return $this->parseArray($expr); - case 'Expr_ArrayDimFetch': + case self::EXPR_ARRAY_DIM_FETCH: return $this->parseArrayDimFetch($expr); case 'Expr_PropertyFetch': return $this->parsePropertyFetch($expr); @@ -638,7 +657,7 @@ class Translator extends \PhpAot\Core\Translator case 'Scalar_Int': case 'Scalar_Float': case 'Scalar_String': - case 'Expr_Variable': + case self::EXPR_VARIABLE: return $this->parseIdentifier($expr); case 'Scalar_MagicConst_File': return $this->parseMagicConstFile($expr); @@ -672,23 +691,25 @@ class Translator extends \PhpAot\Core\Translator $left = $v->var; $right = $v->expr; - if ($left->getType() === 'Expr_ArrayDimFetch') { + if ($left->getType() === self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($left->var); $code = ''; // 这是 PHP 的初始化+赋值写法,需要先创建数组 - if (!$this->hasVar($array) and $left->var->getType() === 'Expr_Variable') { + if (!$this->hasVar($array) and $left->var->getType() === self::EXPR_VARIABLE) { $this->addLocalVar($array, self::TYPE_ARRAY); } + + $value = $this->trimBrackets($this->parseExpr($right)); if ($left->dim === null) { - return $code . "$array.offsetSet(php::null, " . $this->parseExpr($right) . ")"; + return $code . "$array.offsetSet(php::null, $value)"; } else { - $dim = $this->parseIdentifier($left->dim); - return $code . "$array.offsetSet($dim, " . $this->parseExpr($right) . ")"; + $dim = $this->trimBrackets($this->parseIdentifier($left->dim)); + return $code . "$array.offsetSet($dim, $value)"; } } elseif ($left->getType() === 'Expr_PropertyFetch') { $array = $this->parseIdentifier($left->var); $propName = $this->parseIdentifier($left->name); - return "$array.setProperty(\"$propName\", " . $this->parseExpr($right) . ")"; + return "$array.setProperty($propName, " . $this->trimBrackets($this->parseExpr($right)) . ")"; } elseif ($right->getType() === 'Expr_Assign') { $chain[] = $left; while ($right->getType() === 'Expr_Assign') { @@ -902,7 +923,7 @@ class Translator extends \PhpAot\Core\Translator return self::TYPE_OBJECT; case 'Expr_Assign': return $this->detectVarType($expr->var); - case 'Expr_Variable': + case self::EXPR_VARIABLE: return $this->detectVarType($expr); case 'Expr_ConstFetch': return $this->detectConstType($expr); @@ -937,6 +958,11 @@ class Translator extends \PhpAot\Core\Translator if ($assocArray) { // TODO 混杂模式数组赋值 $key = $item->key ? $this->parseIdentifier($item->key) : 'php::null'; + if (str_starts_with($key, self::LITERAL_STRINGS)) { + $key = "$key.toStdString()"; + } elseif ($key === '0L') { + $key = 'php::zero'; + } $list[] = $this->getIndent() . '{ ' . $key . ', ' . self::TYPE_VAR . '(' . $value . ') }'; } else { @@ -1048,13 +1074,6 @@ class Translator extends \PhpAot\Core\Translator return 'php::concat(' . $left . ', ' . $right . ')'; } - private function parseAssignOpConcat(mixed $expr): string - { - $var = $this->parseIdentifier($expr->var); - $value = $this->parseIdentifier($expr->expr); - return $var . '.append(' . $value . ')'; - } - private function parseFor(mixed $v): string { $init = $v->init; @@ -1113,16 +1132,54 @@ class Translator extends \PhpAot\Core\Translator return '++' . $this->parseIdentifier($expr->var); } + private function removeAssignOp(string $op): string + { + return str_replace('=', '', $op); + } + private function parseAssignOp(mixed $node, string $op): string { $var = $this->parseIdentifier($node->var); $expr = $this->parseIdentifier($node->expr); - if ($node->var->getType() === 'Expr_Variable') { + $leftExprType = $node->var->getType(); + if ($leftExprType === self::EXPR_VARIABLE) { $type = $this->detectVarType($node->var); return $var . ' ' . $op . ' ' . $this->convertExprType($expr, $type, $this->detectExprType($node->expr)); + } elseif ($leftExprType === self::EXPR_ARRAY_DIM_FETCH) { + /** + * $count[$r] -= 1; + * 需要转为下面语句: + * $tmp_var = $count[$r] - 1; + * $count[$r] = $tmp_var; + */ + $type = $this->detectVarType($node->var); + $rightType = $this->detectExprType($node->expr); + $tmpVar = $this->genTmpVarName(); + $this->addLocalVar($tmpVar, $rightType); + $dim = $this->parseIdentifier($node->var->dim); + $binaryOp = $this->removeAssignOp($op); + + if ($binaryOp === '.') { + $this->beforeStmtLines[] = "$tmpVar = php::concat(" . + $this->convertVarType($tmpVar, $var) . ', ' . + $this->convertExprType($expr, $type, $rightType) . ');'; + } else { + $this->beforeStmtLines[] = "$tmpVar = " . + $this->convertVarType($tmpVar, $var) . ' ' . + $binaryOp . ' ' . + $this->convertExprType($expr, $type, $rightType) . ';'; + } + return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar); } else { return $var . ' ' . $op . ' (' . $expr . ')'; } + + // TODO 属性设置 + } + + private function parseAssignOpConcat(mixed $expr): string + { + return $this->parseAssignOp($expr, '.='); } private function parseAssignOpPlus(mixed $expr): string @@ -1130,39 +1187,62 @@ class Translator extends \PhpAot\Core\Translator return $this->parseAssignOp($expr, '+='); } - private function parseArrayDimFetch($node): string + private function parseAssignOpMinus(mixed $expr): string { - $var = $this->parseIdentifier($node->var); - $dim = $this->parseIdentifier($node->dim); - return $var . '.offsetGet(' . $dim . ')'; + return $this->parseAssignOp($expr, '-='); } - private function parseBinaryOpShiftLeft($expr): string + private function parseAssignOpMod(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '<<'); + return $this->parseAssignOp($expr, '%='); } - private function parseBinaryOpShiftRight($expr): string + private function parseAssignOpMul(mixed $expr): string { - return $this->parseBinaryOp($expr->left, $expr->right, '>>'); + return $this->parseAssignOp($expr, '*='); } - private function parseAssignOpMinus(mixed $expr): string + private function parseAssignOpDiv(mixed $expr): string { - $var = $this->parseIdentifier($expr->var); - return $var . ' -= ' . $this->parseIdentifier($expr->expr); + return $this->parseAssignOp($expr, '/='); } - private function parseAssignOpMul(mixed $expr): string + private function parseAssignOpBitwiseAnd(mixed $expr): string { - $var = $this->parseIdentifier($expr->var); - return $var . ' *= ' . $this->convertVarType($var, $this->parseExpr($expr->expr)); + return $this->parseAssignOp($expr, '&='); } - private function parseAssignOpDiv(mixed $expr): string + private function fatalError(Node $node, string $msg): void + { + $this->climate->red("Fatal error: $msg in {$this->file}:" . $node->getStartLine()); + debug_print_backtrace(); + exit(255); + } + + private function parseArrayDimFetch($node): string + { + $var = $this->parseIdentifier($node->var); + if ($node->dim === null) { + $this->fatalError($node, 'Unsupported operand types: null + array'); + } + $dim = $this->parseIdentifier($node->dim); + return $var . '.offsetGet(' . $this->trimBrackets($dim) . ')'; + } + + private function parseArrayDimStore($array, $dim, $var): string { - $var = $this->parseIdentifier($expr->var); - return $var . ' /= ' . $this->parseIdentifier($expr->expr); + $id = $this->parseIdentifier($array); + return $id . '.offsetSet(' . $this->trimBrackets($dim) . ', ' . $this->trimBrackets($var) . ')'; + } + + private function parseBinaryOpShiftLeft($expr): string + { + return $this->parseBinaryOp($expr->left, $expr->right, '<<'); + } + + private function parseBinaryOpShiftRight($expr): string + { + return $this->parseBinaryOp($expr->left, $expr->right, '>>'); } private function parseBinaryOpMod(mixed $expr): string @@ -1209,7 +1289,7 @@ class Translator extends \PhpAot\Core\Translator $internalFunction = $this->isInternalFunction($funcName); $list_args = []; foreach ($args as $i => $arg) { - if ($arg->value->getType() === 'Expr_Variable') { + if ($arg->value->getType() === self::EXPR_VARIABLE) { $name = $this->parseIdentifier($arg->value); // 调用了不存在的变量,可能是引用 if (!$this->hasVar($name)) { @@ -1265,12 +1345,6 @@ class Translator extends \PhpAot\Core\Translator return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>')); } - private function parseAssignOpMod(mixed $expr): string - { - $var = $this->parseIdentifier($expr->var); - return $var . ' %= ' . $this->parseIdentifier($expr->expr); - } - private function parseBinaryOpPow(mixed $expr): string { $left = $this->parseIdentifier($expr->left); @@ -1675,7 +1749,7 @@ class Translator extends \PhpAot\Core\Translator $lines = []; foreach ($vars as $var) { $type = $var->getType(); - if ($type === 'Expr_ArrayDimFetch') { + if ($type === self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($var->var); $dim = $this->parseIdentifier($var->dim); $lines[] = $array . '.offsetUnset(' . $dim . ');'; @@ -1683,7 +1757,7 @@ class Translator extends \PhpAot\Core\Translator $object = $this->parseIdentifier($var->var); $propName = $this->parseIdentifier($var->name); $lines[] = $object . '.unsetProperty("' . $propName . '");'; - } elseif ($type === 'Expr_Variable') { + } elseif ($type === self::EXPR_VARIABLE) { $name = $this->parseIdentifier($var); $lines[] = "$name.unset();"; } else { @@ -1731,7 +1805,7 @@ class Translator extends \PhpAot\Core\Translator $this->addLocalVar($keyVar, self::TYPE_VAR); } - if ($node->valueVar->getType() == 'Expr_ArrayDimFetch') { + if ($node->valueVar->getType() == self::EXPR_ARRAY_DIM_FETCH) { $array = $this->parseIdentifier($node->valueVar->var); if (!$this->hasVar($array) or $node->valueVar->dim === null) { abort($node->valueVar); @@ -1887,20 +1961,17 @@ class Translator extends \PhpAot\Core\Translator { $vars = $expr->vars; foreach($vars as $var) { - if ($var->getType() === 'Expr_Variable') { + $type = $var->getType(); + if ($type === self::EXPR_VARIABLE) { return $this->hasVar($var->name) ? 'true' : 'false'; + } elseif ($type === self::EXPR_ARRAY_DIM_FETCH) { + return $this->parseIdentifier($var->var) . ".offsetExists(" . $this->parseIdentifier($var->dim) . ')'; } else { abort($var); } } } - private function parseAssignOpBitwiseAnd(mixed $node): string - { - $var = $this->parseIdentifier($node->var); - return $var . ' &= ' . $this->parseIdentifier($node->expr); - } - private function parseCastArray(mixed $expr): string { return $this->convertArrayExpr($this->parseIdentifier($expr->expr)); @@ -1924,7 +1995,7 @@ class Translator extends \PhpAot\Core\Translator $code .= PHP_EOL; $literalStringsCount = count($this->literalStrings); - $code .= 'php::Var _literal_strings[' . $literalStringsCount . '] = {' . PHP_EOL; + $code .= 'php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '] = {' . PHP_EOL; $this->indentLevel++; foreach ($this->literalStrings as $str => $index) { $code .= $this->getIndent() . 'php::String{ZEND_STRL("' . $this->escapeString($str) . '"), true},' . PHP_EOL; @@ -1992,4 +2063,12 @@ class Translator extends \PhpAot\Core\Translator } file_put_contents($file, $code); } + + private function parseAssignRef(mixed $expr) + { + if ($expr->var->getType() === self::EXPR_VARIABLE && $expr->expr->getType() === self::EXPR_VARIABLE) { + return $this->parseIdentifier($expr->var) . ' = &' . $this->parseIdentifier($expr->expr); + } + abort($expr); + } } diff --git a/src/functions.php b/src/functions.php index 0e79092c..4fa6cfe6 100644 --- a/src/functions.php +++ b/src/functions.php @@ -10,7 +10,8 @@ function abort($v) global $translator; $lang = $translator->getLang(); if ($translator->mode == 'cli') { - echo 'Error: Unsupported ' . $lang . ' Syntax, Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL; + echo 'Error: Unsupported ' . $lang . ' Syntax,'; + echo ' Line: ' . $translator->getLine($v) . ', Type: ' . $translator->getType($v) . PHP_EOL; if (DEBUG) { debug_print_backtrace(); var_dump($v); diff --git a/tests/aot/array_assignment_edge_cases.phpt b/tests/aot/array_assignment_edge_cases.phpt new file mode 100644 index 00000000..103fc0d5 --- /dev/null +++ b/tests/aot/array_assignment_edge_cases.phpt @@ -0,0 +1,153 @@ +--TEST-- +Array Assignment Operators Edge Cases +--FILE-- + 0]; + $edge['missing'] += 10; + var_dump($edge['missing']); // 10 + + // Test with null coalescing assignment (if supported) + $nullArray = []; + if (!isset($nullArray['test'])) { + $nullArray['test'] = 0; + } + $nullArray['test'] += 5; + var_dump($nullArray['test']); // 5 + + // Test with different data types + $mixed = [ + 'int' => 42, + 'float' => 3.14, + 'string' => '100', + 'bool_true' => true, + 'bool_false' => false, + 'null_val' => 0 + ]; + + $mixed['int'] += 8; + var_dump($mixed['int']); // 50 + + $mixed['float'] += 1.86; + var_dump($mixed['float']); // 5.0 + + $mixed['string'] += 50; + var_dump($mixed['string']); // 150 + + $mixed['bool_true'] += 5; + var_dump($mixed['bool_true']); // 6 (true = 1) + + $mixed['bool_false'] += 3; + var_dump($mixed['bool_false']); // 3 (false = 0) + + // Test with incrementing zero + $mixed['null_val'] += 1; + var_dump($mixed['null_val']); // 1 + + // Test decrementing to negative + $negative = ['value' => 5]; + $negative['value'] -= 10; + var_dump($negative['value']); // -5 + + // Test with very large numbers + $large = ['num' => PHP_INT_MAX]; + $large['num'] -= PHP_INT_MAX - 100; + var_dump($large['num']); // 100 + + // Test string concatenation with assignment + $text = ['message' => 'Hello']; + $text['message'] .= ' World'; + var_dump($text['message']); // 'Hello World' + + $text['message'] .= '!'; + var_dump($text['message']); // 'Hello World!' + + // Test with zero values + $zero = [ + 'zero_int' => 0, + 'zero_float' => 0.0, + 'zero_string' => '0' + ]; + + $zero['zero_int'] += 42; + var_dump($zero['zero_int']); // 42 + + $zero['zero_float'] += 3.5; + var_dump($zero['zero_float']); // 3.5 + + $zero['zero_string'] += 10; + var_dump($zero['zero_string']); // 10 (string "0" converts to 0, then 0+10=10) + + // Test with boolean results in assignment context + $boolTest = ['flag' => 1]; + $boolTest['flag'] += 0; // Should remain 1 + var_dump($boolTest['flag']); // 1 + + $boolTest['flag'] -= 1; // Should become 0 + var_dump($boolTest['flag']); // 0 + + // Test with constant values + define('INCREMENT_VALUE', 7); + $constTest = ['value' => 13]; + $constTest['value'] += INCREMENT_VALUE; + var_dump($constTest['value']); // 20 + + // Test chained operations + $chain = ['val' => 100]; + $chain['val'] += 10; + $chain['val'] -= 5; + $chain['val'] *= 2; + $chain['val'] /= 5; + var_dump($chain['val']); // 42 (100+10=110, 110-5=105, 105*2=210, 210/5=42) + + // Test with variable variables for array keys + $keyName = 'dynamic'; + $dynamicArray = ['dynamic' => 15]; + $dynamicArray[$keyName] += 25; + var_dump($dynamicArray[$keyName]); // 40 + + + $funcArray = ['func_key' => 8]; + $funcArray[getIndex()] += 12; + var_dump($funcArray[getIndex()]); // 20 + + echo "All array assignment edge case tests passed!\n"; +} +?> +--EXPECT-- +int(999) +int(10) +int(5) +int(50) +float(5) +int(150) +int(6) +int(3) +int(1) +int(-5) +int(100) +string(11) "Hello World" +string(12) "Hello World!" +int(42) +float(3.5) +int(10) +int(1) +int(0) +int(20) +int(42) +int(40) +int(20) +All array assignment edge case tests passed! diff --git a/tests/aot/array_assignment_operators.phpt b/tests/aot/array_assignment_operators.phpt new file mode 100644 index 00000000..5d245dde --- /dev/null +++ b/tests/aot/array_assignment_operators.phpt @@ -0,0 +1,124 @@ +--TEST-- +Array Assignment Operators +--FILE-- + 5, 'b' => 10, 'c' => 15]; + +// Test += operator on array elements +var_dump($array['a']); // 5 +$array['a'] += 10; +var_dump($array['a']); // 15 + +var_dump($array['b']); // 10 +$array['b'] += $array['a']; // 10 + 15 = 25 +var_dump($array['b']); // 25 + +// Test -= operator on array elements +$array['c'] -= 5; +var_dump($array['c']); // 10 + +$array['b'] -= $array['a']; // 25 - 15 = 10 +var_dump($array['b']); // 10 + +// Test *= operator on array elements +$array['a'] *= 2; +var_dump($array['a']); // 30 + +$array['c'] *= $array['a']; // 10 * 30 = 300 +var_dump($array['c']); // 300 + +// Test /= operator on array elements +$array['c'] /= 10; +var_dump($array['c']); // 30 + +$array['a'] /= $array['b']; // 30 / 10 = 3 +var_dump($array['a']); // 3 (as float) + +// Test %= operator on array elements +$modArray = ['x' => 25, 'y' => 7]; +$modArray['x'] %= 7; // 25 % 7 = 4 +var_dump($modArray['x']); // 4 + +$modArray['y'] %= 3; // 7 % 3 = 1 +var_dump($modArray['y']); // 1 + +// Test .= operator on array elements (string concatenation) +$strArray = ['msg' => 'Hello', 'suffix' => ' World']; +$strArray['msg'] .= $strArray['suffix']; +var_dump($strArray['msg']); // 'Hello World' + +$strArray['suffix'] .= '!'; +var_dump($strArray['suffix']); // ' World!' + +// Test with numeric indices +$numericArray = [0 => 100, 1 => 200, 2 => 300]; +$numericArray[0] += 50; +var_dump($numericArray[0]); // 150 + +$numericArray[1] -= 50; +var_dump($numericArray[1]); // 150 + +$numericArray[2] *= 2; +var_dump($numericArray[2]); // 600 + +// Test with variable keys +$key = 'dynamic'; +$dynamicArray = ['dynamic' => 42, 'static' => 10]; +$dynamicArray[$key] += 8; +var_dump($dynamicArray[$key]); // 50 + +// Test with expression as key +$exprArray = [1 => 10, 2 => 20, 3 => 30]; +$exprArray[1 + 1] += 5; // $exprArray[2] += 5 +var_dump($exprArray[2]); // 25 + +// Test nested array assignment operators +$nested = [ + 'level1' => [ + 'level2' => 100 + ] +]; +$nested['level1']['level2'] += 50; +var_dump('nested array assignment operators', $nested['level1']['level2']); // 150 + +// Test with function results +$funcArray = ['value' => 10]; +$funcArray['value'] += strlen("hello"); // 10 + 5 = 15 +var_dump($funcArray['value']); // 15 + +// Test multiple operations in sequence +$seqArray = ['counter' => 0]; +$seqArray['counter'] += 5; +$seqArray['counter'] *= 2; +$seqArray['counter'] -= 3; +$seqArray['counter'] /= 7; +var_dump($seqArray['counter']); // 1 (7*2-3=7, then 7/7=1) + +echo "All array assignment tests passed!\n"; +?> +--EXPECT-- +int(5) +int(15) +int(10) +int(25) +int(10) +int(10) +int(30) +int(300) +int(30) +int(3) +int(4) +int(1) +string(11) "Hello World" +string(7) " World!" +int(150) +int(150) +int(600) +int(50) +int(25) +string(33) "nested array assignment operators" +int(150) +int(15) +int(1) +All array assignment tests passed! \ No newline at end of file diff --git a/tests/aot/complex_array_operations.phpt b/tests/aot/complex_array_operations.phpt new file mode 100644 index 00000000..fb9302bb --- /dev/null +++ b/tests/aot/complex_array_operations.phpt @@ -0,0 +1,143 @@ +--TEST-- +Complex Array Operations with Assignment +--FILE-- + 100]; + return $arr; +} + +function main() { + // Test with string keys and various operations + $data = [ + 'numbers' => [1, 2, 3, 4, 5], + 'values' => [ + 'a' => 10, + 'b' => 20, + 'c' => 30 + ], + 'strings' => [ + 'greeting' => 'Hello', + 'name' => 'World' + ] + ]; + + // Test incrementing array values in a loop + foreach (['a', 'b', 'c'] as $key) { + $data['values'][$key] += 5; + } + var_dump($data['values']['a']); // 15 + var_dump($data['values']['b']); // 25 + var_dump($data['values']['c']); // 35 + + $ref = getReference(); + $ref[0] += 25; + var_dump($ref[0]); // 125 + + // Test with conditional array access + $conditional = ['positive' => 10, 'negative' => -5]; + $key = 'positive'; + $conditional[$key] += 15; + var_dump($conditional[$key]); // 25 + + $key = 'negative'; + $conditional[$key] -= 3; + var_dump($conditional[$key]); // -8 + + // Actually test incrementing an element at the end + $indexed = [10, 20, 30]; + $indexed[0] += 5; + var_dump($indexed[0]); // 15 + + // Test with computed keys + $computed = [ + 0 => 100, + 1 => 200, + 2 => 300 + ]; + + $index = 1; + $computed[$index] *= 2; + var_dump($computed[1]); // 400 + + // Test with array_merge results + $base = ['x' => 5, 'y' => 10]; + $additions = ['x' => 3, 'z' => 7]; + + // Add values from additions to base where keys match + foreach ($additions as $key => $value) { + if (isset($base[$key])) { + $base[$key] += $value; + } else { + $base[$key] = $value; + } + } + var_dump($base['x']); // 8 + var_dump($base['y']); // 10 + var_dump($base['z']); // 7 + + // Test with array_slice results (as variable) + $fullArray = [0 => 10, 1 => 20, 2 => 30, 3 => 40]; + $indexToModify = 2; + $fullArray[$indexToModify] += 5; + var_dump($fullArray[$indexToModify]); // 35 + + // Test with array returned from expression + $exprResult = ['count' => 0]; + $exprResult['count'] += 1; + $exprResult['count'] *= 10; + $exprResult['count'] += 1; + var_dump($exprResult['count']); // 11 + + // Test with multidimensional arrays + $matrix = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]; + + $matrix[1][2] += 10; // $matrix[1][2] is 6, becomes 16 + var_dump($matrix[1][2]); // 16 + + $matrix[0][0] *= 2; + var_dump($matrix[0][0]); // 2 + + // Test with array_keys and array_values + $assoc = ['first' => 100, 'second' => 200, 'third' => 300]; + $keys = array_keys($assoc); + + foreach ($keys as $key) { + $assoc[$key] += 10; + } + var_dump($assoc['first']); // 110 + + // Test with array references + $original = ['value' => 50]; + $ref =& $original; + $ref['value'] += 25; + var_dump($original['value']); // 75 + + echo "All complex array operation tests passed!\n"; +} +?> +--EXPECT-- +int(15) +int(25) +int(35) +int(125) +int(25) +int(-8) +int(15) +int(400) +int(8) +int(10) +int(7) +int(35) +int(11) +int(16) +int(2) +int(110) +int(75) +All complex array operation tests passed!