diff --git a/CHANGELOG b/CHANGELOG index 2a16c4a28..57aa18501 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ # 4.0.0 (2024-XX-XX) - * Add support for recursive loops (via `loop.recurse()`) + * Add support for recursive loops (via the `loop()` function) * Add `loop.changed`, `loop.previous`, `loop.next`, and `loop.cycle` variables * Make `loop.last` always available (even for non-countable iterators) * Change the compilation of `for` loops to throw an exception when a `loop.*` variable is not defined diff --git a/src/ExpressionParser.php b/src/ExpressionParser.php index 1c392af2f..7258d4c20 100644 --- a/src/ExpressionParser.php +++ b/src/ExpressionParser.php @@ -460,6 +460,15 @@ class ExpressionParser } return new GetAttrExpression($args->getNode('0'), $args->getNode('1'), \count($args) > 2 ? $args->getNode('2') : null, Template::ANY_CALL, $line); + case 'loop': + $args = $this->parseArguments(); + if (\count($args) < 1) { + throw new SyntaxError('The "loop" function takes an iterator as an argument.', $line, $this->parser->getStream()->getSourceContext()); + } + $recurseArgs = new ArrayExpression([], $line); + $recurseArgs->addElement($args->getNode('0')); + + return new GetAttrExpression(new NameExpression('loop', $line), new ConstantExpression('__invoke', $line), $recurseArgs, Template::METHOD_CALL, $line); default: if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { $arguments = new ArrayExpression([], $line); diff --git a/src/NodeVisitor/ForNodeVisitor.php b/src/NodeVisitor/ForNodeVisitor.php index b4a64cbad..afa35e75c 100644 --- a/src/NodeVisitor/ForNodeVisitor.php +++ b/src/NodeVisitor/ForNodeVisitor.php @@ -43,14 +43,14 @@ final class ForNodeVisitor implements NodeVisitorInterface return $node; } - // We look for exactly {{ loop.recurse(...) }} + // We look for exactly {{ loop.__invoke(...) }} $exprNode = $node->getNode('expr'); if ( $exprNode instanceof GetAttrExpression && $exprNode->getNode('node') instanceof NameExpression && 'loop' === $exprNode->getNode('node')->getAttribute('name') && $exprNode->getNode('attribute') instanceof ConstantExpression - && 'recurse' === $exprNode->getNode('attribute')->getAttribute('value') + && '__invoke' === $exprNode->getNode('attribute')->getAttribute('value') ) { $exprNode->setAttribute('is_generator', true); } diff --git a/src/Runtime/LoopContext.php b/src/Runtime/LoopContext.php index 26b0765b2..20c0b1849 100644 --- a/src/Runtime/LoopContext.php +++ b/src/Runtime/LoopContext.php @@ -103,7 +103,7 @@ final class LoopContext return $values[$this->getIndex0() % count($values)]; } - public function recurse($iterator): \Generator + public function __invoke($iterator): \Generator { yield from ($this->recurseFunc)(new LoopIterator($iterator), $this->parent, $this->blocks, $this->recurseFunc, $this->depth + 1); }