Began to add classes

This commit is contained in:
nocturn9x 2020-07-31 12:01:54 +02:00
parent 0c05b24c3d
commit 1d6a9f30da
7 changed files with 76 additions and 18 deletions

View File

@ -6,7 +6,7 @@ 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.statement import Statement, Print, StatementExpr, If, While, Del, Break, Return, Var, Block, Function
from .meta.statement import Statement, Print, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
class Interpreter(Expression.Visitor, Statement.Visitor):
@ -31,7 +31,6 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
self.globals.define("truthy", Truthy())
self.globals.define("stringify", Stringify())
self.looping = False
self.in_function = False
def number_operand(self, op, operand):
"""
@ -157,6 +156,10 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
elif statement.else_branch:
self.exec(statement.else_branch)
def visit_class(self, stmt: Class):
"""Visits a class declaration"""
def visit_while(self, statement: While):
"""
Visits a while node and executes it
@ -268,13 +271,10 @@ class Interpreter(Expression.Visitor, Statement.Visitor):
Visits a return statement
"""
if self.in_function:
value = None
if statement.value:
value = self.eval(statement.value)
raise ReturnException(value)
else:
raise JAPLError(statement.keyword, "'return' outside function")
value = None
if statement.value:
value = self.eval(statement.value)
raise ReturnException(value)
def visit_function(self, statement: Function):
"""

View File

@ -0,0 +1,6 @@
from enum import Enum, auto
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()

View File

@ -56,6 +56,9 @@ class Statement(object):
def visit_return(self, visitor):
raise NotImplementedError
@staticmethod
def visit_class(self, visitor):
raise NotImplementedError
@dataclass
class Print(Statement):
@ -170,3 +173,14 @@ class Return(Statement, BaseException):
def accept(self, visitor):
visitor.visit_return(self)
@dataclass
class Class(Statement):
"""A class statement"""
name: Token
methods: list
def accept(self, visitor):
visitor.visit_class(self)

View File

@ -3,8 +3,8 @@ 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, Expression, Binary, Unary, Literal, Grouping, Expression
from .meta.statement import Print, StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Statement
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression
from .meta.statement import Print, StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Class
class Parser(object):
@ -398,11 +398,24 @@ class Parser(object):
body = self.block()
return Function(name, parameters, body)
def class_declaration(self):
"""Parses a class declaration"""
name = self.consume(TokenType.ID, "Expecting class name")
self.consume(TokenType.LB, "Expecting '{' before class body")
methods = []
while not self.check(TokenType.RB) and not self.done():
methods.append(self.function("method"))
self.consume(TokenType.RB, "Expecting '}' after class body")
return Class(name, methods)
def declaration(self):
"""Parses a declaration"""
try:
if self.match(TokenType.FUN):
if self.match(TokenType.CLASS):
return self.class_declaration()
elif self.match(TokenType.FUN):
return self.function("function")
elif self.match(TokenType.VAR):
return self.var_declaration()

View File

@ -1,6 +1,7 @@
from .meta.exceptions import JAPLError
from .meta.expression import Expression
from .meta.statement import Statement
from .meta.functiontype import FunctionType
try:
from functools import singledispatchmethod
except ImportError:
@ -9,7 +10,7 @@ from typing import List, Union
from collections import deque
class Resolver(object):
class Resolver(Expression.Visitor, Statement.Visitor):
"""
This class serves the purpose of correctly resolving
name bindings (even with closures) efficiently
@ -22,6 +23,7 @@ class Resolver(object):
self.interpreter = interpreter
self.scopes = deque()
self.current_function = FunctionType.NONE
@singledispatchmethod
def resolve(self, stmt_or_expr: Union[Statement, Expression, List[Statement]]):
@ -75,6 +77,8 @@ class Resolver(object):
if not self.scopes:
return
scope = self.scopes[-1]
if name.lexeme in scope:
raise JAPLError(name, "Cannot re-declare the same variable in local scope, use assignment instead")
scope[name.lexeme] = False
def define(self, name):
@ -118,15 +122,18 @@ class Resolver(object):
self.interpreter.resolve(expr, i)
i += 1
def resolve_function(self, function):
def resolve_function(self, function, function_type: FunctionType):
"""Resolves function objects"""
enclosing = self.current_function
self.current_function = function_type
self.begin_scope()
for param in function.params:
self.declare(param)
self.define(param)
self.resolve(function.body)
self.end_scope()
self.current_function = enclosing
def visit_assign(self, expr):
"""Visits an assignment expression"""
@ -139,7 +146,11 @@ class Resolver(object):
self.declare(stmt.name)
self.define(stmt.name)
self.resolve_function(stmt)
self.resolve_function(stmt, FunctionType.FUNCTION)
def visit_class(self, stmt):
self.declare(stmt.name)
self.define(stmt.name)
def visit_statement_expr(self, stmt):
"""Visits a statement expression node"""
@ -162,6 +173,8 @@ class Resolver(object):
def visit_return(self, stmt):
"""Visits a return statement node"""
if self.current_function == FunctionType.NONE:
raise JAPLError(stmt.keyword, "'return' outside function")
if stmt.value is not None:
self.resolve(stmt.value)

8
JAPL/types/japlclass.py Normal file
View File

@ -0,0 +1,8 @@
class JAPLClass(object):
"""A JAPL class"""
def __init__(self, name: str):
self.name = name
def __repr__(self):
return self.name

View File

@ -35,7 +35,7 @@ class JAPL(object):
token, message = err.args
print(f"An exception occurred at line {token.line}, file '{file}' at '{token.lexeme}': {message}")
else:
print(f"An exception occurred, details below\n\n{err}")
print(f"An exception occurred, details below\n\n{type(err).__name__}: {err}")
def repl(self):
"""Starts an interactive REPL"""
@ -67,8 +67,12 @@ class JAPL(object):
self.resolver.resolve(ast)
result = self.interpreter.interpret(ast)
except JAPLError as error:
token, message = error.args
print(f"A runtime exception occurred at line {token.line} at '{token.lexeme}': {message}")
if len(error.args) == 2:
token, message = error.args
print(
f"An exception occurred at line {token.line}, file 'stdin' at '{token.lexeme}': {message}")
else:
print(f"An exception occurred, details below\n\n{type(error).__name__}: {error}")
else:
if result is not None:
print(repr(result))