feat: add for loops
This commit is contained in:
@@ -1,15 +1,24 @@
|
||||
print("1,2,3,4,5")
|
||||
for i from 1 to 5 {
|
||||
print(i)
|
||||
}
|
||||
|
||||
print("0,2,4,6,8")
|
||||
for j from 0 until 10 by 2 {
|
||||
print(j)
|
||||
}
|
||||
|
||||
print("0,1,2,3,4")
|
||||
for k to 4 {
|
||||
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
|
||||
while l < 10 {
|
||||
print(l)
|
||||
|
||||
2
main.py
2
main.py
@@ -13,7 +13,7 @@ def main():
|
||||
123
|
||||
"This is
|
||||
another string" """
|
||||
path: str = "examples/10_while.peb"
|
||||
path: str = "examples/05_loop.peb"
|
||||
with open(path, "r") as f:
|
||||
source = f.read()
|
||||
lexer: Lexer = Lexer()
|
||||
|
||||
@@ -41,6 +41,10 @@ class Stmt(ABC):
|
||||
def visit_while_stmt(self, stmt: WhileStmt) -> T:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def visit_for_stmt(self, stmt: ForStmt) -> T:
|
||||
...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BlockStmt(Stmt):
|
||||
@@ -85,7 +89,6 @@ class LetStmt(Stmt):
|
||||
return visitor.visit_let_stmt(self)
|
||||
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WhileStmt(Stmt):
|
||||
condition: Expr
|
||||
@@ -93,3 +96,18 @@ class WhileStmt(Stmt):
|
||||
|
||||
def accept(self, visitor: Stmt.Visitor[T]) -> T:
|
||||
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)
|
||||
|
||||
@@ -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.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.error import PebbleRuntimeError
|
||||
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)):
|
||||
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:
|
||||
value: Any = 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):
|
||||
return
|
||||
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.")
|
||||
|
||||
@@ -10,6 +10,7 @@ KEYWORDS: dict[str, TokenType] = {
|
||||
"while": TokenType.WHILE,
|
||||
"from": TokenType.FROM,
|
||||
"to": TokenType.TO,
|
||||
"until": TokenType.UNTIL,
|
||||
"by": TokenType.BY,
|
||||
"false": TokenType.FALSE,
|
||||
"true": TokenType.TRUE,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from typing import Optional
|
||||
|
||||
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.pebble import Pebble
|
||||
from src.token import Token, TokenType
|
||||
@@ -34,8 +34,8 @@ class Parser:
|
||||
statements: list[Stmt] = []
|
||||
self.skip_newlines()
|
||||
while not self.is_at_end():
|
||||
self.skip_newlines()
|
||||
statements.append(self.declaration())
|
||||
self.skip_newlines()
|
||||
return statements
|
||||
|
||||
def skip_newlines(self):
|
||||
@@ -107,6 +107,8 @@ class Parser:
|
||||
return LetStmt(name, initializer)
|
||||
|
||||
def statement(self) -> Stmt:
|
||||
if self.match(TokenType.FOR):
|
||||
return self.for_stmt()
|
||||
if self.match(TokenType.IF):
|
||||
return self.if_stmt()
|
||||
if self.match(TokenType.PRINT):
|
||||
@@ -117,6 +119,61 @@ class Parser:
|
||||
return self.block_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:
|
||||
condition: Expr = self.expression()
|
||||
then_branch: Stmt = self.statement()
|
||||
|
||||
@@ -52,6 +52,7 @@ class TokenType(Enum):
|
||||
WHILE = auto()
|
||||
FROM = auto()
|
||||
TO = auto()
|
||||
UNTIL = auto()
|
||||
BY = auto()
|
||||
|
||||
# Misc
|
||||
@@ -73,5 +74,7 @@ class Token:
|
||||
res: str = f"[{self.type.name}"
|
||||
if self.value is not None:
|
||||
res += f" ({self.value!r})"
|
||||
elif self.type == TokenType.IDENTIFIER:
|
||||
res += f" ({self.lexeme})"
|
||||
res += "]"
|
||||
return res
|
||||
|
||||
Reference in New Issue
Block a user