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(): def main():
path: Path = Path("examples/16_break.peb") path: Path = Path("examples/16_break_continue.peb")
Pebble.run_file(path) Pebble.run_file(path)

View File

@@ -57,6 +57,10 @@ class Stmt(ABC):
def visit_break_stmt(self, stmt: BreakStmt) -> T: def visit_break_stmt(self, stmt: BreakStmt) -> T:
... ...
@abstractmethod
def visit_continue_stmt(self, stmt: ContinueStmt) -> T:
...
@dataclass(frozen=True) @dataclass(frozen=True)
class BlockStmt(Stmt): class BlockStmt(Stmt):
@@ -150,3 +154,11 @@ class BreakStmt(Stmt):
def accept(self, visitor: Stmt.Visitor[T]) -> T: def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_break_stmt(self) 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, \ from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \
CallExpr CallExpr
from src.ast.stmt import Stmt, LetStmt, PrintStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \ 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]): 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: def visit_break_stmt(self, stmt: BreakStmt) -> str:
return self.indented(f"{stmt.keyword.lexeme}\n") 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): class BreakException(RuntimeError):
pass 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, \ from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr CallExpr
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ 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.callable import PebbleCallable
from src.core.function import PebbleFunction from src.core.function import PebbleFunction
from src.interpreter.environment import Environment from src.interpreter.environment import Environment
from src.interpreter.error import PebbleRuntimeError 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.interpreter.globals import GlobalEnvironment
from src.pebble import Pebble from src.pebble import Pebble
from src.token import TokenType, Token from src.token import TokenType, Token
@@ -183,6 +183,8 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
self.execute(stmt.body) self.execute(stmt.body)
except BreakException: except BreakException:
break break
except ContinueException:
pass
def visit_for_stmt(self, stmt: ForStmt) -> None: def visit_for_stmt(self, stmt: ForStmt) -> None:
previous_env: Environment = self.env previous_env: Environment = self.env
@@ -231,6 +233,8 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
self.execute(stmt.body) self.execute(stmt.body)
except BreakException: except BreakException:
break break
except ContinueException:
pass
value += step_value value += step_value
self.env = previous_env self.env = previous_env
@@ -244,6 +248,9 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def visit_break_stmt(self, stmt: BreakStmt) -> None: def visit_break_stmt(self, stmt: BreakStmt) -> None:
raise BreakException() raise BreakException()
def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
raise ContinueException()
@staticmethod @staticmethod
def is_truthy(value: Any) -> bool: def is_truthy(value: Any) -> bool:
if value is None or value is False: if value is None or value is False:
@@ -258,18 +265,18 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
@staticmethod @staticmethod
def check_number_operand(operator: Token, operand: Any): def check_number_operand(operator: Token, operand: Any):
if isinstance(operand, float): if isinstance(operand, (int, float)):
return return
raise PebbleRuntimeError(operator, "Operand must be a number.") raise PebbleRuntimeError(operator, "Operand must be a number.")
@staticmethod @staticmethod
def check_number_operands(operator: Token, left: Any, right: Any): 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 return
raise PebbleRuntimeError(operator, "Operands must be numbers.") raise PebbleRuntimeError(operator, "Operands must be numbers.")
@staticmethod @staticmethod
def check_number_clause(clause: Token, value: Any): def check_number_clause(clause: Token, value: Any):
if isinstance(value, float): if isinstance(value, (int, float)):
return return
raise PebbleRuntimeError(clause, "For loop clauses must be numbers.") 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, \ from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \
AssignExpr AssignExpr
from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, PrintStmt, IfStmt, FunctionStmt, \ 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.pebble import Pebble
from src.token import Token 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: def visit_break_stmt(self, stmt: BreakStmt) -> None:
if self.current_loop == LoopType.NONE: if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot break outside a loop") 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, "print": TokenType.PRINT,
"return": TokenType.RETURN, "return": TokenType.RETURN,
"break": TokenType.BREAK, "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, \ from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \
CallExpr CallExpr
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ 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.consts import MAX_FUNCTION_ARGS
from src.parser.error import ParsingError from src.parser.error import ParsingError
from src.pebble import Pebble from src.pebble import Pebble
@@ -136,6 +136,8 @@ class Parser:
return self.return_stmt() return self.return_stmt()
if self.match(TokenType.BREAK): if self.match(TokenType.BREAK):
return self.break_stmt() return self.break_stmt()
if self.match(TokenType.CONTINUE):
return self.continue_stmt()
if self.match(TokenType.WHILE): if self.match(TokenType.WHILE):
return self.while_stmt() return self.while_stmt()
if self.match(TokenType.LEFT_BRACE): if self.match(TokenType.LEFT_BRACE):
@@ -225,6 +227,11 @@ class Parser:
self.expect_eol("Expected end of line after break statement.") self.expect_eol("Expected end of line after break statement.")
return BreakStmt(keyword) 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: def while_stmt(self) -> Stmt:
condition: Expr = self.expression() condition: Expr = self.expression()
body: Stmt = self.statement() body: Stmt = self.statement()

View File

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