feat(interpreter): add basic interpreter
This commit is contained in:
6
main.py
6
main.py
@@ -1,5 +1,8 @@
|
||||
from typing import Any
|
||||
|
||||
from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr
|
||||
from src.ast.printer import AstPrinter
|
||||
from src.interpreter.interpreter import Interpreter
|
||||
from src.lexer import Lexer
|
||||
from src.parser.parser import Parser
|
||||
from src.token import Token, TokenType
|
||||
@@ -38,6 +41,9 @@ def main():
|
||||
return
|
||||
|
||||
print(printer.print(ast))
|
||||
interpreter: Interpreter = Interpreter()
|
||||
result: Any = interpreter.interpret(ast)
|
||||
print(f"Result: {result}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
0
src/interpreter/__init__.py
Normal file
0
src/interpreter/__init__.py
Normal file
7
src/interpreter/error.py
Normal file
7
src/interpreter/error.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from src.token import Token
|
||||
|
||||
|
||||
class PebbleRuntimeError(RuntimeError):
|
||||
def __init__(self, token: Token, msg: str):
|
||||
super().__init__(msg)
|
||||
self.token: Token = token
|
||||
100
src/interpreter/interpreter.py
Normal file
100
src/interpreter/interpreter.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from typing import Any
|
||||
|
||||
from src.ast.expr import Visitor, LiteralExpr, T, GroupingExpr, UnaryExpr, BinaryExpr, Expr
|
||||
from src.interpreter.error import PebbleRuntimeError
|
||||
from src.pebble import Pebble
|
||||
from src.token import TokenType, Token
|
||||
|
||||
|
||||
class Interpreter(Visitor[Any]):
|
||||
def interpret(self, expr: Expr) -> Any:
|
||||
try:
|
||||
return self.evaluate(expr)
|
||||
except PebbleRuntimeError as e:
|
||||
Pebble.runtime_error(e)
|
||||
|
||||
def evaluate(self, expr: Expr) -> Any:
|
||||
return expr.accept(self)
|
||||
|
||||
def visit_binary_expr(self, expr: BinaryExpr) -> Any:
|
||||
left: Any = self.evaluate(expr.left)
|
||||
right: Any = self.evaluate(expr.right)
|
||||
|
||||
match expr.operator.type:
|
||||
case TokenType.MINUS:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) - float(right)
|
||||
case TokenType.SLASH:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) / float(right)
|
||||
case TokenType.STAR:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) * float(right)
|
||||
case TokenType.PLUS:
|
||||
if isinstance(left, float) and isinstance(right, float):
|
||||
return float(left) + float(right)
|
||||
if isinstance(left, str) and isinstance(right, str):
|
||||
return str(left) + str(right)
|
||||
raise PebbleRuntimeError(expr.operator, "Operators must be two numbers or two strings.")
|
||||
case TokenType.GREATER:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) > float(right)
|
||||
case TokenType.GREATER_EQUAL:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) >= float(right)
|
||||
case TokenType.LESS:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) < float(right)
|
||||
case TokenType.LESS_EQUAL:
|
||||
self.check_number_operands(expr.operator, left, right)
|
||||
return float(left) <= float(right)
|
||||
case TokenType.EQUAL_EQUAL:
|
||||
return self.is_equal(left, right)
|
||||
case TokenType.BANG_EQUAL:
|
||||
return not self.is_equal(left, right)
|
||||
|
||||
# Unreachable
|
||||
return None
|
||||
|
||||
def visit_unary_expr(self, expr: UnaryExpr) -> Any:
|
||||
right: Any = self.evaluate(expr.right)
|
||||
|
||||
match expr.operator.type:
|
||||
case TokenType.MINUS:
|
||||
self.check_number_operand(expr.operator, right)
|
||||
return -float(right)
|
||||
case TokenType.BANG:
|
||||
return not self.is_truthy(right)
|
||||
|
||||
# Unreachable
|
||||
return None
|
||||
|
||||
def visit_grouping_expr(self, expr: GroupingExpr) -> Any:
|
||||
return self.evaluate(expr.expression)
|
||||
|
||||
def visit_literal_expr(self, expr: LiteralExpr) -> Any:
|
||||
return expr.value
|
||||
|
||||
@staticmethod
|
||||
def is_truthy(value: Any) -> bool:
|
||||
if value is None or value is False:
|
||||
return False
|
||||
if value is True:
|
||||
return True
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def is_equal(a: Any, b: Any) -> bool:
|
||||
return a == b
|
||||
|
||||
@staticmethod
|
||||
def check_number_operand(operator: Token, operand: Any):
|
||||
if isinstance(operand, 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):
|
||||
return
|
||||
raise PebbleRuntimeError(operator, "Operands must be numbers.")
|
||||
@@ -1,3 +1,4 @@
|
||||
from src.interpreter.error import PebbleRuntimeError
|
||||
from src.position import Position
|
||||
from src.token import Token, TokenType
|
||||
|
||||
@@ -17,3 +18,7 @@ class Pebble:
|
||||
cls.report(token.position, " at end", msg)
|
||||
else:
|
||||
cls.report(token.position, f" at '{token.lexeme}'", msg)
|
||||
|
||||
@classmethod
|
||||
def runtime_error(cls, error: PebbleRuntimeError):
|
||||
print(f"{error}\n({error.token.position})")
|
||||
|
||||
Reference in New Issue
Block a user