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 {
|
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)
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user