feat: add break statements

This commit is contained in:
2026-02-06 15:51:23 +01:00
parent a1bc85f3fa
commit a3a8068d61
11 changed files with 76 additions and 17 deletions

6
examples/16_break.peb Normal file
View File

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

View File

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

View File

@@ -53,6 +53,10 @@ class Stmt(ABC):
def visit_for_stmt(self, stmt: ForStmt) -> T:
...
@abstractmethod
def visit_break_stmt(self, stmt: BreakStmt) -> T:
...
@dataclass(frozen=True)
class BlockStmt(Stmt):
@@ -138,3 +142,11 @@ class ForStmt(Stmt):
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_for_stmt(self)
@dataclass(frozen=True)
class BreakStmt(Stmt):
keyword: Token
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_break_stmt(self)

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
from enum import Enum, auto
from typing import Any, TYPE_CHECKING
from src.ast.stmt import FunctionStmt
@@ -12,11 +11,6 @@ if TYPE_CHECKING:
from src.interpreter.interpreter import Interpreter
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
class PebbleFunction(PebbleCallable):
def __init__(self, declaration: FunctionStmt, closure: Environment):
self.declaration: FunctionStmt = declaration

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
ReturnStmt, BreakStmt
class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
@@ -85,10 +85,10 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
return res
def visit_if_stmt(self, stmt: IfStmt) -> str:
res: str = self.indented(f"if {self.format(stmt.condition)} {self.format(stmt.then_branch)}")
res: str = self.indented(f"if {self.format(stmt.condition)} {self.format(stmt.then_branch).lstrip()}")
res = res.rstrip("\n")
if stmt.else_branch is not None:
res += f" else {self.format(stmt.else_branch)}"
res += f" else {self.format(stmt.else_branch).lstrip()}"
res = res.rstrip("\n")
res += "\n"
return res
@@ -128,3 +128,6 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]):
res += f" = {stmt.initializer.accept(self)}"
res += "\n"
return res
def visit_break_stmt(self, stmt: BreakStmt) -> str:
return self.indented(f"{stmt.keyword.lexeme}\n")

View File

@@ -5,3 +5,7 @@ class ReturnException(RuntimeError):
def __init__(self, value: Any):
super().__init__()
self.value: Any = value
class BreakException(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
ReturnStmt, BreakStmt
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
from src.interpreter.exceptions import ReturnException, BreakException
from src.interpreter.globals import GlobalEnvironment
from src.pebble import Pebble
from src.token import TokenType, Token
@@ -179,7 +179,10 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
def visit_while_stmt(self, stmt: WhileStmt) -> None:
while self.is_truthy(self.evaluate(stmt.condition)):
try:
self.execute(stmt.body)
except BreakException:
break
def visit_for_stmt(self, stmt: ForStmt) -> None:
previous_env: Environment = self.env
@@ -224,7 +227,10 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
while condition():
self.env.define(stmt.variable.lexeme, value)
try:
self.execute(stmt.body)
except BreakException:
break
value += step_value
self.env = previous_env
@@ -235,6 +241,9 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
value = self.evaluate(stmt.initializer)
self.env.define(stmt.name.lexeme, value)
def visit_break_stmt(self, stmt: BreakStmt) -> None:
raise BreakException()
@staticmethod
def is_truthy(value: Any) -> bool:
if value is None or value is False:

View File

@@ -1,12 +1,12 @@
from __future__ import annotations
from enum import Enum, auto
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
from src.core.function import FunctionType
ExpressionStmt, BlockStmt, BreakStmt, T
from src.pebble import Pebble
from src.token import Token
@@ -14,11 +14,23 @@ if TYPE_CHECKING:
from src.interpreter.interpreter import Interpreter
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
class LoopType(Enum):
NONE = auto()
WHILE = auto()
FOR = auto()
class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
def __init__(self, interpreter: Interpreter):
self.interpreter: Interpreter = interpreter
self.scopes: list[dict[str, bool]] = []
self.current_func: FunctionType = FunctionType.NONE
self.current_loop: LoopType = LoopType.NONE
def resolve(self, *objects: Expr | Stmt) -> None:
for obj in objects:
@@ -126,10 +138,15 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.define(stmt.name)
def visit_while_stmt(self, stmt: WhileStmt) -> None:
enclosing_loop: LoopType = self.current_loop
self.current_loop = LoopType.WHILE
self.resolve(stmt.condition)
self.resolve(stmt.body)
self.current_loop = enclosing_loop
def visit_for_stmt(self, stmt: ForStmt) -> None:
enclosing_loop: LoopType = self.current_loop
self.current_loop = LoopType.FOR
self.begin_scope()
self.declare(stmt.variable)
self.define(stmt.variable)
@@ -141,3 +158,8 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]):
self.resolve(stmt.step)
self.resolve(stmt.body)
self.end_scope()
self.current_loop = enclosing_loop
def visit_break_stmt(self, stmt: BreakStmt) -> None:
if self.current_loop == LoopType.NONE:
Pebble.token_error(stmt.keyword, "Cannot break outside a loop")

View File

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

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
ReturnStmt, BreakStmt
from src.consts import MAX_FUNCTION_ARGS
from src.parser.error import ParsingError
from src.pebble import Pebble
@@ -134,6 +134,8 @@ class Parser:
return self.print_stmt()
if self.match(TokenType.RETURN):
return self.return_stmt()
if self.match(TokenType.BREAK):
return self.break_stmt()
if self.match(TokenType.WHILE):
return self.while_stmt()
if self.match(TokenType.LEFT_BRACE):
@@ -218,6 +220,11 @@ class Parser:
self.expect_eol("Expected end of line after return statement.")
return ReturnStmt(keyword, value)
def break_stmt(self) -> Stmt:
keyword: Token = self.previous()
self.expect_eol("Expected end of line after break statement.")
return BreakStmt(keyword)
def while_stmt(self) -> Stmt:
condition: Expr = self.expression()
body: Stmt = self.statement()

View File

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