Skip to content

Commit d706a8d

Browse files
feat: implement break and continue statement support
1 parent a54402e commit d706a8d

8 files changed

Lines changed: 77 additions & 2 deletions

File tree

src/AST/BreakStatement.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codewithkyrian\Jinja\AST;
6+
7+
class BreakStatement extends Statement
8+
{
9+
public string $type = "BreakStatement";
10+
}

src/AST/ContinueStatement.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codewithkyrian\Jinja\AST;
6+
7+
class ContinueStatement extends Statement
8+
{
9+
public string $type = "ContinueStatement";
10+
}

src/Core/Interpreter.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
use Codewithkyrian\Jinja\Exceptions\SyntaxError;
2727
use Codewithkyrian\Jinja\Runtime\ArrayValue;
2828
use Codewithkyrian\Jinja\Runtime\BooleanValue;
29+
use Codewithkyrian\Jinja\Runtime\BreakControl;
30+
use Codewithkyrian\Jinja\Runtime\ContinueControl;
2931
use Codewithkyrian\Jinja\Runtime\FunctionValue;
3032
use Codewithkyrian\Jinja\Runtime\KeywordArgumentsValue;
3133
use Codewithkyrian\Jinja\Runtime\NullValue;
@@ -73,6 +75,12 @@ function evaluate(?Statement $statement, Environment $environment): RuntimeValue
7375
case "Macro":
7476
return $this->evaluateMacro($statement, $environment);
7577

78+
case "BreakStatement":
79+
throw new BreakControl();
80+
81+
case "ContinueStatement":
82+
throw new ContinueControl();
83+
7684
case "NumericLiteral":
7785
return new NumericValue($statement->value);
7886

@@ -786,8 +794,15 @@ private function evaluateFor(ForStatement $node, Environment $environment): Stri
786794

787795
$scopeUpdateFunction = $scopeUpdateFunctions[$i];
788796
$scopeUpdateFunction($scope);
789-
$evaluated = $this->evaluateBlock($node->body, $scope);
790-
$result .= $evaluated->value;
797+
798+
try {
799+
$evaluated = $this->evaluateBlock($node->body, $scope);
800+
$result .= $evaluated->value;
801+
} catch (BreakControl $e) {
802+
break;
803+
} catch (ContinueControl $e) {
804+
continue;
805+
}
791806

792807
$noIteration = false; // At least one iteration took place
793808
}

src/Core/Parser.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Codewithkyrian\Jinja\AST\ArrayLiteral;
88
use Codewithkyrian\Jinja\AST\BinaryExpression;
99
use Codewithkyrian\Jinja\AST\BooleanLiteral;
10+
use Codewithkyrian\Jinja\AST\BreakStatement;
1011
use Codewithkyrian\Jinja\AST\CallExpression;
1112
use Codewithkyrian\Jinja\AST\FilterExpression;
1213
use Codewithkyrian\Jinja\AST\ForStatement;
@@ -20,6 +21,7 @@
2021
use Codewithkyrian\Jinja\AST\ObjectLiteral;
2122
use Codewithkyrian\Jinja\AST\Program;
2223
use Codewithkyrian\Jinja\AST\SelectExpression;
24+
use Codewithkyrian\Jinja\AST\ContinueStatement;
2325
use Codewithkyrian\Jinja\AST\SetStatement;
2426
use Codewithkyrian\Jinja\AST\SliceExpression;
2527
use Codewithkyrian\Jinja\AST\Statement;
@@ -166,6 +168,18 @@ private function parseJinjaStatement(): Statement
166168

167169
break;
168170

171+
case TokenType::Break:
172+
$this->current++;
173+
$this->expect(TokenType::CloseStatement, "Expected closing statement token");
174+
$result = new BreakStatement();
175+
break;
176+
177+
case TokenType::Continue:
178+
$this->current++;
179+
$this->expect(TokenType::CloseStatement, "Expected closing statement token");
180+
$result = new ContinueStatement();
181+
break;
182+
169183
default:
170184
throw new SyntaxError("Unknown statement type: $tokenType->value");
171185
}

src/Core/Token.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ class Token
2525
'or' => TokenType::Or,
2626
'not' => TokenType::Not,
2727
'not in' => TokenType::NotIn,
28+
'macro' => TokenType::Macro,
29+
'endmacro' => TokenType::EndMacro,
30+
'break' => TokenType::Break,
31+
'continue' => TokenType::Continue,
2832

2933
// Literals
3034
'true' => TokenType::BooleanLiteral,

src/Core/TokenType.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,6 @@ enum TokenType: string
5656
case Not = "UnaryOperator";
5757
case Macro = "Macro";
5858
case EndMacro = "EndMacro";
59+
case Break = "Break";
60+
case Continue = "Continue";
5961
}

src/Runtime/BreakControl.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codewithkyrian\Jinja\Runtime;
6+
7+
/**
8+
* Control flow exception thrown when a break statement is encountered in a loop.
9+
*/
10+
class BreakControl extends \Exception {}

src/Runtime/ContinueControl.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Codewithkyrian\Jinja\Runtime;
6+
7+
/**
8+
* Control flow exception thrown when a continue statement is encountered in a loop.
9+
*/
10+
class ContinueControl extends \Exception {}

0 commit comments

Comments
 (0)