数组操作处理

pull/1/head
韩天峰 8 months ago
parent 19e0dc48af
commit c9c558103c
  1. 10
      examples/fannkuchredux.php
  2. 12
      examples/simple.php
  3. 1
      src/Core/Translator.php
  4. 189
      src/Php/Translator.php
  5. 3
      src/functions.php
  6. 153
      tests/aot/array_assignment_edge_cases.phpt
  7. 124
      tests/aot/array_assignment_operators.phpt
  8. 143
      tests/aot/complex_array_operations.phpt

@ -25,15 +25,11 @@ function fannkuch($n)
while ($i < $n) { while ($i < $n) {
$perm[$i] = $perm1[$i]; $perm[$i] = $perm1[$i];
$i += 1; $i += 1;
//
// var_dump($perm, $perm1);
// var_dump('[i] i=' . $i);
} }
// Count flips and update max and checksum // Count flips and update max and checksum
$f = 0; $f = 0;
$k = $perm[0]; $k = $perm[0];
// var_dump('[xxx] k=' . $k);
while ($k != 0) { while ($k != 0) {
$i = 0; $i = 0;
@ -46,11 +42,8 @@ function fannkuch($n)
$k = $perm[0]; $k = $perm[0];
$f += 1; $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 ($f > $flips) $flips = $f;
if (($nperm & 0x1) == 0) $checksum += $f; else $checksum -= $f; if (($nperm & 0x1) == 0) $checksum += $f; else $checksum -= $f;
@ -82,6 +75,7 @@ function main()
{ {
global $argc, $argv; global $argc, $argv;
$n = $argc > 2 ? $argv[2] : 7; $n = $argc > 2 ? $argv[2] : 7;
$begin = microtime(true);
printf("Pfannkuchen(%d) = %d\n", $n, fannkuch($n)); printf("Pfannkuchen(%d) = %d\n", $n, fannkuch($n));
echo "Time: ", microtime(true) - $begin, "\n";
} }

@ -1,13 +1,9 @@
<?php <?php
function Ack($m, $n){
if($m == 0) return $n+1;
if($n == 0) return Ack($m-1, 1);
return Ack($m - 1, Ack($m, ($n - 1)));
}
function main() { function main() {
$n = 7; $newArray = [];
$r = Ack(3, $n); $newArray[] = 1;
print "Ack(3,$n): $r\n"; $newArray[] += [1]; // This is different: it's adding to index 1
var_dump($newArray); // [1, 1]
} }

@ -32,5 +32,4 @@ abstract class Translator
{ {
return str_repeat($this->indentStr, $this->indentLevel); return str_repeat($this->indentStr, $this->indentLevel);
} }
} }

