feat: add continue statements

This commit is contained in:
2026-02-06 15:58:47 +01:00
parent a3a8068d61
commit c7792d39b7
11 changed files with 62 additions and 15 deletions

View File

@@ -1,6 +0,0 @@
for i to 10 {
if i == 5 {
break
}
print(i)
}

View File

@@ -0,0 +1,14 @@
for i to 10 {
if i == 5 {
break
}
print(i)
}
for i to 10 {
print(i)
if i <= 5 {
continue
}
print("Larger than 5")
}

View File

@@ -4,7 +4,7 @@ from src.pebble import Pebble
def main():
path: Path = Path("examples/16_break.peb")
path: Path = Path("examples/16_break_continue.peb")
Pebble.run_file(path)

View File

@@ -57,6 +57,10 @@ class Stmt(ABC):
def visit_break_stmt(self, stmt: BreakStmt) -> T:
...
@abstractmethod
def visit_continue_stmt(self, stmt: ContinueStmt) -> T:
...
@dataclass(frozen=True)
class BlockStmt(Stmt):
@@ -150,3 +154,11 @@ class BreakStmt(Stmt):
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_break_stmt(self)
@dataclass(frozen=True)
class ContinueStmt(Stmt):
keyword: Token
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_continue_stmt(self)

View File

@@ -3,7 +3,7 @@ from typing import Any
from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \
CallExpr
from src.ast.stmt import Stmt, LetStmt, PrintStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt
ReturnStmt, BreakStmt, ContinueStmt
class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
@@ -131,3 +131,6 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
def visit_break_stmt(self, stmt: BreakStmt) -> str:
return self.indented(f"{stmt.keyword.lexeme}\n")
def visit_continue_stmt(self, stmt: ContinueStmt) -> str:
return self.indented(f"{stmt.keyword.lexeme}\n")

View File

@@ -9,3 +9,7 @@ class ReturnException(RuntimeError):
class BreakException(RuntimeError):
pass
class ContinueException(RuntimeError):
pass

View File

@@ -4,12 +4,12 @@ from typing import Any, Optional
from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt
ReturnStmt, BreakStmt, ContinueStmt
from src.core.callable import PebbleCallable
from src.core.function import PebbleFunction
from src.interpreter.environment import Environment
from src.interpreter.error import PebbleRuntimeError
from src.interpreter.exceptions import ReturnException, BreakException
from src.interpreter.exceptions import ReturnException, BreakException, ContinueException
from src.interpreter.globals import GlobalEnvironment
from src.pebble import Pebble
from src.token import TokenType, Token
@@ -183,6 +183,8 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
self.execute(stmt.body)
except BreakException:
break
except ContinueException:
pass
def visit_for_stmt(self, stmt: ForStmt) -> None:
previous_env: Environment = self.env
@@ -231,6 +233,8 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
self.execute(stmt.body)
except BreakException:
break
except ContinueException:
pass
value += step_value
self.env = previous_env
@@ -244,6 +248,9 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def visit_break_stmt(self, stmt: BreakStmt) -> None:
raise BreakException()
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
raise ContinueException()
@staticmethod
def is_truthy(value: Any) -> bool:
if value is None or value is False:
@@ -258,18 +265,18 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
@staticmethod
def check_number_operand(operator: Token, operand: Any):
if isinstance(operand, float):
if isinstance(operand, (int, float)):
return
raise PebbleRuntimeError(operator, "Operand must be a number.")
@staticmethod
def check_number_operands(operator: Token, left: Any, right: Any):
if isinstance(left, float) and isinstance(right, float):
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
return
raise PebbleRuntimeError(operator, "Operands must be numbers.")
@staticmethod
def check_number_clause(clause: Token, value: Any):
if isinstance(value, float):
if isinstance(value, (int, float)):
return
raise PebbleRuntimeError(clause, "For loop clauses must be numbers.")

View File

@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \
AssignExpr
from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, PrintStmt, IfStmt, FunctionStmt, \
ExpressionStmt, BlockStmt, BreakStmt, T
ExpressionStmt, BlockStmt, BreakStmt, T, ContinueStmt
from src.pebble import Pebble
from src.token import Token
@@ -163,3 +163,7 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
def visit_break_stmt(self, stmt: BreakStmt) -> None:
if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot break outside a loop")
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot continue outside a loop")

View File

@@ -19,4 +19,5 @@ KEYWORDS: dict[str, TokenType] = {
"print": TokenType.PRINT,
"return": TokenType.RETURN,
"break": TokenType.BREAK,
"continue": TokenType.CONTINUE,
}

View File

@@ -3,7 +3,7 @@ from typing import Optional
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \
ReturnStmt, BreakStmt
ReturnStmt, BreakStmt, ContinueStmt
from src.consts import MAX_FUNCTION_ARGS
from src.parser.error import ParsingError
from src.pebble import Pebble
@@ -136,6 +136,8 @@ class Parser:
return self.return_stmt()
if self.match(TokenType.BREAK):
return self.break_stmt()
if self.match(TokenType.CONTINUE):
return self.continue_stmt()
if self.match(TokenType.WHILE):
return self.while_stmt()
if self.match(TokenType.LEFT_BRACE):
@@ -225,6 +227,11 @@ class Parser:
self.expect_eol("Expected end of line after break statement.")
return BreakStmt(keyword)
def continue_stmt(self) -> Stmt:
keyword: Token = self.previous()
self.expect_eol("Expected end of line after continue statement.")
return ContinueStmt(keyword)
def while_stmt(self) -> Stmt:
condition: Expr = self.expression()
body: Stmt = self.statement()

View File

@@ -57,6 +57,7 @@ class TokenType(Enum):
BY = auto()
RETURN = auto()
BREAK = auto()
CONTINUE = auto()
# Misc
PRINT = auto()