353 lines
12 KiB
Python
353 lines
12 KiB
Python
import operator
|
|
from typing import List
|
|
from .types.callable import Callable
|
|
from .types.japlclass import JAPLClass
|
|
from .types.instance import JAPLInstance
|
|
from .meta.environment import Environment
|
|
from .meta.tokentype import TokenType
|
|
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
|
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass
|
|
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set
|
|
from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
|
|
|
|
|
|
class Interpreter(Expression.Visitor, Statement.Visitor):
|
|
"""
|
|
An interpreter for the JAPL
|
|
programming language
|
|
"""
|
|
|
|
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
|
|
TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
|
|
TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
|
|
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
|
|
|
|
def __init__(self):
|
|
"""Object constructor"""
|
|
|
|
self.environment = Environment()
|
|
self.locals = {}
|
|
self.globals = self.environment
|
|
self.globals.define("clock", Clock())
|
|
self.globals.define("type", Type())
|
|
self.globals.define("truthy", Truthy())
|
|
self.globals.define("stringify", Stringify())
|
|
self.globals.define("print", PrintFunction())
|
|
self.globals.define("isinstance", IsInstance())
|
|
self.globals.define("issuperclass", IsSuperclass())
|
|
self.globals.define("issubclass", IsSubclass())
|
|
|
|
def number_operand(self, op, operand):
|
|
"""
|
|
An helper method to check if the operand
|
|
to a unary operator is a number
|
|
"""
|
|
|
|
if isinstance(operand, (int, float)):
|
|
return
|
|
raise JAPLError(op,
|
|
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
|
|
|
|
def compatible_operands(self, op, left, right):
|
|
"""
|
|
Helper method to check types when doing binary
|
|
operations
|
|
"""
|
|
|
|
if op.kind == TokenType.SLASH and right == 0:
|
|
raise JAPLError(op, "Cannot divide by 0")
|
|
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
|
|
if op.kind not in (TokenType.DEQ, TokenType.NE):
|
|
raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
|
return
|
|
elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
return
|
|
elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
|
|
if isinstance(left, str) and isinstance(right, str):
|
|
return
|
|
elif isinstance(left, str) and isinstance(right, int):
|
|
return
|
|
elif isinstance(left, int) and isinstance(right, str):
|
|
return
|
|
raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
|
|
|
def visit_literal(self, expr: Literal):
|
|
"""
|
|
Visits a Literal node in the Abstract Syntax Tree,
|
|
returning its value to the visitor
|
|
"""
|
|
|
|
return expr.value
|
|
|
|
def visit_logical(self, expr: Logical):
|
|
"""Visits a logical node"""
|
|
|
|
left = self.eval(expr.left)
|
|
if expr.operator.kind == TokenType.OR:
|
|
if bool(left):
|
|
return left
|
|
elif not bool(left):
|
|
return self.eval(expr.right)
|
|
return self.eval(expr.right)
|
|
|
|
def eval(self, expr: Expression):
|
|
"""
|
|
Evaluates an expression by calling its accept()
|
|
method and passing self to it. This mechanism is known
|
|
as the 'Visitor Pattern': the expression object will
|
|
later call the interpreter's appropriate method to
|
|
evaluate itself
|
|
"""
|
|
|
|
return expr.accept(self)
|
|
|
|
def visit_grouping(self, grouping: Grouping):
|
|
"""
|
|
Visits a Grouping node in the Abstract Syntax Tree,
|
|
recursively evaluating its subexpressions
|
|
"""
|
|
|
|
return self.eval(grouping.expr)
|
|
|
|
def visit_unary(self, expr: Unary):
|
|
"""
|
|
Visits a Unary node in the Abstract Syntax Teee,
|
|
returning the negation of the given object, if
|
|
the operation is supported
|
|
"""
|
|
|
|
right = self.eval(expr.right)
|
|
self.number_operand(expr.operator, right)
|
|
if expr.operator.kind == TokenType.NEG:
|
|
return not right
|
|
return -right
|
|
|
|
def visit_binary(self, expr: Binary):
|
|
"""
|
|
Visits a Binary node in the Abstract Syntax Tree,
|
|
recursively evaulating both operands first and then
|
|
performing the operation specified by the operator
|
|
"""
|
|
|
|
left = self.eval(expr.left)
|
|
right = self.eval(expr.right)
|
|
self.compatible_operands(expr.operator, left, right)
|
|
return self.OPS[expr.operator.kind](left, right)
|
|
|
|
def visit_statement_expr(self, stmt: StatementExpr):
|
|
"""
|
|
Visits an expression statement and evaluates it
|
|
"""
|
|
|
|
self.eval(stmt.expression)
|
|
|
|
def visit_if(self, statement: If):
|
|
"""
|
|
Visits an If node and evaluates it
|
|
"""
|
|
|
|
if self.eval(statement.condition):
|
|
self.exec(statement.then_branch)
|
|
elif statement.else_branch:
|
|
self.exec(statement.else_branch)
|
|
|
|
def visit_class(self, stmt: Class):
|
|
"""Visits a class declaration"""
|
|
|
|
superclass = None
|
|
if stmt.superclass:
|
|
superclass = self.eval(stmt.superclass)
|
|
if not isinstance(superclass, JAPLClass):
|
|
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
|
|
self.environment.define(stmt.name.lexeme, None)
|
|
if superclass:
|
|
environment = Environment(self.environment)
|
|
environment.define("super", superclass)
|
|
else:
|
|
environment = self.environment
|
|
methods = {}
|
|
for method in stmt.methods:
|
|
func = JAPLFunction(method, environment)
|
|
methods[method.name.lexeme] = func
|
|
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
|
|
if superclass:
|
|
self.environment = environment.enclosing
|
|
self.environment.assign(stmt.name, klass)
|
|
|
|
def visit_while(self, statement: While):
|
|
"""
|
|
Visits a while node and executes it
|
|
"""
|
|
|
|
while self.eval(statement.condition):
|
|
try:
|
|
self.exec(statement.body)
|
|
except BreakException:
|
|
break
|
|
|
|
def visit_var_stmt(self, stmt: Var):
|
|
"""
|
|
Visits a var statement
|
|
"""
|
|
|
|
val = None
|
|
if stmt.init:
|
|
val = self.eval(stmt.init)
|
|
self.environment.define(stmt.name.lexeme, val)
|
|
|
|
def lookup(self, name, expr: Expression):
|
|
"""
|
|
Performs name lookups in the closest scope
|
|
"""
|
|
|
|
distance = self.locals.get(expr)
|
|
if distance is not None:
|
|
return self.environment.get_at(distance, name.lexeme)
|
|
else:
|
|
return self.globals.get(name)
|
|
|
|
def visit_var_expr(self, expr: Variable):
|
|
"""
|
|
Visits a var expression
|
|
"""
|
|
|
|
return self.lookup(expr.name, expr)
|
|
|
|
def visit_del(self, stmt: Del):
|
|
"""
|
|
Visits a del expression
|
|
"""
|
|
|
|
return self.environment.delete(stmt.name)
|
|
|
|
def visit_assign(self, stmt: Assignment):
|
|
"""
|
|
Visits an assignment expression
|
|
"""
|
|
|
|
right = self.eval(stmt.value)
|
|
distance = self.locals.get(stmt)
|
|
if distance is not None:
|
|
self.environment.assign_at(distance, stmt.name, right)
|
|
else:
|
|
self.globals.assign(stmt.name, right)
|
|
return right
|
|
|
|
def visit_block(self, stmt: Block):
|
|
"""
|
|
Visits a new scope block
|
|
"""
|
|
|
|
return self.execute_block(stmt.statements, Environment(self.environment))
|
|
|
|
def visit_break(self, stmt: Break):
|
|
"""
|
|
Visits a break statement
|
|
"""
|
|
|
|
raise BreakException()
|
|
|
|
def visit_call_expr(self, expr: Call):
|
|
"""
|
|
Visits a call expression
|
|
"""
|
|
|
|
callee = self.eval(expr.callee)
|
|
if not isinstance(callee, Callable):
|
|
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
|
|
arguments = []
|
|
for argument in expr.arguments:
|
|
arguments.append(self.eval(argument))
|
|
function = callee
|
|
if function.arity != len(arguments):
|
|
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
|
|
return function.call(self, arguments)
|
|
|
|
def execute_block(self, statements: List[Statement], scope: Environment):
|
|
"""
|
|
Executes a block of statements
|
|
"""
|
|
|
|
prev = self.environment
|
|
try:
|
|
self.environment = scope
|
|
for statement in statements:
|
|
self.exec(statement)
|
|
finally:
|
|
self.environment = prev
|
|
|
|
def visit_return(self, statement: Return):
|
|
"""
|
|
Visits a return statement
|
|
"""
|
|
|
|
value = None
|
|
if statement.value:
|
|
value = self.eval(statement.value)
|
|
raise ReturnException(value)
|
|
|
|
def visit_function(self, statement: Function):
|
|
"""
|
|
Visits a function
|
|
"""
|
|
|
|
function = JAPLFunction(statement, self.environment)
|
|
self.environment.define(statement.name.lexeme, function)
|
|
|
|
def visit_get(self, expr: Get):
|
|
"""Visits property get expressions and evaluates them"""
|
|
|
|
obj = self.eval(expr.object)
|
|
if isinstance(obj, JAPLInstance):
|
|
return obj.get(expr.name)
|
|
raise JAPLError(expr.name, "Only instances have properties")
|
|
|
|
def visit_set(self, expr: Set):
|
|
"""Visits property set expressions and evaluates them"""
|
|
|
|
obj = self.eval(expr.object)
|
|
if not isinstance(obj, JAPLInstance):
|
|
raise JAPLError(expr, "Only instances have fields")
|
|
value = self.eval(expr.value)
|
|
obj.set(expr.name, value)
|
|
|
|
def visit_this(self, expr):
|
|
"""Evaluates 'this' expressions"""
|
|
|
|
return self.lookup(expr.keyword, expr)
|
|
|
|
def visit_super(self, expr):
|
|
"""Evaluates 'super' expressions"""
|
|
|
|
distance = self.locals.get(expr)
|
|
superclass = self.environment.get_at(distance, "super")
|
|
instance = self.environment.get_at(distance - 1, "this")
|
|
meth = superclass.get_method(expr.method.lexeme)
|
|
if not meth:
|
|
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
|
|
return meth.bind(instance)
|
|
|
|
def exec(self, statement: Statement):
|
|
"""
|
|
Executes a statement
|
|
"""
|
|
|
|
statement.accept(self)
|
|
|
|
def interpret(self, statements: List[Statement]):
|
|
"""
|
|
Executes a JAPL program
|
|
"""
|
|
|
|
for statement in statements:
|
|
self.exec(statement)
|
|
|
|
def resolve(self, expr: Expression, depth: int):
|
|
"""
|
|
Stores the result of the name resolution: this
|
|
info will be used later to know exactly in which
|
|
environment to look up a given variable
|
|
"""
|
|
|
|
self.locals[expr] = depth # How many environments to skip!
|