diff --git a/examples/09_logical.peb b/examples/09_logical.peb new file mode 100644 index 0000000..41ced95 --- /dev/null +++ b/examples/09_logical.peb @@ -0,0 +1,2 @@ +print("hi" or 2) // "hi". +print(null or "yes") // "yes". \ No newline at end of file diff --git a/main.py b/main.py index 2ea2368..a048745 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,7 @@ def main(): 123 "This is another string" """ - path: str = "examples/04_if_else.peb" + path: str = "examples/09_logical.peb" with open(path, "r") as f: source = f.read() lexer: Lexer = Lexer() diff --git a/src/ast/expr.py b/src/ast/expr.py index e655394..63f5883 100644 --- a/src/ast/expr.py +++ b/src/ast/expr.py @@ -41,6 +41,10 @@ class Expr(ABC): def visit_variable_expr(self, expr: VariableExpr) -> T: ... + @abstractmethod + def visit_logical_expr(self, expr: LogicalExpr) -> T: + ... + @dataclass(frozen=True) class AssignExpr(Expr): @@ -92,3 +96,13 @@ class VariableExpr(Expr): def accept(self, visitor: Expr.Visitor[T]) -> T: return visitor.visit_variable_expr(self) + + +@dataclass(frozen=True) +class LogicalExpr(Expr): + left: Expr + operator: Token + right: Expr + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_logical_expr(self) diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index c4c9a35..00bc85d 100644 --- a/src/interpreter/interpreter.py +++ b/src/interpreter/interpreter.py @@ -1,6 +1,6 @@ from typing import Any -from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr +from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt from src.interpreter.environment import Environment from src.interpreter.error import PebbleRuntimeError @@ -40,6 +40,21 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]): self.env.assign(expr.name, value) return value + def visit_logical_expr(self, expr: LogicalExpr) -> Any: + left: Any = self.evaluate(expr.left) + + match expr.operator.type: + case TokenType.OR: + if self.is_truthy(left): + return left + case TokenType.AND: + if not self.is_truthy(left): + return left + case _: + # Unreachable + raise PebbleRuntimeError(expr.operator, f"Unknown logical operator") + return self.evaluate(expr.right) + def visit_binary_expr(self, expr: BinaryExpr) -> Any: left: Any = self.evaluate(expr.left) right: Any = self.evaluate(expr.right) diff --git a/src/parser/parser.py b/src/parser/parser.py index 643a1f0..613e0a3 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -1,6 +1,6 @@ from typing import Optional -from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr +from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr from src.ast.stmt import Stmt, PrintStmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt from src.parser.error import ParsingError from src.pebble import Pebble @@ -149,7 +149,7 @@ class Parser: return self.assignment() def assignment(self) -> Expr: - expr: Expr = self.equality() + expr: Expr = self.or_() if self.match(TokenType.EQUAL, TokenType.PLUS_EQUAL, TokenType.MINUS_EQUAL, TokenType.STAR_EQUAL, TokenType.SLASH_EQUAL): operator: Token = self.previous() value: Expr = self.assignment() @@ -169,6 +169,22 @@ class Parser: self.error(operator, "Invalid assignment target.") return expr + def or_(self) -> Expr: + expr: Expr = self.and_() + while self.match(TokenType.OR): + operator: Token = self.previous() + right: Expr = self.and_() + expr = LogicalExpr(expr, operator, right) + return expr + + def and_(self) -> Expr: + expr: Expr = self.equality() + while self.match(TokenType.AND): + operator: Token = self.previous() + right: Expr = self.equality() + expr = LogicalExpr(expr, operator, right) + return expr + def equality(self) -> Expr: expr: Expr = self.comparison() while self.match(TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL):