From e208fe11bbadd2e2aedcc484c637f2be7dc681a8 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Wed, 10 Jun 2026 17:11:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E5=AE=9E=E7=8E=B0=E5=A4=9A?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=20break=20=E5=92=8C=20continue=20=E8=AF=AD?= =?UTF-8?q?=E5=8F=A5=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加多层级跳转标志变量 _brk_flag 和 _cnt_flag - 在循环结束处插入跳转标志检查代码 - 将 switch 语句包装在 do-while(0) 中以支持多层级跳转 - 实现 break N 和 continue N 的计数器递减逻辑 - 添加测试用例覆盖多层嵌套循环和 switch 语句场景 - 支持最多 N 层的跳转指令解析和编译 --- src/Php/CompilerBase.php | 55 +++++++-- src/Php/Context/FunctionContext.php | 6 + src/Php/Parser/StdContainerTrait.php | 1 + src/Php/Translator.php | 1 + .../control_flow/break-continue-level.phpt | 106 ++++++++++++++++-- 5 files changed, 154 insertions(+), 15 deletions(-) diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 1a014bff..0a9b00fa 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -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; diff --git a/src/Php/Context/FunctionContext.php b/src/Php/Context/FunctionContext.php index d1413ff3..0c0d5ed5 100644 --- a/src/Php/Context/FunctionContext.php +++ b/src/Php/Context/FunctionContext.php @@ -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; + /** * 赋值表达式的左值,写操作,右值为读操作. */ diff --git a/src/Php/Parser/StdContainerTrait.php b/src/Php/Parser/StdContainerTrait.php index f523cac0..477f0441 100644 --- a/src/Php/Parser/StdContainerTrait.php +++ b/src/Php/Parser/StdContainerTrait.php @@ -384,6 +384,7 @@ trait StdContainerTrait } $body = $this->parseStmts($node->stmts); + $body .= $this->genLoopEndFlagCheck(); $this->indentLevel--; $code .= $this->parseBeforeStmtLines() . PHP_EOL; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 86e2d132..60dc0c6b 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -2644,6 +2644,7 @@ CODE; } } $code .= $this->parseStmts($node->stmts); + $code .= $this->genLoopEndFlagCheck(); $code .= '}' . PHP_EOL; $this->indentLevel--; $code .= $this->getIndent() . '} else {' . PHP_EOL; diff --git a/tests/aot/control_flow/break-continue-level.phpt b/tests/aot/control_flow/break-continue-level.phpt index da27c110..db92f7dd 100644 --- a/tests/aot/control_flow/break-continue-level.phpt +++ b/tests/aot/control_flow/break-continue-level.phpt @@ -1,12 +1,9 @@ --TEST-- Break and continue with numeric levels ---SKIPIF-- - 1 not supported in AOT"; -?> --FILE-- --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