@ -26,6 +26,9 @@ class Translator extends \PhpAot\Core\Translator
const string VALUE_NAN = 'std::numeric_limits<double>::quiet_NaN()'; const string VALUE_NAN = 'std::numeric_limits<double>::quiet_NaN()';
const string VALUE_INF = 'std::numeric_limits<double>::infinity()'; const string VALUE_INF = 'std::numeric_limits<double>::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'; private string $phpxDir = '~/workspace/projects/phpx';
protected string $lang = 'PHP'; protected string $lang = 'PHP';
@ -91,6 +94,7 @@ class Translator extends \PhpAot\Core\Translator
private int $optimizeLevel = 0; private int $optimizeLevel = 0;
private int $floatPrecision = 17; private int $floatPrecision = 17;
private bool $debugInfo = true; private bool $debugInfo = true;
private bool $noLiteralStrings = false;
private bool $verbose = false; private bool $verbose = false;
private string $file; private string $file;
private string $dir; private string $dir;
@ -151,6 +155,12 @@ class Translator extends \PhpAot\Core\Translator
'required' => false, 'required' => false,
'noValue' => true, 'noValue' => true,
], ],
'noLiteralStrings' => [
'longPrefix' => 'no-literal-strings',
'description' => 'Disable literal strings optimization',
'required' => false,
'noValue' => true,
],
]); ]);
$this->preprocessArgvAdvanced(); $this->preprocessArgvAdvanced();
@ -159,6 +169,8 @@ class Translator extends \PhpAot\Core\Translator
$this->showUsage(); $this->showUsage();
exit(0); exit(0);
} }
// $this->noLiteralStrings = $climate->arguments->get('no-literal-strings');
$this->noLiteralStrings = true;
$this->optimizeLevel = $climate->arguments->get('optimize'); $this->optimizeLevel = $climate->arguments->get('optimize');
$this->definedFunctions = array_flip(get_defined_functions()['internal']); $this->definedFunctions = array_flip(get_defined_functions()['internal']);
} }
@ -183,6 +195,7 @@ class Translator extends \PhpAot\Core\Translator
$climate->tab()->out('-o, --output <file> Output binary name (default: input basename)'); $climate->tab()->out('-o, --output <file> Output binary name (default: input basename)');
$climate->tab()->out('-v, --verbose Verbose output'); $climate->tab()->out('-v, --verbose Verbose output');
$climate->tab()->out('-h, --help Show this help message'); $climate->tab()->out('-h, --help Show this help message');
$climate->tab()->out('--no-literal-strings Disable literal strings optimization');
$climate->br(); $climate->br();
$climate->bold('EXAMPLES:'); $climate->bold('EXAMPLES:');
@ -234,7 +247,7 @@ class Translator extends \PhpAot\Core\Translator
} }
$literalStringsCount = count($this->literalStrings); $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; return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
} }
@ -414,7 +427,7 @@ class Translator extends \PhpAot\Core\Translator
{ {
$type = $expr->getType(); $type = $expr->getType();
switch ($type) { switch ($type) {
case 'Expr_Variable': case self::EXPR_VARIABLE:
return $this->escapeVarName($expr->name); return $this->escapeVarName($expr->name);
case 'Name': case 'Name':
case 'Identifier': case 'Identifier':
@ -424,8 +437,12 @@ class Translator extends \PhpAot\Core\Translator
case 'Scalar_Float': case 'Scalar_Float':
return $this->parseScalarFloat($expr); return $this->parseScalarFloat($expr);
case 'Scalar_String': case 'Scalar_String':
if ($this->noLiteralStrings) {
return '"' . $this->escapeString($expr->value) . '"';
} else {
$index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value); $index = $this->literalStrings[$expr->value] ?? $this->addLiteralString($expr->value);
return '_literal_strings[' . $index . ']'; return self::LITERAL_STRINGS . '[' . $index . ']';
}
case 'Expr_ConstFetch': case 'Expr_ConstFetch':
return $this->parseConstFetch($expr); return $this->parseConstFetch($expr);
default: default:
@ -534,6 +551,8 @@ class Translator extends \PhpAot\Core\Translator
return $this->parseIsset($expr); return $this->parseIsset($expr);
case 'Expr_Assign': case 'Expr_Assign':
return $this->parseAssign($expr); return $this->parseAssign($expr);
case 'Expr_AssignRef':
return $this->parseAssignRef($expr);
case 'Expr_Print': case 'Expr_Print':
return $this->parsePrint($expr); return $this->parsePrint($expr);
case 'Expr_BinaryOp_Equal': case 'Expr_BinaryOp_Equal':
@ -601,7 +620,7 @@ class Translator extends \PhpAot\Core\Translator
return $this->parseBinaryOpMinus($expr); return $this->parseBinaryOpMinus($expr);
case 'Expr_Array': case 'Expr_Array':
return $this->parseArray($expr); return $this->parseArray($expr);
case 'Expr_ArrayDimFetch': case self::EXPR_ARRAY_DIM_FETCH:
return $this->parseArrayDimFetch($expr); return $this->parseArrayDimFetch($expr);
case 'Expr_PropertyFetch': case 'Expr_PropertyFetch':
return $this->parsePropertyFetch($expr); return $this->parsePropertyFetch($expr);
@ -638,7 +657,7 @@ class Translator extends \PhpAot\Core\Translator
case 'Scalar_Int': case 'Scalar_Int':
case 'Scalar_Float': case 'Scalar_Float':
case 'Scalar_String': case 'Scalar_String':
case 'Expr_Variable': case self::EXPR_VARIABLE:
return $this->parseIdentifier($expr); return $this->parseIdentifier($expr);
case 'Scalar_MagicConst_File': case 'Scalar_MagicConst_File':
return $this->parseMagicConstFile($expr); return $this->parseMagicConstFile($expr);
@ -672,23 +691,25 @@ class Translator extends \PhpAot\Core\Translator
$left = $v->var; $left = $v->var;
$right = $v->expr; $right = $v->expr;
if ($left->getType() === 'Expr_ArrayDimFetch') { if ($left->getType() === self::EXPR_ARRAY_DIM_FETCH) {
$array = $this->parseIdentifier($left->var); $array = $this->parseIdentifier($left->var);
$code = ''; $code = '';
// 这是 PHP 的初始化+赋值写法,需要先创建数组 // 这是 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); $this->addLocalVar($array, self::TYPE_ARRAY);
} }
$value = $this->trimBrackets($this->parseExpr($right));
if ($left->dim === null) { if ($left->dim === null) {
return $code . "$array.offsetSet(php::null, " . $this->parseExpr($right) . ")"; return $code . "$array.offsetSet(php::null, $value)";
} else { } else {
$dim = $this->parseIdentifier($left->dim); $dim = $this->trimBrackets($this->parseIdentifier($left->dim));
return $code . "$array.offsetSet($dim, " . $this->parseExpr($right) . ")"; return $code . "$array.offsetSet($dim, $value)";
} }
} elseif ($left->getType() === 'Expr_PropertyFetch') { } elseif ($left->getType() === 'Expr_PropertyFetch') {
$array = $this->parseIdentifier($left->var); $array = $this->parseIdentifier($left->var);
$propName = $this->parseIdentifier($left->name); $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') { } elseif ($right->getType() === 'Expr_Assign') {
$chain[] = $left; $chain[] = $left;
while ($right->getType() === 'Expr_Assign') { while ($right->getType() === 'Expr_Assign') {
@ -902,7 +923,7 @@ class Translator extends \PhpAot\Core\Translator
return self::TYPE_OBJECT; return self::TYPE_OBJECT;
case 'Expr_Assign': case 'Expr_Assign':
return $this->detectVarType($expr->var); return $this->detectVarType($expr->var);
case 'Expr_Variable': case self::EXPR_VARIABLE:
return $this->detectVarType($expr); return $this->detectVarType($expr);
case 'Expr_ConstFetch': case 'Expr_ConstFetch':
return $this->detectConstType($expr); return $this->detectConstType($expr);
@ -937,6 +958,11 @@ class Translator extends \PhpAot\Core\Translator
if ($assocArray) { if ($assocArray) {
// TODO 混杂模式数组赋值 // TODO 混杂模式数组赋值
$key = $item->key ? $this->parseIdentifier($item->key) : 'php::null'; $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 . ', ' . $list[] = $this->getIndent() . '{ ' . $key . ', ' .
self::TYPE_VAR . '(' . $value . ') }'; self::TYPE_VAR . '(' . $value . ') }';
} else { } else {
@ -1048,13 +1074,6 @@ class Translator extends \PhpAot\Core\Translator
return 'php::concat(' . $left . ', ' . $right . ')'; 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 private function parseFor(mixed $v): string
{ {
$init = $v->init; $init = $v->init;
@ -1113,16 +1132,54 @@ class Translator extends \PhpAot\Core\Translator
return '++' . $this->parseIdentifier($expr->var); return '++' . $this->parseIdentifier($expr->var);
} }
private function removeAssignOp(string $op): string
{
return str_replace('=', '', $op);
}
private function parseAssignOp(mixed $node, string $op): string private function parseAssignOp(mixed $node, string $op): string
{ {
$var = $this->parseIdentifier($node->var); $var = $this->parseIdentifier($node->var);
$expr = $this->parseIdentifier($node->expr); $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); $type = $this->detectVarType($node->var);
return $var . ' ' . $op . ' ' . $this->convertExprType($expr, $type, $this->detectExprType($node->expr)); 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 { } else {
return $var . ' ' . $op . ' (' . $expr . ')'; return $var . ' ' . $op . ' (' . $expr . ')';
} }
// TODO 属性设置
}
private function parseAssignOpConcat(mixed $expr): string
{
return $this->parseAssignOp($expr, '.=');
} }
private function parseAssignOpPlus(mixed $expr): string private function parseAssignOpPlus(mixed $expr): string
@ -1130,39 +1187,62 @@ class Translator extends \PhpAot\Core\Translator
return $this->parseAssignOp($expr, '+='); return $this->parseAssignOp($expr, '+=');
} }
private function parseArrayDimFetch($node): string private function parseAssignOpMinus(mixed $expr): string
{ {
$var = $this->parseIdentifier($node->var); return $this->parseAssignOp($expr, '-=');
$dim = $this->parseIdentifier($node->dim);
return $var . '.offsetGet(' . $dim . ')';
} }
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 $this->parseAssignOp($expr, '/=');
return $var . ' -= ' . $this->parseIdentifier($expr->expr);
} }
private function parseAssignOpMul(mixed $expr): string private function parseAssignOpBitwiseAnd(mixed $expr): string
{ {
$var = $this->parseIdentifier($expr->var); return $this->parseAssignOp($expr, '&=');
return $var . ' *= ' . $this->convertVarType($var, $this->parseExpr($expr->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($expr->var); $var = $this->parseIdentifier($node->var);
return $var . ' /= ' . $this->parseIdentifier($expr->expr); 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
{
$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 private function parseBinaryOpMod(mixed $expr): string
@ -1209,7 +1289,7 @@ class Translator extends \PhpAot\Core\Translator
$internalFunction = $this->isInternalFunction($funcName); $internalFunction = $this->isInternalFunction($funcName);
$list_args = []; $list_args = [];
foreach ($args as $i => $arg) { foreach ($args as $i => $arg) {
if ($arg->value->getType() === 'Expr_Variable') { if ($arg->value->getType() === self::EXPR_VARIABLE) {
$name = $this->parseIdentifier($arg->value); $name = $this->parseIdentifier($arg->value);
// 调用了不存在的变量,可能是引用 // 调用了不存在的变量,可能是引用
if (!$this->hasVar($name)) { if (!$this->hasVar($name)) {
@ -1265,12 +1345,6 @@ class Translator extends \PhpAot\Core\Translator
return $this->convertBoolExpr($this->parseBinaryOp($expr->left, $expr->right, '>')); 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 private function parseBinaryOpPow(mixed $expr): string
{ {
$left = $this->parseIdentifier($expr->left); $left = $this->parseIdentifier($expr->left);
@ -1675,7 +1749,7 @@ class Translator extends \PhpAot\Core\Translator
$lines = []; $lines = [];
foreach ($vars as $var) { foreach ($vars as $var) {
$type = $var->getType(); $type = $var->getType();
if ($type === 'Expr_ArrayDimFetch') { if ($type === self::EXPR_ARRAY_DIM_FETCH) {
$array = $this->parseIdentifier($var->var); $array = $this->parseIdentifier($var->var);
$dim = $this->parseIdentifier($var->dim); $dim = $this->parseIdentifier($var->dim);
$lines[] = $array . '.offsetUnset(' . $dim . ');'; $lines[] = $array . '.offsetUnset(' . $dim . ');';
@ -1683,7 +1757,7 @@ class Translator extends \PhpAot\Core\Translator
$object = $this->parseIdentifier($var->var); $object = $this->parseIdentifier($var->var);
$propName = $this->parseIdentifier($var->name); $propName = $this->parseIdentifier($var->name);
$lines[] = $object . '.unsetProperty("' . $propName . '");'; $lines[] = $object . '.unsetProperty("' . $propName . '");';
} elseif ($type === 'Expr_Variable') { } elseif ($type === self::EXPR_VARIABLE) {
$name = $this->parseIdentifier($var); $name = $this->parseIdentifier($var);
$lines[] = "$name.unset();"; $lines[] = "$name.unset();";
} else { } else {
@ -1731,7 +1805,7 @@ class Translator extends \PhpAot\Core\Translator
$this->addLocalVar($keyVar, self::TYPE_VAR); $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); $array = $this->parseIdentifier($node->valueVar->var);
if (!$this->hasVar($array) or $node->valueVar->dim === null) { if (!$this->hasVar($array) or $node->valueVar->dim === null) {
abort($node->valueVar); abort($node->valueVar);
@ -1887,20 +1961,17 @@ class Translator extends \PhpAot\Core\Translator
{ {
$vars = $expr->vars; $vars = $expr->vars;
foreach($vars as $var) { foreach($vars as $var) {
if ($var->getType() === 'Expr_Variable') { $type = $var->getType();
if ($type === self::EXPR_VARIABLE) {
return $this->hasVar($var->name) ? 'true' : 'false'; 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 { } else {
abort($var); 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 private function parseCastArray(mixed $expr): string
{ {
return $this->convertArrayExpr($this->parseIdentifier($expr->expr)); return $this->convertArrayExpr($this->parseIdentifier($expr->expr));
@ -1924,7 +1995,7 @@ class Translator extends \PhpAot\Core\Translator
$code .= PHP_EOL; $code .= PHP_EOL;
$literalStringsCount = count($this->literalStrings); $literalStringsCount = count($this->literalStrings);
$code .= 'php::Var _literal_strings[' . $literalStringsCount . '] = {' . PHP_EOL; $code .= 'php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '] = {' . PHP_EOL;
$this->indentLevel++; $this->indentLevel++;
foreach ($this->literalStrings as $str => $index) { foreach ($this->literalStrings as $str => $index) {
$code .= $this->getIndent() . 'php::String{ZEND_STRL("' . $this->escapeString($str) . '"), true},' . PHP_EOL; $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); 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);
}
} }

@ -10,7 +10,8 @@ function abort($v)
global $translator; global $translator;
$lang = $translator->getLang(); $lang = $translator->getLang();
if ($translator->mode == 'cli') { 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) { if (DEBUG) {
debug_print_backtrace(); debug_print_backtrace();
var_dump($v); var_dump($v);

@ -0,0 +1,153 @@
--TEST--
Array Assignment Operators Edge Cases
--FILE--
<?php
// Test edge cases for array assignment operators
// Test with function return values as indices
function getIndex() {
return 'func_key';
}
function main() {
// Test assignment to non-existent keys (should create the key)
$empty = [];
$empty['new'] += 999; // This should behave differently in PHP vs AOT - in PHP this starts at 0
var_dump($empty['new']);
// Since this would start at 0 in PHP, result should be 0 + 10 = 10
// However, for AOT purposes we'll test with pre-existing 0 values
$edge = ['missing' => 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!

@ -0,0 +1,124 @@
--TEST--
Array Assignment Operators
--FILE--
<?php
// Test array assignment operators
$array = ['a' => 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!

@ -0,0 +1,143 @@
--TEST--
Complex Array Operations with Assignment
--FILE--
<?php
// Test complex array operations with assignment operators
// Test with array returned from function
function getReference() {
$arr = [0 => 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!
Loading…
Cancel
Save