Added methods, properties, setters and getters to class objects

This commit is contained in:
nocturn9x 2020-07-31 13:44:59 +02:00
parent 8f4fe11813
commit 6becfb749e
8 changed files with 95 additions and 12 deletions

View File

@ -2,11 +2,12 @@ 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 .types.native import Clock, Type, JAPLFunction, Truthy, Stringify
from .meta.exceptions import JAPLError, BreakException, ReturnException
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set
from .meta.statement import Statement, Print, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
@ -31,7 +32,6 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
self.globals.define("type", Type())
self.globals.define("truthy", Truthy())
self.globals.define("stringify", Stringify())
self.looping = False
def number_operand(self, op, operand):
"""
@ -161,7 +161,11 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
"""Visits a class declaration"""
self.environment.define(stmt.name.lexeme, None)
klass = JAPLClass(stmt.name.lexeme)
methods = {}
for method in stmt.methods:
func = JAPLFunction(method, self.environment)
methods[method.name.lexeme] = func
klass = JAPLClass(stmt.name.lexeme, methods)
self.environment.assign(stmt.name, klass)
def visit_while(self, statement: While):
@ -169,13 +173,11 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
Visits a while node and executes it
"""
self.looping = True
while self.eval(statement.condition):
try:
self.exec(statement.body)
except BreakException:
break
self.looping = False
def visit_var_stmt(self, stmt: Var):
"""
@ -237,9 +239,7 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
Visits a break statement
"""
if self.looping:
raise BreakException()
raise JAPLError(stmt.token, "'break' outside loop")
raise BreakException()
def visit_call_expr(self, expr: Call):
"""
@ -288,6 +288,23 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
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 exec(self, statement: Statement):
"""
Executes a statement

View File

@ -36,6 +36,14 @@ class Expression(object):
def visit_unary(self, visitor):
raise NotImplementedError
@staticmethod
def visit_get(self, visitor):
raise NotImplementedError
@staticmethod
def visit_set(self, visitor):
raise NotImplementedError
@dataclass
class Binary(Expression):
@ -94,6 +102,7 @@ class Assignment(Expression):
def __hash__(self):
return super().__hash__()
@dataclass
class Logical(Expression):
left: Expression
@ -113,6 +122,7 @@ class Call(Expression):
def accept(self, visitor):
return visitor.visit_call_expr(self)
@dataclass
class Get(Expression):
object: Expression
@ -120,3 +130,13 @@ class Get(Expression):
def accept(self, visitor):
return visitor.visit_get(self)
@dataclass
class Set(Expression):
object: Expression
name: Token
value: Expression
def accept(self, visitor):
return visitor.visit_set(self)

View File

@ -4,3 +4,4 @@ from enum import Enum, auto
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
METHOD = auto()

7
JAPL/meta/looptype.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum, auto
class LoopType(Enum):
NONE = auto()
WHILE = auto()

View File

@ -2,7 +2,7 @@ from .meta.exceptions import ParseError
from .meta.tokentype import TokenType
from .meta.tokenobject import Token
from typing import List, Union
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression, Get
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression, Get, Set
from .meta.statement import Print, StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Class
@ -238,6 +238,8 @@ class Parser(object):
if isinstance(expr, Variable):
name = expr.name
return Assignment(name, value)
elif isinstance(expr, Get):
return Set(expr.object, expr.name, value)
raise self.throw(eq, "Invalid syntax")
return expr

View File

@ -2,10 +2,11 @@ from .meta.exceptions import JAPLError
from .meta.expression import Expression
from .meta.statement import Statement
from .meta.functiontype import FunctionType
from .meta.looptype import LoopType
try:
from functools import singledispatchmethod
except ImportError:
from singledispatchmethod import singledispatchmethod
from singledispatchmethod import singledispatchmethod # Backport
from typing import List, Union
from collections import deque
@ -24,6 +25,7 @@ class Resolver(Expression.Visitor, Statement.Visitor):
self.interpreter = interpreter
self.scopes = deque()
self.current_function = FunctionType.NONE
self.current_loop = LoopType.NONE
@singledispatchmethod
def resolve(self, stmt_or_expr: Union[Statement, Expression, List[Statement]]):
@ -151,6 +153,9 @@ class Resolver(Expression.Visitor, Statement.Visitor):
def visit_class(self, stmt):
self.declare(stmt.name)
self.define(stmt.name)
for method in stmt.methods:
ftype = FunctionType.METHOD
self.resolve_function(method, ftype)
def visit_statement_expr(self, stmt):
"""Visits a statement expression node"""
@ -181,8 +186,11 @@ class Resolver(Expression.Visitor, Statement.Visitor):
def visit_while(self, stmt):
"""Visits a while statement node"""
loop = self.current_loop
self.current_loop = LoopType.WHILE
self.resolve(stmt.condition)
self.resolve(stmt.body)
self.current_loop = loop
def visit_binary(self, expr):
"""Visits a binary expression node"""
@ -225,4 +233,16 @@ class Resolver(Expression.Visitor, Statement.Visitor):
def visit_break(self, stmt):
"""Visits a break statement"""
return
if self.current_loop == LoopType.NONE:
raise JAPLError("'break' outside loop")
def visit_get(self, expr):
"""Visits a property get expression"""
self.resolve(expr.object)
def visit_set(self, expr):
"""Visits a property set expression"""
self.resolve(expr.value)
self.resolve(expr.object)

View File

@ -1,8 +1,23 @@
from ..meta.exceptions import JAPLError
from ..meta.tokenobject import Token
class JAPLInstance:
"""A class instance"""
def __init__(self, klass):
self.klass = klass
self.fields = {}
def __repr__(self):
return f"<instance of '{self.klass.name}'>"
def get(self, name: Token):
if name.lexeme in self.fields:
return self.fields[name.lexeme]
elif name.lexeme in self.klass.methods:
return self.klass.methods[name.lexeme]
raise JAPLError(name, f"Undefined property '{name.lexeme}'")
def set(self, name: Token, value: object):
self.fields[name.lexeme] = value

View File

@ -5,8 +5,9 @@ from .instance import JAPLInstance
class JAPLClass(Callable):
"""A JAPL class"""
def __init__(self, name: str):
def __init__(self, name: str, methods: dict):
self.name = name
self.methods = methods
self.arity = 0
def __repr__(self):