feat: add for loops

This commit is contained in:
2026-02-06 03:53:55 +01:00
parent be1b03bb1f
commit bf750748e3
7 changed files with 148 additions and 6 deletions

View File

@@ -1,15 +1,24 @@
print("1,2,3,4,5")
for i from 1 to 5 { for i from 1 to 5 {
print(i) print(i)
} }
print("0,2,4,6,8")
for j from 0 until 10 by 2 { for j from 0 until 10 by 2 {
print(j) print(j)
} }
print("0,1,2,3,4")
for k to 4 { for k to 4 {
print(k) print(k)
} }
print("5,4,3,2,1")
for k from 5 to 1 by -1 {
print(k)
}
print("0,3,6,9")
let l = 0 let l = 0
while l < 10 { while l < 10 {
print(l) print(l)

View File

@@ -13,7 +13,7 @@ def main():
123 123
"This is "This is
another string" """ another string" """
path: str = "examples/10_while.peb" path: str = "examples/05_loop.peb"
with open(path, "r") as f: with open(path, "r") as f:
source = f.read() source = f.read()
lexer: Lexer = Lexer() lexer: Lexer = Lexer()

View File

@@ -41,6 +41,10 @@ class Stmt(ABC):
def visit_while_stmt(self, stmt: WhileStmt) -> T: def visit_while_stmt(self, stmt: WhileStmt) -> T:
... ...
@abstractmethod
def visit_for_stmt(self, stmt: ForStmt) -> T:
...
@dataclass(frozen=True) @dataclass(frozen=True)
class BlockStmt(Stmt): class BlockStmt(Stmt):
@@ -85,7 +89,6 @@ class LetStmt(Stmt):
return visitor.visit_let_stmt(self) return visitor.visit_let_stmt(self)
@dataclass(frozen=True) @dataclass(frozen=True)
class WhileStmt(Stmt): class WhileStmt(Stmt):
condition: Expr condition: Expr
@@ -93,3 +96,18 @@ class WhileStmt(Stmt):
def accept(self, visitor: Stmt.Visitor[T]) -> T: def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_while_stmt(self) return visitor.visit_while_stmt(self)
@dataclass(frozen=True)
class ForStmt(Stmt):
variable: Token
start_token: Optional[Token]
start: Optional[Expr]
end_token: Optional[Token]
end: Optional[Expr]
step_token: Optional[Token]
step: Optional[Expr]
body: Stmt
def accept(self, visitor: Stmt.Visitor[T]) -> T:
return visitor.visit_for_stmt(self)

View File

@@ -1,7 +1,7 @@
from typing import Any 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
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt
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.pebble import Pebble from src.pebble import Pebble
@@ -137,6 +137,54 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
while self.is_truthy(self.evaluate(stmt.condition)): while self.is_truthy(self.evaluate(stmt.condition)):
self.execute(stmt.body) self.execute(stmt.body)
def visit_for_stmt(self, stmt: ForStmt) -> None:
previous_env: Environment = self.env
self.env = Environment(self.env)
start_value: float = 0
if stmt.start is not None:
start: Any = self.evaluate(stmt.start)
self.check_number_clause(stmt.start_token, start)
start_value = start
end_value: Optional[float] = None
inclusive: Optional[bool] = None
if stmt.end is not None:
end: Any = self.evaluate(stmt.end)
self.check_number_clause(stmt.end_token, end)
end_value = end
inclusive = stmt.end_token.type == TokenType.TO
step_value: float = 1
if stmt.step is not None:
step: Any = self.evaluate(stmt.step)
self.check_number_clause(stmt.step_token, step)
step_value = step
value: float = start_value
def condition() -> bool:
if end_value is None:
return False
match (step_value < 0, inclusive):
case (False, False):
return value < end_value
case (False, True):
return value <= end_value
case (True, False):
return value > end_value
case (True, True):
return value >= end_value
# Unreachable
return True
while condition():
self.env.define(stmt.variable.lexeme, value)
self.execute(stmt.body)
value += step_value
self.env = previous_env
def visit_let_stmt(self, stmt: LetStmt) -> None: def visit_let_stmt(self, stmt: LetStmt) -> None:
value: Any = None value: Any = None
if stmt.initializer is not None: if stmt.initializer is not None:
@@ -166,3 +214,9 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]):
if isinstance(left, float) and isinstance(right, float): if isinstance(left, float) and isinstance(right, float):
return return
raise PebbleRuntimeError(operator, "Operands must be numbers.") raise PebbleRuntimeError(operator, "Operands must be numbers.")
@staticmethod
def check_number_clause(clause: Token, value: Any):
if isinstance(value, float):
return
raise PebbleRuntimeError(clause, "For loop clauses must be numbers.")

