Significant code refactoring, improved readability and standard compliance by adding more (and more specific) type hints. Revised visitor pattern

This commit is contained in:
nocturn9x 2020-07-30 15:38:33 +02:00
parent ff346d9559
commit 71a73d010b
13 changed files with 256 additions and 203 deletions

4
.gitignore vendored
View File

@ -127,3 +127,7 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# PyCharm
.idea/

View File

@ -1,11 +1,12 @@
from .meta.expression import Expression, Variable, Call
from .meta.tokentype import TokenType
from .meta.exceptions import JAPLError, BreakException, ReturnException
from .meta.statement import Statement
from .meta.environment import Environment
from .types.callable import Callable
import operator import operator
from typing import List
from .types.callable import Callable
from .meta.environment import Environment
from .meta.tokentype import TokenType
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify 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
class Interpreter(object): class Interpreter(object):
@ -14,19 +15,10 @@ class Interpreter(object):
programming language programming language
""" """
OPS = {TokenType.MINUS: operator.sub, OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
TokenType.PLUS: operator.add, TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
TokenType.SLASH: operator.truediv, TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
TokenType.STAR: operator.mul, TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
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): def __init__(self):
"""Object constructor""" """Object constructor"""
@ -41,7 +33,7 @@ class Interpreter(object):
self.looping = False self.looping = False
self.in_function = False self.in_function = False
def number_operand(self, operator, operand): def number_operand(self, op, operand):
""" """
An helper method to check if the operand An helper method to check if the operand
to a unary operator is a number to a unary operator is a number
@ -49,40 +41,41 @@ class Interpreter(object):
if isinstance(operand, (int, float)): if isinstance(operand, (int, float)):
return return
raise JAPLError(operator, f"Unsupported unary operator '{operator.lexeme}' for object of type '{type(operand).__name__}'") raise JAPLError(op,
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
def compatible_operands(self, operator, left, right): def compatible_operands(self, op, left, right):
""" """
Helper method to check types when doing binary Helper method to check types when doing binary
operations operations
""" """
if operator.kind == TokenType.SLASH and right == 0: if op.kind == TokenType.SLASH and right == 0:
raise JAPLError(operator, "Cannot divide by 0") raise JAPLError(op, "Cannot divide by 0")
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))): elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
if operator.kind not in (TokenType.DEQ, TokenType.NE): if op.kind not in (TokenType.DEQ, TokenType.NE):
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
return return
elif isinstance(left, (int, float)) and isinstance(right, (int, float)): elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
return return
elif operator.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE): elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
if isinstance(left, str) and isinstance(right, str): if isinstance(left, str) and isinstance(right, str):
return return
elif isinstance(left, str) and isinstance(right, int): elif isinstance(left, str) and isinstance(right, int):
return return
elif isinstance(left, int) and isinstance(right, str): elif isinstance(left, int) and isinstance(right, str):
return return
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
def visit_literal(self, visitor: Expression.Visitor): def visit_literal(self, expr: Literal):
""" """
Visits a Literal node in the Abstract Syntax Tree, Visits a Literal node in the Abstract Syntax Tree,
returning its value to the visitor returning its value to the visitor
""" """
return visitor.value return expr.value
def visit_logical(self, expr): def visit_logical(self, expr: Logical):
"""Visits a logical node""" """Visits a logical node"""
left = self.eval(expr.left) left = self.eval(expr.left)
@ -98,13 +91,13 @@ class Interpreter(object):
Evaluates an expression by calling its accept() Evaluates an expression by calling its accept()
method and passing self to it. This mechanism is known method and passing self to it. This mechanism is known
as the 'Visitor Pattern': the expression object will as the 'Visitor Pattern': the expression object will
later call the interprerer's appropiate method to later call the interpreter's appropriate method to
evaluate itself evaluate itself
""" """
return expr.accept(self) return expr.accept(self)
def visit_grouping(self, grouping: Expression): def visit_grouping(self, grouping: Grouping):
""" """
Visits a Grouping node in the Abstract Syntax Tree, Visits a Grouping node in the Abstract Syntax Tree,
recursively evaluating its subexpressions recursively evaluating its subexpressions
@ -112,7 +105,7 @@ class Interpreter(object):
return self.eval(grouping.expr) return self.eval(grouping.expr)
def visit_unary(self, expr: Expression): def visit_unary(self, expr: Unary):
""" """
Visits a Unary node in the Abstract Syntax Teee, Visits a Unary node in the Abstract Syntax Teee,
returning the negation of the given object, if returning the negation of the given object, if
@ -125,7 +118,7 @@ class Interpreter(object):
return not right return not right
return -right return -right
def visit_binary(self, expr: Expression): def visit_binary(self, expr: Binary):
""" """
Visits a Binary node in the Abstract Syntax Tree, Visits a Binary node in the Abstract Syntax Tree,
recursively evaulating both operands first and then recursively evaulating both operands first and then
@ -137,7 +130,7 @@ class Interpreter(object):
self.compatible_operands(expr.operator, left, right) self.compatible_operands(expr.operator, left, right)
return self.OPS[expr.operator.kind](left, right) return self.OPS[expr.operator.kind](left, right)
def visit_print(self, stmt: Statement): def visit_print(self, stmt: Print):
""" """
Visits the print statement node in the AST and Visits the print statement node in the AST and
evaluates its expression before printing it to evaluates its expression before printing it to
@ -147,21 +140,27 @@ class Interpreter(object):
val = self.eval(stmt.expression) val = self.eval(stmt.expression)
print(val) print(val)
def visit_statement_expr(self, stmt: Statement): def visit_statement_expr(self, stmt: StatementExpr):
"""Visits an expression statement and evaluates it""" """
Visits an expression statement and evaluates it
"""
self.eval(stmt.expression) self.eval(stmt.expression)
def visit_if(self, statement): def visit_if(self, statement: If):
"""Visits an If node and evaluates it""" """
Visits an If node and evaluates it
"""
if self.eval(statement.condition): if self.eval(statement.condition):
self.exec(statement.then_branch) self.exec(statement.then_branch)
elif statement.else_branch: elif statement.else_branch:
self.exec(statement.else_branch) self.exec(statement.else_branch)
def visit_while(self, statement): def visit_while(self, statement: While):
"""Visits a while node and executes it""" """
Visits a while node and executes it
"""
self.looping = True self.looping = True
while self.eval(statement.condition): while self.eval(statement.condition):
@ -171,16 +170,20 @@ class Interpreter(object):
break break
self.looping = False self.looping = False
def visit_var_stmt(self, stmt: Statement): def visit_var_stmt(self, stmt: Var):
"""Vitits a var statement""" """
Visits a var statement
"""
val = None val = None
if stmt.init: if stmt.init:
val = self.eval(stmt.init) val = self.eval(stmt.init)
self.environment.define(stmt.name.lexeme, val) self.environment.define(stmt.name.lexeme, val)
def lookup(self, name, expr): def lookup(self, name, expr: Expression):
"""Performs name lookups in the closest scope""" """
Performs name lookups in the closest scope
"""
distance = self.locals.get(expr) distance = self.locals.get(expr)
if distance is not None: if distance is not None:
@ -189,40 +192,52 @@ class Interpreter(object):
return self.globals.get(name) return self.globals.get(name)
def visit_var_expr(self, expr: Variable): def visit_var_expr(self, expr: Variable):
"""Visits a var expression""" """
Visits a var expression
"""
return self.lookup(expr.name, expr) return self.lookup(expr.name, expr)
def visit_del(self, stmt: Statement): def visit_del(self, stmt: Del):
"""Visits a del expression""" """
Visits a del expression
"""
return self.environment.delete(stmt.name) return self.environment.delete(stmt.name)
def visit_assign(self, visitor): def visit_assign(self, stmt: Assignment):
"""Visits an assignment expression""" """
Visits an assignment expression
"""
right = self.eval(visitor.value) right = self.eval(stmt.value)
distance = self.locals.get(visitor) distance = self.locals.get(stmt)
if distance is not None: if distance is not None:
self.environment.assign_at(distance, visitor.name, right) self.environment.assign_at(distance, stmt.name, right)
else: else:
self.globals.assign(visitor.name, right) self.globals.assign(stmt.name, right)
return right return right
def visit_block(self, visitor): def visit_block(self, stmt: Block):
"""Visits a new scope block""" """
Visits a new scope block
"""
return self.execute_block(visitor.statements, Environment(self.environment)) return self.execute_block(stmt.statements, Environment(self.environment))
def visit_break(self, visitor): def visit_break(self, stmt: Break):
"""Visits a break statement""" """
Visits a break statement
"""
if self.looping: if self.looping:
raise BreakException() raise BreakException()
raise JAPLError(visitor.token, "'break' outside loop") raise JAPLError(stmt.token, "'break' outside loop")
def visit_call_expr(self, expr: Expression): def visit_call_expr(self, expr: Call):
"""Visits a call expression""" """
Visits a call expression
"""
callee = self.eval(expr.callee) callee = self.eval(expr.callee)
if not isinstance(callee, Callable): if not isinstance(callee, Callable):
@ -235,8 +250,10 @@ class Interpreter(object):
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}") raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
return function.call(self, arguments) return function.call(self, arguments)
def execute_block(self, statements, scope: Environment): def execute_block(self, statements: List[Statement], scope: Environment):
"""Executes a block of statements""" """
Executes a block of statements
"""
prev = self.environment prev = self.environment
try: try:
@ -246,8 +263,10 @@ class Interpreter(object):
finally: finally:
self.environment = prev self.environment = prev
def visit_return(self, statement): def visit_return(self, statement: Return):
"""Visits a return statement""" """
Visits a return statement
"""
if self.in_function: if self.in_function:
value = None value = None
@ -257,18 +276,22 @@ class Interpreter(object):
else: else:
raise JAPLError(statement.keyword, "'return' outside function") raise JAPLError(statement.keyword, "'return' outside function")
def visit_function(self, statement): def visit_function(self, statement: Function):
"""Visits a function""" """
Visits a function
"""
function = JAPLFunction(statement, self.environment) function = JAPLFunction(statement, self.environment)
self.environment.define(statement.name.lexeme, function) self.environment.define(statement.name.lexeme, function)
def exec(self, statement: Statement): def exec(self, statement: Statement):
"""Executes a statement""" """
Executes a statement
"""
statement.accept(self) statement.accept(self)
def interpret(self, statements): def interpret(self, statements: List[Statement]):
""" """
Executes a JAPL program Executes a JAPL program
""" """
@ -276,7 +299,11 @@ class Interpreter(object):
for statement in statements: for statement in statements:
self.exec(statement) self.exec(statement)
def resolve(self, expr, depth): def resolve(self, expr: Expression, depth: int):
"""Stores the result of the name resolution""" """
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! self.locals[expr] = depth # How many environments to skip!

View File

@ -1,5 +1,7 @@
from .objects import Token, TokenType, ParseError from .meta.tokenobject import Token
from typing import List, Union from .meta.tokentype import TokenType
from .meta.exceptions import ParseError
from typing import List
class Lexer(object): class Lexer(object):
@ -10,7 +12,6 @@ class Lexer(object):
are caught here as well. are caught here as well.
""" """
TOKENS = {"(": TokenType.LP, ")": TokenType.RP, TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
"{": TokenType.LB, "}": TokenType.RB, "{": TokenType.LB, "}": TokenType.RB,
".": TokenType.DOT, ",": TokenType.COMMA, ".": TokenType.DOT, ",": TokenType.COMMA,
@ -30,7 +31,6 @@ class Lexer(object):
"this": TokenType.THIS, "super": TokenType.SUPER, "this": TokenType.THIS, "super": TokenType.SUPER,
"del": TokenType.DEL, "break": TokenType.BREAK} "del": TokenType.DEL, "break": TokenType.BREAK}
def __init__(self, source: str): def __init__(self, source: str):
"""Object constructor""" """Object constructor"""
@ -182,7 +182,6 @@ class Lexer(object):
and returns a list of tokens and returns a list of tokens
""" """
while not self.done(): while not self.done():
self.start = self.current self.start = self.current
self.scan_token() self.scan_token()

View File

@ -1,17 +1,19 @@
from .tokentype import TokenType from .tokentype import TokenType
class JAPLError(BaseException): class JAPLError(BaseException):
"""JAPL's exceptions base class""" """JAPL's exceptions base class"""
def __repr__(self): def __repr__(self):
return self.args[1] return self.args[1]
class ParseError(JAPLError): class ParseError(JAPLError):
"""An error occurred while parsing""" """An error occurred while parsing"""
def __repr__(self): def __repr__(self):
if len(self.args) > 1: if len(self.args) > 1:
token, message = self.args message, token = self.args
if token.kind == TokenType.EOF: if token.kind == TokenType.EOF:
return f"Unexpected error while parsing at line {token.line}, at end: {message}" return f"Unexpected error while parsing at line {token.line}, at end: {message}"
else: else:

View File

@ -4,14 +4,15 @@ from .tokenobject import Token
from typing import List from typing import List
class Expression(ABC): class Expression(object):
""" """
An object representing a JAPL expression. An object representing a JAPL expression.
This is an abstract base class and is not This class is not meant to be instantiated directly,
meant to be instantiated directly, inherit inherit from it instead!
from it instead!
""" """
def accept(self, visitor):
raise NotImplementedError
class Visitor(ABC): class Visitor(ABC):
""" """
@ -20,23 +21,24 @@ class Expression(ABC):
""" """
@abstractmethod @abstractmethod
def accept(self, visitor):
raise NotImplementedError
def visit_literal(self, visitor): def visit_literal(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def visit_binary(self, visitor): def visit_binary(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def visit_grouping(self, visitor): def visit_grouping(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod
def visit_unary(self, visitor): def visit_unary(self, visitor):
raise NotImplementedError raise NotImplementedError
@dataclass @dataclass
class Binary(Expression, Expression.Visitor): class Binary(Expression):
left: Expression left: Expression
operator: Token operator: Token
right: Expression right: Expression
@ -46,7 +48,7 @@ class Binary(Expression, Expression.Visitor):
@dataclass @dataclass
class Unary(Expression, Expression.Visitor): class Unary(Expression):
operator: Token operator: Token
right: Expression right: Expression
@ -55,7 +57,7 @@ class Unary(Expression, Expression.Visitor):
@dataclass @dataclass
class Literal(Expression, Expression.Visitor): class Literal(Expression):
value: object value: object
def accept(self, visitor): def accept(self, visitor):
@ -63,7 +65,7 @@ class Literal(Expression, Expression.Visitor):
@dataclass @dataclass
class Grouping(Expression, Expression.Visitor): class Grouping(Expression):
expr: Expression expr: Expression
def accept(self, visitor): def accept(self, visitor):
@ -71,29 +73,29 @@ class Grouping(Expression, Expression.Visitor):
@dataclass @dataclass
class Variable(Expression, Expression.Visitor): class Variable(Expression):
name: Token name: Token
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_var_expr(self) return visitor.visit_var_expr(self)
def __hash__(self): def __hash__(self):
return hash(self.name.lexeme) return super().__hash__()
@dataclass @dataclass
class Assignment(Expression, Expression.Visitor): class Assignment(Expression):
name: Token name: Token
value: Expression value: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_assign(self) return visitor.visit_assign(self)
def __hash__(self): def __hash__(self):
return hash(self.name.lexeme) return super().__hash__()
@dataclass @dataclass
class Logical(Expression, Expression.Visitor): class Logical(Expression):
left: Expression left: Expression
operator: Token operator: Token
right: Expression right: Expression
@ -103,7 +105,7 @@ class Logical(Expression, Expression.Visitor):
@dataclass @dataclass
class Call(Expression, Expression.Visitor): class Call(Expression):
callee: Expression callee: Expression
paren: Token paren: Token
arguments: List[Expression] = () arguments: List[Expression] = ()

View File

@ -2,25 +2,63 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from .expression import Expression from .expression import Expression
from .tokenobject import Token from .tokenobject import Token
from typing import List from typing import List, Any
class Statement(ABC): class Statement(object):
""" """
An Abstract Base Class representing A Base Class representing JAPL statements
JAPL's statements
""" """
def accept(self, visitor):
raise NotImplementedError
class Visitor(ABC): class Visitor(ABC):
"""Wrapper to implement the Visitor Pattern""" """Wrapper to implement the Visitor Pattern"""
@abstractmethod @abstractmethod
def accept(self, visitor): def visit_print(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_statement_expr(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_var_stmt(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_del(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_block(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_if(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_while(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_break(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_function(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_return(self, visitor):
raise NotImplementedError raise NotImplementedError
@dataclass @dataclass
class Print(Statement, Statement.Visitor): class Print(Statement):
""" """
The print statement The print statement
""" """
@ -32,7 +70,7 @@ class Print(Statement, Statement.Visitor):
@dataclass @dataclass
class StatementExpr(Statement, Statement.Visitor): class StatementExpr(Statement):
""" """
An expression statement An expression statement
""" """
@ -44,7 +82,7 @@ class StatementExpr(Statement, Statement.Visitor):
@dataclass @dataclass
class Var(Statement, Statement.Visitor): class Var(Statement):
""" """
A var statement A var statement
""" """
@ -57,19 +95,19 @@ class Var(Statement, Statement.Visitor):
@dataclass @dataclass
class Del(Statement, Statement.Visitor): class Del(Statement):
""" """
A del statement A del statement
""" """
name: Token name: Any
def accept(self, visitor): def accept(self, visitor):
visitor.visit_del(self) visitor.visit_del(self)
@dataclass @dataclass
class Block(Statement, Statement.Visitor): class Block(Statement):
"""A block statement""" """A block statement"""
statements: List[Statement] statements: List[Statement]
@ -77,8 +115,9 @@ class Block(Statement, Statement.Visitor):
def accept(self, visitor): def accept(self, visitor):
visitor.visit_block(self) visitor.visit_block(self)
@dataclass @dataclass
class If(Statement, Statement.Visitor): class If(Statement):
"""An if statement""" """An if statement"""
condition: Expression condition: Expression
@ -90,7 +129,7 @@ class If(Statement, Statement.Visitor):
@dataclass @dataclass
class While(Statement, Statement.Visitor): class While(Statement):
"""A while statement""" """A while statement"""
condition: Expression condition: Expression
@ -100,8 +139,9 @@ class While(Statement, Statement.Visitor):
print("porcodio") print("porcodio")
visitor.visit_while(self) visitor.visit_while(self)
@dataclass @dataclass
class Break(Statement, Statement.Visitor): class Break(Statement):
"""A break statement""" """A break statement"""
token: Token token: Token
@ -109,8 +149,9 @@ class Break(Statement, Statement.Visitor):
def accept(self, visitor): def accept(self, visitor):
visitor.visit_break(self) visitor.visit_break(self)
@dataclass @dataclass
class Function(Statement, Statement.Visitor): class Function(Statement):
"""A function statement""" """A function statement"""
name: Token name: Token
@ -122,7 +163,7 @@ class Function(Statement, Statement.Visitor):
@dataclass @dataclass
class Return(Statement, Statement.Visitor, BaseException): class Return(Statement, BaseException):
"""A return statement""" """A return statement"""
keyword: Token keyword: Token

View File

@ -1,5 +0,0 @@
from .meta.statement import Statement, Print
from .meta.expression import Expression, Unary, Binary, Grouping, Literal
from .meta.exceptions import ParseError, JAPLError
from .meta.tokentype import TokenType
from .meta.tokenobject import Token

View File

@ -1,9 +1,10 @@
from .objects import Expression, Token, TokenType, Binary, Unary, \
Literal, Grouping, ParseError, JAPLError from .meta.exceptions import ParseError
from .meta.statement import Print, StatementExpr, Var, Del, Block, \ from .meta.tokentype import TokenType
If, While, Break, Function, Return from .meta.tokenobject import Token
from .meta.expression import Variable, Assignment, Logical, Call from typing import List, Union
from typing import List 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
class Parser(object): class Parser(object):
@ -26,7 +27,7 @@ class Parser(object):
return True return True
return False return False
def throw(self, message: str, token: Token) -> ParseError: def throw(self, token: Token, message: str) -> ParseError:
"""Returns ParseError with the given message""" """Returns ParseError with the given message"""
return ParseError(message, token) return ParseError(message, token)
@ -41,10 +42,10 @@ class Parser(object):
break break
else: else:
token_type = self.peek().kind token_type = self.peek().kind
if token_type in (TokenType.IF, TokenType.CLASS, if token_type in (
TokenType.VAR, TokenType.FOR, TokenType.IF, TokenType.CLASS, TokenType.VAR, TokenType.FOR, TokenType.WHILE, TokenType.PRINT,
TokenType.WHILE, TokenType.PRINT, TokenType.RETURN, TokenType.FUN
TokenType.RETURN, TokenType.FUN): ):
return return
self.step() self.step()
@ -63,11 +64,13 @@ class Parser(object):
return self.tokens[self.current - 1] return self.tokens[self.current - 1]
def done(self): def done(self):
"""Returns True if we reached EOF""" """
Returns True if we reached EOF
"""
return self.peek().kind == TokenType.EOF return self.peek().kind == TokenType.EOF
def match(self, *types: list): def match(self, *types: Union[TokenType, List[TokenType]]):
""" """
Checks if the current token matches Checks if the current token matches
any of the given token type(s) any of the given token type(s)
@ -90,7 +93,6 @@ class Parser(object):
return self.step() return self.step()
raise self.throw(self.peek(), message) raise self.throw(self.peek(), message)
def primary(self): def primary(self):
"""Parses unary expressions (literals)""" """Parses unary expressions (literals)"""
@ -184,9 +186,7 @@ class Parser(object):
""" """
expr: Expression = self.addition() expr: Expression = self.addition()
while self.match(TokenType.GT, TokenType.GE, while self.match(TokenType.GT, TokenType.GE, TokenType.LT, TokenType.LE, TokenType.NE):
TokenType.LT, TokenType.LE,
TokenType.NE):
operator: Token = self.previous() operator: Token = self.previous()
right: Expression = self.addition() right: Expression = self.addition()
expr = Binary(expr, operator, right) expr = Binary(expr, operator, right)

View File

@ -1,7 +1,10 @@
from .meta.exceptions import JAPLError from .meta.exceptions import JAPLError
from .meta.expression import Expression from .meta.expression import Expression
from .meta.statement import Statement from .meta.statement import Statement
try:
from functools import singledispatchmethod from functools import singledispatchmethod
except ImportError:
from singledispatchmethod import singledispatchmethod
from typing import List, Union from typing import List, Union
from collections import deque from collections import deque

View File

@ -1,29 +1,11 @@
from abc import ABC, abstractmethod class Callable(object):
from dataclasses import dataclass
class CallableBase(ABC):
"""Abstract base class for callables"""
def __init__(self, arity):
"""Object constructor"""
self.arity: int = arity
@abstractmethod
def call(self, interpreter, arguments):
"""Calls the callable"""
raise NotImplementedError
class Callable(CallableBase):
"""A generic callable""" """A generic callable"""
def call(self): def call(self, interpreter, arguments):
... raise NotImplementedError
def __init__(self, arity): def __init__(self, arity):
"""Object constructor""" """Object constructor"""
self.arity: int = arity self.arity = arity

View File

@ -7,7 +7,7 @@ from ..meta.exceptions import ReturnException
class Clock(Callable): class Clock(Callable):
"""JAPL's wrapper around time.time""" """JAPL's wrapper around time.time"""
def __init__(self, *args): def __init__(self, *_):
"""Object constructor""" """Object constructor"""
self.arity = 0 self.arity = 0
@ -22,7 +22,7 @@ class Clock(Callable):
class Type(Callable): class Type(Callable):
"""JAPL's wrapper around type""" """JAPL's wrapper around type"""
def __init__(self, *args): def __init__(self, *_):
"""Object constructor""" """Object constructor"""
self.arity = 1 self.arity = 1
@ -63,7 +63,7 @@ class JAPLFunction(Callable):
class Truthy(Callable): class Truthy(Callable):
"""JAPL's wrapper around bool""" """JAPL's wrapper around bool"""
def __init__(self, *args): def __init__(self, *_):
"""Object constructor""" """Object constructor"""
self.arity = 1 self.arity = 1
@ -78,7 +78,7 @@ class Truthy(Callable):
class Stringify(Callable): class Stringify(Callable):
"""JAPL's wrapper around str()""" """JAPL's wrapper around str()"""
def __init__(self, *args): def __init__(self, *_):
"""Object constructor""" """Object constructor"""
self.arity = 1 self.arity = 1
@ -88,4 +88,3 @@ class Stringify(Callable):
def __repr__(self): def __repr__(self):
return f"<built-in function stringify>" return f"<built-in function stringify>"

View File

@ -1,7 +1,6 @@
from JAPL.lexer import Lexer from JAPL.lexer import Lexer
from JAPL.meta.exceptions import ParseError, JAPLError from JAPL.meta.exceptions import ParseError, JAPLError
from JAPL.resolver import Resolver from JAPL.resolver import Resolver
from sys import stderr
from JAPL.parser import Parser from JAPL.parser import Parser
from JAPL.interpreter import Interpreter from JAPL.interpreter import Interpreter
@ -9,7 +8,6 @@ from JAPL.interpreter import Interpreter
class JAPL(object): class JAPL(object):
"""Wrapper around JAPL's interpreter, lexer and parser""" """Wrapper around JAPL's interpreter, lexer and parser"""
interpreter = Interpreter() interpreter = Interpreter()
resolver = Resolver(interpreter) resolver = Resolver(interpreter)
@ -34,7 +32,7 @@ class JAPL(object):
print(f"Error' '{file}', permission denied") print(f"Error' '{file}', permission denied")
except JAPLError as err: except JAPLError as err:
if len(err.args) == 2: if len(err.args) == 2:
token, message = err.args message, token = err.args
print(f"An exception occurred at line {token.line}, file '{file}' at '{token.lexeme}': {message}") print(f"An exception occurred at line {token.line}, file '{file}' at '{token.lexeme}': {message}")
else: else:
print(f"An exception occurred, details below\n\n{err}") print(f"An exception occurred, details below\n\n{err}")
@ -49,7 +47,7 @@ class JAPL(object):
source = input(">>> ") source = input(">>> ")
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
print() print()
exit() else:
if not source: if not source:
continue continue
lexer = Lexer(source) lexer = Lexer(source)
@ -61,7 +59,7 @@ class JAPL(object):
try: try:
ast = Parser(tokens).parse() ast = Parser(tokens).parse()
except ParseError as err: except ParseError as err:
token, message = err.args message, token = err.args
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}") print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
else: else:
try: try:

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
singledispatchmethod