feat(php): 实现多层级 break 和 continue 语句支持

- 添加多层级跳转标志变量 _brk_flag 和 _cnt_flag
- 在循环结束处插入跳转标志检查代码
- 将 switch 语句包装在 do-while(0) 中以支持多层级跳转
- 实现 break N 和 continue N 的计数器递减逻辑
- 添加测试用例覆盖多层嵌套循环和 switch 语句场景
- 支持最多 N 层的跳转指令解析和编译
pull/1/head
韩天峰 3 months ago
parent d51b8411e2
commit e208fe11bb
  1. 55
      src/Php/CompilerBase.php
  2. 6
      src/Php/Context/FunctionContext.php
  3. 1
      src/Php/Parser/StdContainerTrait.php
  4. 1
      src/Php/Translator.php
  5. 106
      tests/aot/control_flow/break-continue-level.phpt

@ -2374,7 +2374,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$code .= ') {' . PHP_EOL;
$code .= $this->parseBlockStmts($stmts);
$code .= $this->genLoopEndFlagCheck();
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
@ -3131,6 +3131,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$code = $this->parseBeforeStmtLines() . PHP_EOL;
$code .= 'while (' . $cond . ') {' . PHP_EOL;
$code .= $this->parseBlockStmts($stmts);
$code .= $this->genLoopEndFlagCheck();
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
@ -3148,6 +3149,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$code = $this->parseBeforeStmtLines() . PHP_EOL;
$code .= 'do {' . PHP_EOL;
$code .= $this->parseBlockStmts($stmts);
$code .= $this->genLoopEndFlagCheck();
$code .= $this->getIndent() . '} while (' . $cond . ');' . PHP_EOL;
return $code;
@ -3858,6 +3860,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$body = $this->parseStmts($node->stmts);
$body .= $this->genLoopEndFlagCheck();
$this->indentLevel--;
$code .= $this->parseBeforeStmtLines() . PHP_EOL;
@ -3937,7 +3940,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$code = $this->parseBeforeStmtLines() . PHP_EOL;
if ($type === self::TYPE_INT or $type === self::TYPE_BOOL) {
$code .= 'switch (' . $tmp_var . ') {' . PHP_EOL;
$code .= 'do {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'switch (' . $tmp_var . ') {' . PHP_EOL;
$this->indentLevel++;
foreach ($v->cases as $case) {
if (empty($case->cond)) {
@ -3946,6 +3951,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$condType = $case->cond->getType();
if ($condType !== 'Scalar_Int' and $condType !== 'Scalar_Float') {
$this->context->localVars = $localVars;
$this->indentLevel -= 2;
goto _fail;
}
$code .= $this->getIndent() . 'case ' . $this->parseScalar($case->cond) . ': {' . PHP_EOL;
@ -3954,7 +3960,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$code .= $this->getIndent() . '}' . PHP_EOL;
}
$this->indentLevel--;
$code .= $this->getIndent() . '}';
$code .= $this->getIndent() . '}' . PHP_EOL;
$code .= $this->genLoopEndFlagCheck();
$this->indentLevel--;
$code .= $this->getIndent() . '} while(0);' . PHP_EOL;
return $var_def . $code;
}
@ -4013,6 +4022,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
}
$code .= $this->genLoopEndFlagCheck();
$this->indentLevel--;
$code .= $this->getIndent() . '} while (0);';
@ -4088,9 +4098,9 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$num = $v->num;
if ($num) {
$value = $this->parseIdentifier($num);
if ($value > 1) {
$this->fatalError($v, 'Cannot break more than 1 level');
if ($num->value > 1) {
$this->context->hasMultiLevelBreak = true;
return '_brk_flag = ' . ($num->value - 1) . '; break;';
}
}
@ -4102,12 +4112,35 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->context->inLoop) {
$this->fatalError($v, 'Cannot continue outside loop');
}
if ($v->num and $v->num->value > 1) {
$this->fatalError($v, 'Cannot continue more than 1 level');
$num = $v->num;
if ($num) {
if ($num->value > 1) {
$this->context->hasMultiLevelContinue = true;
return '_cnt_flag = ' . ($num->value - 1) . '; break;';
}
}
return 'continue;';
}
/**
* Emit flag-propagation checks at the end of a loop body.
*
* Translates multi-level break / continue into plain break / continue
* by decrementing a counter at each loop boundary until it reaches zero.
*/
protected function genLoopEndFlagCheck(): string
{
$code = '';
$indent = $this->getIndent();
if ($this->context->hasMultiLevelBreak) {
$code .= "{$indent}if (_brk_flag > 0) { _brk_flag--; break; }" . PHP_EOL;
}
if ($this->context->hasMultiLevelContinue) {
$code .= "{$indent}if (_cnt_flag > 0) { _cnt_flag--; if (_cnt_flag == 0) continue; else break; }" . PHP_EOL;
}
return $code;
}
protected function parseScalarFloat(Node\Scalar\Float_ $expr): string
{
$value = $expr->value;
@ -5273,6 +5306,12 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function genScopeVarDecl(): string
{
$code = '';
if ($this->context->hasMultiLevelBreak) {
$code .= $this->getIndent() . 'int _brk_flag = 0;' . PHP_EOL;
}
if ($this->context->hasMultiLevelContinue) {
$code .= $this->getIndent() . 'int _cnt_flag = 0;' . PHP_EOL;
}
foreach ($this->context->localVars as $name => $type) {
if (isset($this->context->arguments[$name])) {
continue;

@ -51,6 +51,12 @@ class FunctionContext
public bool $inLoop = false;
public bool $inClosure = false;
/** True if any break N (N > 1) appears in this function. */
public bool $hasMultiLevelBreak = false;
/** True if any continue N (N > 1) appears in this function. */
public bool $hasMultiLevelContinue = false;
/**
* 赋值表达式的左值,写操作,右值为读操作.
*/

@ -384,6 +384,7 @@ trait StdContainerTrait
}
$body = $this->parseStmts($node->stmts);
$body .= $this->genLoopEndFlagCheck();
$this->indentLevel--;
$code .= $this->parseBeforeStmtLines() . PHP_EOL;

@ -2644,6 +2644,7 @@ CODE;
}
}
$code .= $this->parseStmts($node->stmts);
$code .= $this->genLoopEndFlagCheck();
$code .= '}' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '} else {' . PHP_EOL;

@ -1,12 +1,9 @@
--TEST--
Break and continue with numeric levels
--SKIPIF--
<?php
echo "skip Break/continue with levels > 1 not supported in AOT";
?>
--FILE--
<?php
// break 2
// break 2 from nested for
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($i == 1 && $j == 2) {
@ -14,7 +11,102 @@ for ($i = 0; $i < 3; $i++) {
}
}
}
echo "done\n";
echo "break-2-for: done\n";
// continue 2 from nested for
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($i == 1 && $j == 1) {
continue 2;
}
}
}
echo "continue-2-for: done\n";
// break 2 from nested while
$i = 0;
while ($i < 3) {
$j = 0;
while ($j < 3) {
if ($i == 1 && $j == 2) {
break 2;
}
$j++;
}
$i++;
}
echo "break-2-while: done\n";
// continue 2 from nested while
$i = 0;
while ($i < 3) {
$j = 0;
while ($j < 3) {
$j++;
if ($i == 1 && $j == 2) {
continue 2;
}
}
$i++;
}
echo "continue-2-while: done\n";
// break 2 from nested do-while
$i = 0;
do {
$j = 0;
do {
if ($i == 1 && $j == 2) {
break 2;
}
$j++;
} while ($j < 3);
$i++;
} while ($i < 3);
echo "break-2-do-while: done\n";
// continue 3 from triple-nested for
for ($a = 0; $a < 2; $a++) {
for ($b = 0; $b < 2; $b++) {
for ($c = 0; $c < 2; $c++) {
if ($a == 0 && $b == 1 && $c == 0) {
continue 3;
}
}
}
}
echo "continue-3-for: done\n";
// break 3 from triple-nested for
for ($a = 0; $a < 2; $a++) {
for ($b = 0; $b < 2; $b++) {
for ($c = 0; $c < 2; $c++) {
if ($a == 0 && $b == 1 && $c == 0) {
break 3;
}
}
}
}
echo "break-3-for: done\n";
// break 2 from switch inside for
for ($i = 0; $i < 3; $i++) {
switch ($i) {
case 1:
break 2;
default:
break;
}
}
echo "break-2-switch: done\n";
?>
--EXPECT--
done
break-2-for: done
continue-2-for: done
break-2-while: done
continue-2-while: done
break-2-do-while: done
continue-3-for: done
break-3-for: done
break-2-switch: done

Loading…
Cancel
Save