feat: add continue statements
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
for i to 10 {
|
||||
if i == 5 {
|
||||
break
|
||||
}
|
||||
print(i)
|
||||
}
|
||||
14
examples/16_break_continue.peb
Normal file
14
examples/16_break_continue.peb
Normal 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")
|
||||
}
|
||||
2
main.py
2
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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -9,3 +9,7 @@ class ReturnException(RuntimeError):
|
||||
|
||||
class BreakException(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class ContinueException(RuntimeError):
|
||||
pass
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -19,4 +19,5 @@ KEYWORDS: dict[str, TokenType] = {
|
||||
"print": TokenType.PRINT,
|
||||
"return": TokenType.RETURN,
|
||||
"break": TokenType.BREAK,
|
||||
"continue": TokenType.CONTINUE,
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -57,6 +57,7 @@ class TokenType(Enum):
|
||||
BY = auto()
|
||||
RETURN = auto()
|
||||
BREAK = auto()
|
||||
CONTINUE = auto()
|
||||
|
||||
# Misc
|
||||
PRINT = auto()
|
||||
|
||||
Reference in New Issue
Block a user