diff --git a/examples/16_break.peb b/examples/16_break.peb new file mode 100644 index 0000000..0305b7b --- /dev/null +++ b/examples/16_break.peb @@ -0,0 +1,6 @@ +for i to 10 { + if i == 5 { + break + } + print(i) +} \ No newline at end of file diff --git a/main.py b/main.py index 5e0fa6c..d21913b 100644 --- a/main.py +++ b/main.py @@ -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) diff --git a/src/ast/stmt.py b/src/ast/stmt.py index 0356a7e..8d6c2ad 100644 --- a/src/ast/stmt.py +++ b/src/ast/stmt.py @@ -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) diff --git a/src/core/function.py b/src/core/function.py index 729b566..929d269 100644 --- a/src/core/function.py +++ b/src/core/function.py @@ -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 diff --git a/src/formatter.py b/src/formatter.py index 35ad1dd..97dd8ef 100644 --- a/src/formatter.py +++ b/src/formatter.py @@ -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") diff --git a/src/interpreter/exceptions.py b/src/interpreter/exceptions.py index e0b65e3..c6c3ca4 100644 --- a/src/interpreter/exceptions.py +++ b/src/interpreter/exceptions.py @@ -5,3 +5,7 @@ class ReturnException(RuntimeError): def __init__(self, value: Any): super().__init__() self.value: Any = value + + +class BreakException(RuntimeError): + pass diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 0d8206a..1fd6fd7 100644 --- a/src/interpreter/interpreter.py +++ b/src/interpreter/interpreter.py @@ -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)): - self.execute(stmt.body) + 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) - self.execute(stmt.body) + 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: diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 149ca83..450c063 100644 --- a/src/interpreter/resolver.py +++ b/src/interpreter/resolver.py @@ -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") diff --git a/src/keyword.py b/src/keyword.py index c6f46e9..cb79f36 100644 --- a/src/keyword.py +++ b/src/keyword.py @@ -18,4 +18,5 @@ KEYWORDS: dict[str, TokenType] = { "null": TokenType.NULL, "print": TokenType.PRINT, "return": TokenType.RETURN, + "break": TokenType.BREAK, } diff --git a/src/parser/parser.py b/src/parser/parser.py index 850a698..541052a 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -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() diff --git a/src/token.py b/src/token.py index 7a8e6f2..ee82083 100644 --- a/src/token.py +++ b/src/token.py @@ -56,6 +56,7 @@ class TokenType(Enum): UNTIL = auto() BY = auto() RETURN = auto() + BREAK = auto() # Misc PRINT = auto()