View File

@@ -10,6 +10,7 @@ KEYWORDS: dict[str, TokenType] = {
"while": TokenType.WHILE, "while": TokenType.WHILE,
"from": TokenType.FROM, "from": TokenType.FROM,
"to": TokenType.TO, "to": TokenType.TO,
"until": TokenType.UNTIL,
"by": TokenType.BY, "by": TokenType.BY,
"false": TokenType.FALSE, "false": TokenType.FALSE,
"true": TokenType.TRUE, "true": TokenType.TRUE,

View File

@@ -1,7 +1,7 @@
from typing import Optional 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
from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt
from src.parser.error import ParsingError from src.parser.error import ParsingError
from src.pebble import Pebble from src.pebble import Pebble
from src.token import Token, TokenType from src.token import Token, TokenType
@@ -34,8 +34,8 @@ class Parser:
statements: list[Stmt] = [] statements: list[Stmt] = []
self.skip_newlines() self.skip_newlines()
while not self.is_at_end(): while not self.is_at_end():
self.skip_newlines()
statements.append(self.declaration()) statements.append(self.declaration())
self.skip_newlines()
return statements return statements
def skip_newlines(self): def skip_newlines(self):
@@ -107,6 +107,8 @@ class Parser:
return LetStmt(name, initializer) return LetStmt(name, initializer)
def statement(self) -> Stmt: def statement(self) -> Stmt:
if self.match(TokenType.FOR):
return self.for_stmt()
if self.match(TokenType.IF): if self.match(TokenType.IF):
return self.if_stmt() return self.if_stmt()
if self.match(TokenType.PRINT): if self.match(TokenType.PRINT):
@@ -117,6 +119,61 @@ class Parser:
return self.block_stmt() return self.block_stmt()
return self.expression_stmt() return self.expression_stmt()
def for_stmt(self) -> Stmt:
var: Token = self.consume(TokenType.IDENTIFIER, "Missing loop variable.")
end: Optional[Expr] = None
from_clause: Optional[Expr] = None
to_clause: Optional[Expr] = None
until_clause: Optional[Expr] = None
by_clause: Optional[Expr] = None
from_token: Optional[Token] = None
end_token: Optional[Token] = None
by_token: Optional[Token] = None
while self.match(TokenType.FROM, TokenType.TO, TokenType.UNTIL, TokenType.BY):
previous: Token = self.previous()
match previous.type:
case TokenType.FROM:
if from_clause is not None:
raise self.error(previous, "From clause already defined.")
from_clause = self.expression()
from_token = previous
case TokenType.TO:
if to_clause is not None:
raise self.error(previous, "To clause already defined.")
if until_clause is not None:
raise self.error(previous, "Until clause already defined.")
to_clause = self.expression()
end = to_clause
end_token = previous
case TokenType.UNTIL:
if until_clause is not None:
raise self.error(previous, "Until clause already defined.")
if to_clause is not None:
raise self.error(previous, "To clause already defined.")
until_clause = self.expression()
end = until_clause
end_token = previous
case TokenType.BY:
if by_clause is not None:
raise self.error(previous, "By clause already defined.")
by_clause = self.expression()
by_token = previous
body: Stmt = self.statement()
loop: Stmt = ForStmt(
variable=var,
start_token=from_token,
start=from_clause,
end_token=end_token,
end=end,
step_token=by_token,
step=by_clause,
body=body
)
return loop
def if_stmt(self) -> Stmt: def if_stmt(self) -> Stmt:
condition: Expr = self.expression() condition: Expr = self.expression()
then_branch: Stmt = self.statement() then_branch: Stmt = self.statement()

View File

@@ -52,6 +52,7 @@ class TokenType(Enum):
WHILE = auto() WHILE = auto()
FROM = auto() FROM = auto()
TO = auto() TO = auto()
UNTIL = auto()
BY = auto() BY = auto()
# Misc # Misc
@@ -73,5 +74,7 @@ class Token:
res: str = f"[{self.type.name}" res: str = f"[{self.type.name}"
if self.value is not None: if self.value is not None:
res += f" ({self.value!r})" res += f" ({self.value!r})"
elif self.type == TokenType.IDENTIFIER:
res += f" ({self.lexeme})"
res += "]" res += "]"
return res return res