refactor(php): 重构 foreach 循环解析逻辑以支持对象迭代器

- 将原 parseForeach 方法拆分为 parseForeach 和 parseForeachArray 两个方法
- 添加对 Iterator 和 IteratorAggregate 接口的支持
- 实现对象属性遍历的备用方案,使用 get_object_vars 函数
- 添加新的测试用例 iterators_004.phpt 验证迭代器功能
- 添加新的测试用例 iterators_005.phpt 验证 Traversable 接口实现检查
- 为对象迭代创建临时变量并设置正确的类型标识
pull/1/head
韩天峰 7 months ago
parent 749b4ec00d
commit 715abf458d
  1. 55
      src/Php/CompilerBase.php
  2. 7
      src/Php/Translator.php
  3. 56
      tests/core/classes/iterators_004.phpt
  4. 20
      tests/core/classes/iterators_005.phpt

@ -1980,35 +1980,13 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
protected function parseForeach(Foreach_ $node): string
protected function parseForeachArray(Foreach_ $node, string $iteratorVar): string
{
if ($node->byRef) {
$this->fatalError($node, 'Cannot use & with foreach');
}
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);
}
}
}
$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;
$code = 'for (auto iter = ' . $iteratorVar . '.begin(); iter != ' . $iteratorVar . '.end(); ++iter) {' . PHP_EOL;
$this->indentLevel++;
if ($node->keyVar) {
$code .= self::TYPE_VAR . ' ' . $this->getIndent() . ' ' . $keyVar . ' = iter.key();' . PHP_EOL;
@ -2022,9 +2000,10 @@ class CompilerBase extends \PhpAot\Core\Translator
$dim = $this->parseIdentifier($node->valueVar->dim);
$code .= $this->getIndent() . "$array.offsetSet($dim, iter.value());";
} else {
$valueVar = $this->parseIdentifier($node->valueVar);
$code .= self::TYPE_VAR . ' ' . $this->getIndent() . ' ' . $valueVar . ' = iter.value();' . PHP_EOL;
}
$code .= $this->parseStmts($stmts);
$code .= $this->parseStmts($node->stmts);
$this->indentLevel--;
$code .= $this->getIndent() . '}';
@ -2032,6 +2011,32 @@ class CompilerBase extends \PhpAot\Core\Translator
return $code;
}
protected function parseForeach(Foreach_ $node): string
{
if ($node->byRef) {
$this->fatalError($node, 'Cannot use & with foreach');
}
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);
}
}
}
$iteratorVar = $this->genTmpVarName();
$code = '';
$expr = $this->parseIdentifier($node->expr);
$code .= self::TYPE_ARRAY . " $iteratorVar = " . $expr . ';' . PHP_EOL;
$code .= $this->parseBeforeStmtLines() . PHP_EOL;
$code .= $this->parseForeachArray($node, $iteratorVar);
return $code;
}
protected function formatCppCode(string $file): void
{
$cmd = 'cd ' . $this->rootPath . ' && clang-format -i ' . $file;

@ -819,6 +819,10 @@ class Translator extends Preprocessor
$obj = $this->parseIdentifier($node->expr);
$tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, self::TYPE_OBJECT);
$tmpArrayVar = $this->genTmpVarName();
$this->addLocalVar($tmpArrayVar, self::TYPE_ARRAY);
$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;
@ -839,6 +843,9 @@ class Translator extends Preprocessor
$code .= $this->parseStmts($node->stmts);
$code .= '}' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '} else {' . PHP_EOL;
$code .= $this->getIndent() . $tmpArrayVar . ' = php::call("get_object_vars", {' . $obj . '});' . PHP_EOL;
$code .= $this->parseForeachArray($node, $tmpArrayVar);
$this->indentLevel--;
$code .= '}' . PHP_EOL;

@ -0,0 +1,56 @@
--TEST--
ZE2 iterators must be implemented
--FILE--
<?php
class c1 {}
class c2 {
public $max = 3;
public $num = 0;
function current() {
echo __METHOD__ . "\n";
return $this->num;
}
function next(): void {
echo __METHOD__ . "\n";
$this->num++;
}
function valid(): bool {
echo __METHOD__ . "\n";
return $this->num < $this->max;
}
function key(): mixed {
echo __METHOD__ . "\n";
switch($this->num) {
case 0: return "1st";
case 1: return "2nd";
case 2: return "3rd";
default: return "???";
}
}
}
function main() {
echo "1st try\n";
$obj = new c1();
foreach($obj as $w) {
echo "object:$w\n";
}
echo "2nd try\n";
$obj = new c2();
foreach($obj as $v => $w) {
echo "object:$v=>$w\n";
}
print "Done\n";
}
?>
--EXPECT--
1st try
2nd try
object:max=>3
object:num=>0
Done

@ -0,0 +1,20 @@
--TEST--
ZE2 iterators cannot implement Traversable alone
--SKIPIF--
<?php die('skip'); ?>
--FILE--
<?php
class test implements Traversable {
}
function main() {
$obj = new test;
foreach($obj as $v);
print "Done\n";
/* the error doesn't show the filename but 'Unknown' */
}
?>
--EXPECTF--
Fatal error: Class test must implement interface Traversable as part of either Iterator or IteratorAggregate in %s on line %d
Loading…
Cancel
Save