mirror of https://github.com/japl-lang/japl.git
Significant code refactoring, improved readability and standard compliance by adding more (and more specific) type hints. Revised visitor pattern
This commit is contained in:
parent
ff346d9559
commit
71a73d010b
|
@ -127,3 +127,7 @@ dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
|
||||||
|
.idea/
|
|
@ -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!
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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] = ()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>"
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
singledispatchmethod
|
Loading…
Reference in New Issue