diff --git a/examples/16_break.peb b/examples/16_break.peb deleted file mode 100644 index 0305b7b..0000000 --- a/examples/16_break.peb +++ /dev/null @@ -1,6 +0,0 @@ -for i to 10 { - if i == 5 { - break - } - print(i) -} \ No newline at end of file diff --git a/examples/16_break_continue.peb b/examples/16_break_continue.peb new file mode 100644 index 0000000..e145755 --- /dev/null +++ b/examples/16_break_continue.peb @@ -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") +} \ No newline at end of file diff --git a/main.py b/main.py index d21913b..f8a7c63 100644 --- a/main.py +++ b/main.py @@ -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) diff --git a/src/ast/stmt.py b/src/ast/stmt.py index 8d6c2ad..882b9c8 100644 --- a/src/ast/stmt.py +++ b/src/ast/stmt.py @@ -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) diff --git a/src/formatter.py b/src/formatter.py index 97dd8ef..f41c548 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, 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") diff --git a/src/interpreter/exceptions.py b/src/interpreter/exceptions.py index c6c3ca4..4b2ae5d 100644 --- a/src/interpreter/exceptions.py +++ b/src/interpreter/exceptions.py @@ -9,3 +9,7 @@ class ReturnException(RuntimeError): class BreakException(RuntimeError): pass + + +class ContinueException(RuntimeError): + pass diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 1fd6fd7..3aec41b 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, 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.") diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 450c063..0928d7e 100644 --- a/src/interpreter/resolver.py +++ b/src/interpreter/resolver.py @@ -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") diff --git a/src/keyword.py b/src/keyword.py index cb79f36..5885036 100644 --- a/src/keyword.py +++ b/src/keyword.py @@ -19,4 +19,5 @@ KEYWORDS: dict[str, TokenType] = { "print": TokenType.PRINT, "return": TokenType.RETURN, "break": TokenType.BREAK, + "continue": TokenType.CONTINUE, } diff --git a/src/parser/parser.py b/src/parser/parser.py index 541052a..c69cd51 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, 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() diff --git a/src/token.py b/src/token.py index ee82083..6139d13 100644 --- a/src/token.py +++ b/src/token.py @@ -57,6 +57,7 @@ class TokenType(Enum): BY = auto() RETURN = auto() BREAK = auto() + CONTINUE = auto() # Misc PRINT = auto()