diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index 604dca4b..c40ee9d8 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -3,11 +3,17 @@ namespace PhpAot\Php; use PhpParser\Node\Expr; +use PhpParser\NodeAbstract; trait AstNodeType { - protected function isVarExpr(Expr $expr): bool + protected function isVarExpr(NodeAbstract $expr): bool { return $expr instanceof Expr\Variable; } + + protected function isPropertyFetch(NodeAbstract $expr): bool + { + return $expr instanceof Expr\PropertyFetch; + } } \ No newline at end of file diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 21752db6..e6730160 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -134,7 +134,6 @@ class CompilerBase extends \PhpAot\Core\Translator protected array $beforeStmtLines = []; protected array $afterStmtLines = []; protected bool $inLoop = false; - protected bool $inSwitch = false; protected bool $stubFile = false; protected bool $stubFileIncluded = false; protected Parser $parser; @@ -207,11 +206,12 @@ class CompilerBase extends \PhpAot\Core\Translator return isset($this->nativeFunctions[$name]); } - protected function resetScope(): void + protected function resetFunction(): void { $this->localVars = []; $this->arguments = []; $this->tmpVarIndex = 0; + $this->inLoop = false; } protected function resetFile(): void @@ -261,7 +261,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseFunction(Node\FunctionLike $v): string { - $this->resetScope(); + $this->resetFunction(); $this->function = $this->parseIdentifier($v->name); $name = $this->getFunctionName($v); if (isset($this->nativeFunctions[$name])) { @@ -403,9 +403,26 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->getIndent() . '// ' . $class . ' [' . $v->getStartLine() . ':' . $v->getEndLine() . ']'; } + /** + * 在 for/foreach 等包含子语句的语句,之前检查当前待添加的代码是否为空, + * 如果不为空,需要将语句追加到 {} 作用域符号之前 + * @return string + */ + protected function parseBeforeStmtLines(): string + { + if ($this->beforeStmtLines) { + $code = implode(PHP_EOL, $this->beforeStmtLines); + $this->beforeStmtLines = []; + return $code . PHP_EOL; + } else { + return ''; + } + } + protected function parseStmts(array $stmts): string { $lines = []; + $inLoopTop = $this->inLoop; foreach ($stmts as $v) { $class = $v->getType(); $this->beforeStmtLines = []; @@ -426,27 +443,27 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Stmt_For': $this->inLoop = true; $result = $this->parseFor($v); - $this->inLoop = false; + $this->inLoop = $inLoopTop; break; case 'Stmt_Foreach': $this->inLoop = true; $result = $this->parseForeach($v); - $this->inLoop = false; + $this->inLoop = $inLoopTop; break; case 'Stmt_Switch': - $this->inSwitch = true; + $this->inLoop = true; $result = $this->parseSwitch($v); - $this->inSwitch = false; + $this->inLoop = $inLoopTop; break; case 'Stmt_While': $this->inLoop = true; $result = $this->parseWhile($v); - $this->inLoop = false; + $this->inLoop = $inLoopTop; break; case 'Stmt_Do': $this->inLoop = true; $result = $this->parseDo($v); - $this->inLoop = false; + $this->inLoop = $inLoopTop; break; case 'Stmt_If': $result = $this->parseIf($v); @@ -1115,7 +1132,7 @@ class CompilerBase extends \PhpAot\Core\Translator $name = $this->parseIdentifier($left); $type = $this->detectExprType($expr->expr); // for 循环的变量声明,必须在循环体之外,不能创建 scope tmp var - if ($this->hasVar($name)) { + if (!$this->hasVar($name)) { $this->addLocalVar($name, $type); } $code .= $name . ' = ' . '(' . $this->parseIdentifier($expr->expr) . ');'; @@ -1123,6 +1140,7 @@ class CompilerBase extends \PhpAot\Core\Translator $list_cond[] = $this->parseExpr($expr); } + $code .= $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'for (;'; $code .= implode(', ', $list_cond); $code .= '; '; @@ -1418,7 +1436,14 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parsePostInc($expr): string { - return $this->parseIdentifier($expr->var) . '++'; + if ($this->isVarExpr($expr->var)) { + return $this->parseIdentifier($expr->var) . '++'; + } elseif ($this->isPropertyFetch($expr->var)) { + $obj = $this->parseIdentifier($expr->var->var); + $prop = $this->identifierToStr($expr->var->name); + return $obj . '.setProperty(' . $prop . ', ' . $obj. '.getProperty(' . $prop . ') + 1)'; + } + abort($expr, "Post-increment operator is not supported for non-variable expressions"); } protected function parsePostDec($expr): string @@ -1482,7 +1507,8 @@ class CompilerBase extends \PhpAot\Core\Translator { $cond = $this->parseExpr($v->cond); - $code = 'if (' . $cond . ') {' . PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'if (' . $cond . ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($v->stmts); $this->indentLevel--; @@ -1549,7 +1575,8 @@ class CompilerBase extends \PhpAot\Core\Translator $cond = $this->parseExpr($v->cond); $stmts = $v->stmts; - $code = 'while (' . $cond . ') {' . PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'while (' . $cond . ') {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $this->indentLevel--; @@ -1670,8 +1697,8 @@ class CompilerBase extends \PhpAot\Core\Translator { $stmts = $v->stmts; $cond = $this->parseExpr($v->cond); - - $code = 'do {' . PHP_EOL; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'do {' . PHP_EOL; $this->indentLevel++; $code .= $this->parseStmts($stmts); $this->indentLevel--; @@ -1892,7 +1919,7 @@ class CompilerBase extends \PhpAot\Core\Translator return 'php::exit(' . $this->parseIdentifier($node->expr) . ')'; } - protected function parseUnset(Node $node): string + protected function parseUnset(Node\Stmt\Unset_ $node): string { $vars = $node->vars; $lines = []; @@ -1916,7 +1943,7 @@ class CompilerBase extends \PhpAot\Core\Translator return implode(PHP_EOL . $this->getIndent(), $lines); } - protected function parsePropertyFetch(Node $expr): string + protected function parsePropertyFetch(Node\Expr\PropertyFetch $expr): string { return $this->convertToObject($expr->var) . '.getProperty("' . $this->parseIdentifier($expr->name) . '")'; } @@ -1958,18 +1985,28 @@ class CompilerBase extends \PhpAot\Core\Translator if ($node->byRef) { $this->fatalError($node, 'Cannot use & with foreach'); } - if ($node->keyVar) { - $keyVar = $this->parseIdentifier($node->keyVar); + if ($this->isVarExpr($node->expr)) { + $name = $this->parseIdentifier($node->expr); + if ($this->hasVar($name)) { + $type = $this->getVarType($name); + if ($type === self::TYPE_OBJECT) { + return $this->parseForeachObject($node); + } + } } - $valueVar = $this->parseIdentifier($node->valueVar); $iteratorVar = $this->genTmpVarName(); $stmts = $node->stmts; $code = ''; + if ($node->keyVar) { + $keyVar = $this->parseIdentifier($node->keyVar); + } + $valueVar = $this->parseIdentifier($node->valueVar); $expr = $this->parseIdentifier($node->expr); $code .= self::TYPE_ARRAY . " $iteratorVar = " . $expr . ';' . PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; $code .= 'for (auto iter = ' . $iteratorVar . '.begin(); iter != ' . $iteratorVar . '.end(); ++iter) {' . PHP_EOL; $this->indentLevel++; @@ -2035,9 +2072,10 @@ class CompilerBase extends \PhpAot\Core\Translator // 保存作用域,switch 可能会解析失败,在这个过程中会增加变量,需重置 $localVars = $this->localVars; + $code = $this->parseBeforeStmtLines() . PHP_EOL; if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT) { - $code = 'switch (' . $tmp_var . ') {' . PHP_EOL; + $code .= 'switch (' . $tmp_var . ') {' . PHP_EOL; $this->indentLevel++; foreach ($v->cases as $case) { if (empty($case->cond)) { @@ -2105,7 +2143,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseBreak(mixed $v): string { - if (!$this->inLoop and !$this->inSwitch) { + if (!$this->inLoop) { $this->fatalError($v, 'Cannot break outside loop'); } $num = $v->num; @@ -2208,7 +2246,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertToObject(Node $object): string { $id = $this->parseIdentifier($object); - if (!$this->hasVar($id)) { + if ($this->isVarExpr($object) and !$this->hasVar($id)) { $this->addLocalVar($id, self::TYPE_OBJECT); return $id; } @@ -2245,7 +2283,7 @@ class CompilerBase extends \PhpAot\Core\Translator abort($expr); } - protected function parseMethodCall(mixed $expr): string + protected function parseMethodCall(Node\Expr\MethodCall $expr): string { $object = $this->convertToObject($expr->var); $method = $this->parseIdentifier($expr->name); @@ -2333,7 +2371,8 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseTryCatch(mixed $v): string { - $code = 'zend_try {'; + $code = $this->parseBeforeStmtLines() . PHP_EOL; + $code .= 'zend_try {'; $stmts = $v->stmts; $code .= PHP_EOL; @@ -2375,6 +2414,8 @@ class CompilerBase extends \PhpAot\Core\Translator } $code = $this->getIndent() . $var . ' = ' . $exVar . ';' . PHP_EOL; + $code .= $this->parseBeforeStmtLines() . PHP_EOL; + $code .= $this->getIndent() . 'if (' . $var . ' && '; foreach ($types as $type) { $code .= 'php::instanceOf(' . $var . ', "' . $this->parseIdentifier($type) . '")'; @@ -2566,6 +2607,4 @@ class CompilerBase extends \PhpAot\Core\Translator } abort($expr); } - - } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index b6508b69..b6b7df23 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -5,6 +5,7 @@ namespace PhpAot\Php; use MJS\TopSort\Implementations\StringSort; use PhpParser\Modifiers; use PhpParser\Node; +use PhpParser\Node\Stmt\Foreach_; use PhpParser\NodeTraverser; class Translator extends Preprocessor @@ -779,7 +780,7 @@ class Translator extends Preprocessor } } - private function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void + protected function parseClassMethod(Node\Stmt\ClassMethod $v, array &$methodCodes): void { $name = $this->getMethodName($v); $this->method = $name; @@ -794,7 +795,7 @@ class Translator extends Preprocessor $this->method = ''; } - private function parseIdentifierList(array $implements): array + protected function parseIdentifierList(array $implements): array { $list = []; foreach ($implements as $implement) { @@ -803,7 +804,7 @@ class Translator extends Preprocessor return $list; } - private function parseInterface(Node\Stmt\Interface_ $v): void + protected function parseInterface(Node\Stmt\Interface_ $v): void { $name = $this->parseIdentifier($v->name); $this->interface = $name; @@ -812,4 +813,36 @@ class Translator extends Preprocessor $this->interfaces[$interfaceName] = $this->interfaceDef; $this->interfacesDefineInFile[$interfaceName] = $this->interfaceDef; } + + protected function parseForeachObject(Foreach_ $node): string + { + $obj = $this->parseIdentifier($node->expr); + $tmpVar = $this->genTmpVarName(); + $this->addLocalVar($tmpVar, self::TYPE_OBJECT); + $code = 'if (' . $obj . '.instanceOf("IteratorAggregate")) {' . PHP_EOL; + $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . '.exec("getIterator");' . PHP_EOL . '}' . PHP_EOL; + $code .= 'else if (' . $obj . '.instanceOf("Iterator")) {' . PHP_EOL; + $code .= $this->getIndent() . $tmpVar . ' = ' . $obj . ';' . PHP_EOL . '}'. PHP_EOL; + + $code .= 'if (' . $tmpVar . ') {'. PHP_EOL; + + $this->indentLevel++; + $code .= $this->getIndent() .$tmpVar . '.exec("rewind");' . PHP_EOL; + $code .= $this->getIndent() .'while (' . $tmpVar . '.exec("valid")) {' . PHP_EOL; + $this->indentLevel++; + $valueVar = $this->parseIdentifier($node->valueVar); + $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = ' . $tmpVar . '.exec("current");' . PHP_EOL; + if ($node->keyVar) { + $keyVar = $this->parseIdentifier($node->keyVar); + $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $keyVar . ' = ' . $tmpVar . '.exec("key");' . PHP_EOL; + } + $code .= $this->parseStmts($node->stmts); + $code .= $this->getIndent() . $tmpVar . '.exec("next");' . PHP_EOL; + $code .= '}'. PHP_EOL; + $this->indentLevel--; + $this->indentLevel--; + $code .= '}'. PHP_EOL; + return $code; + + } } \ No newline at end of file diff --git a/tests/core/classes/iterators_001.phpt b/tests/core/classes/iterators_001.phpt new file mode 100644 index 00000000..3b3d2c1f --- /dev/null +++ b/tests/core/classes/iterators_001.phpt @@ -0,0 +1,199 @@ +--TEST-- +ZE2 iterators and foreach +--FILE-- +num = 0; + $this->obj = $obj; + } + function rewind(): void { + } + function valid(): bool { + $more = $this->num < $this->obj->max; + echo __METHOD__ . ' = ' .($more ? 'true' : 'false') . "\n"; + return $more; + } + function current(): mixed { + echo __METHOD__ . "\n"; + return $this->num; + } + function next(): void { + echo __METHOD__ . "\n"; + $this->num++; + } + function key(): mixed { + echo __METHOD__ . "\n"; + switch($this->num) { + case 0: return "1st"; + case 1: return "2nd"; + case 2: return "3rd"; + default: return "???"; + } + } +} + +class c implements IteratorAggregate { + + public $max = 3; + + function getIterator(): Traversable { + echo __METHOD__ . "\n"; + return new c_iter($this); + } +} + +function main() { + echo "===Array===\n"; + + $a = array(0,1,2); + foreach($a as $v) { + echo "array:$v\n"; + } + + echo "===Manual===\n"; + $t = new c(); + for ($iter = $t->getIterator(); $iter->valid(); $iter->next()) { + echo $iter->current() . "\n"; + } + + echo "===foreach/std===\n"; + foreach($t as $v) { + echo "object:$v\n"; + } + + echo "===foreach/rec===\n"; + foreach($t as $v) { + foreach($t as $w) { + echo "double:$v:$w\n"; + } + } + + echo "===foreach/key===\n"; + foreach($t as $i => $v) { + echo "object:$i=>$v\n"; + } + + print "Done\n"; +} +?> +--EXPECT-- +===Array=== +array:0 +array:1 +array:2 +===Manual=== +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +0 +c_iter::next +c_iter::valid = true +c_iter::current +1 +c_iter::next +c_iter::valid = true +c_iter::current +2 +c_iter::next +c_iter::valid = false +===foreach/std=== +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +object:0 +c_iter::next +c_iter::valid = true +c_iter::current +object:1 +c_iter::next +c_iter::valid = true +c_iter::current +object:2 +c_iter::next +c_iter::valid = false +===foreach/rec=== +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +double:0:0 +c_iter::next +c_iter::valid = true +c_iter::current +double:0:1 +c_iter::next +c_iter::valid = true +c_iter::current +double:0:2 +c_iter::next +c_iter::valid = false +c_iter::next +c_iter::valid = true +c_iter::current +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +double:1:0 +c_iter::next +c_iter::valid = true +c_iter::current +double:1:1 +c_iter::next +c_iter::valid = true +c_iter::current +double:1:2 +c_iter::next +c_iter::valid = false +c_iter::next +c_iter::valid = true +c_iter::current +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +double:2:0 +c_iter::next +c_iter::valid = true +c_iter::current +double:2:1 +c_iter::next +c_iter::valid = true +c_iter::current +double:2:2 +c_iter::next +c_iter::valid = false +c_iter::next +c_iter::valid = false +===foreach/key=== +c::getIterator +c_iter::__construct +c_iter::valid = true +c_iter::current +c_iter::key +object:1st=>0 +c_iter::next +c_iter::valid = true +c_iter::current +c_iter::key +object:2nd=>1 +c_iter::next +c_iter::valid = true +c_iter::current +c_iter::key +object:3rd=>2 +c_iter::next +c_iter::valid = false +Done diff --git a/tests/core/classes/iterators_002.phpt b/tests/core/classes/iterators_002.phpt new file mode 100644 index 00000000..72acedc9 --- /dev/null +++ b/tests/core/classes/iterators_002.phpt @@ -0,0 +1,105 @@ +--TEST-- +ZE2 iterators and break +--FILE-- +obj = $obj; + } + function rewind(): void { + echo __METHOD__ . "\n"; + $this->num = 0; + } + function valid(): bool { + $more = $this->num < $this->obj->max; + echo __METHOD__ . ' = ' .($more ? 'true' : 'false') . "\n"; + return $more; + } + function current(): mixed { + echo __METHOD__ . "\n"; + return $this->num; + } + function next(): void { + echo __METHOD__ . "\n"; + $this->num++; + } + function key(): mixed { + echo __METHOD__ . "\n"; + switch($this->num) { + case 0: return "1st"; + case 1: return "2nd"; + case 2: return "3rd"; + default: return "???"; + } + } + function __destruct() { + } +} + +class c implements IteratorAggregate { + + public $max = 3; + + function getIterator(): Traversable { + echo __METHOD__ . "\n"; + return new c_iter($this); + } + function __destruct() { + } +} +function main() { + $t = new c(); + + foreach($t as $k => $v) { + foreach($t as $w) { + echo "double:$v:$w\n"; + break; + } + } + + unset($t); + + print "Done\n"; +} +?> +--EXPECT-- +c::getIterator +c_iter::__construct +c_iter::rewind +c_iter::valid = true +c_iter::current +c_iter::key +c::getIterator +c_iter::__construct +c_iter::rewind +c_iter::valid = true +c_iter::current +double:0:0 +c_iter::next +c_iter::valid = true +c_iter::current +c_iter::key +c::getIterator +c_iter::__construct +c_iter::rewind +c_iter::valid = true +c_iter::current +double:1:0 +c_iter::next +c_iter::valid = true +c_iter::current +c_iter::key +c::getIterator +c_iter::__construct +c_iter::rewind +c_iter::valid = true +c_iter::current +double:2:0 +c_iter::next +c_iter::valid = false +Done