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/
|
||||
|
||||
# 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
|
||||
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 .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):
|
||||
|
@ -14,19 +15,10 @@ class Interpreter(object):
|
|||
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}
|
||||
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"""
|
||||
|
@ -41,7 +33,7 @@ class Interpreter(object):
|
|||
self.looping = 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
|
||||
to a unary operator is a number
|
||||
|
@ -49,40 +41,41 @@ class Interpreter(object):
|
|||
|
||||
if isinstance(operand, (int, float)):
|
||||
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
|
||||
operations
|
||||
"""
|
||||
|
||||
if operator.kind == TokenType.SLASH and right == 0:
|
||||
raise JAPLError(operator, "Cannot divide by 0")
|
||||
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 operator.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__}'")
|
||||
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 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):
|
||||
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 '{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,
|
||||
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"""
|
||||
|
||||
left = self.eval(expr.left)
|
||||
|
@ -98,13 +91,13 @@ class Interpreter(object):
|
|||
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 interprerer's appropiate method to
|
||||
later call the interpreter's appropriate method to
|
||||
evaluate itself
|
||||
"""
|
||||
|
||||
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,
|
||||
recursively evaluating its subexpressions
|
||||
|
@ -112,7 +105,7 @@ class Interpreter(object):
|
|||
|
||||
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,
|
||||
returning the negation of the given object, if
|
||||
|
@ -125,7 +118,7 @@ class Interpreter(object):
|
|||
return not right
|
||||
return -right
|
||||
|
||||
def visit_binary(self, expr: Expression):
|
||||
def visit_binary(self, expr: Binary):
|
||||
"""
|
||||
Visits a Binary node in the Abstract Syntax Tree,
|
||||
recursively evaulating both operands first and then
|
||||
|
@ -137,31 +130,37 @@ class Interpreter(object):
|
|||
self.compatible_operands(expr.operator, 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
|
||||
evaluates its expression before printing it to
|
||||
stdout
|
||||
Visits the print statement node in the AST and
|
||||
evaluates its expression before printing it to
|
||||
stdout
|
||||
"""
|
||||
|
||||
val = self.eval(stmt.expression)
|
||||
print(val)
|
||||
|
||||
def visit_statement_expr(self, stmt: Statement):
|
||||
"""Visits an expression statement and evaluates it"""
|
||||
def visit_statement_expr(self, stmt: StatementExpr):
|
||||
"""
|
||||
Visits an expression statement and evaluates it
|
||||
"""
|
||||
|
||||
self.eval(stmt.expression)
|
||||
|
||||
def visit_if(self, statement):
|
||||
"""Visits an If node and evaluates it"""
|
||||
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_while(self, statement):
|
||||
"""Visits a while node and executes it"""
|
||||
def visit_while(self, statement: While):
|
||||
"""
|
||||
Visits a while node and executes it
|
||||
"""
|
||||
|
||||
self.looping = True
|
||||
while self.eval(statement.condition):
|
||||
|
@ -171,16 +170,20 @@ class Interpreter(object):
|
|||
break
|
||||
self.looping = False
|
||||
|
||||
def visit_var_stmt(self, stmt: Statement):
|
||||
"""Vitits a var statement"""
|
||||
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):
|
||||
"""Performs name lookups in the closest scope"""
|
||||
def lookup(self, name, expr: Expression):
|
||||
"""
|
||||
Performs name lookups in the closest scope
|
||||
"""
|
||||
|
||||
distance = self.locals.get(expr)
|
||||
if distance is not None:
|
||||
|
@ -189,40 +192,52 @@ class Interpreter(object):
|
|||
return self.globals.get(name)
|
||||
|
||||
def visit_var_expr(self, expr: Variable):
|
||||
"""Visits a var expression"""
|
||||
"""
|
||||
Visits a var expression
|
||||
"""
|
||||
|
||||
return self.lookup(expr.name, expr)
|
||||
|
||||
def visit_del(self, stmt: Statement):
|
||||
"""Visits a del expression"""
|
||||
def visit_del(self, stmt: Del):
|
||||
"""
|
||||
Visits a del expression
|
||||
"""
|
||||
|
||||
return self.environment.delete(stmt.name)
|
||||
|
||||
def visit_assign(self, visitor):
|
||||
"""Visits an assignment expression"""
|
||||
def visit_assign(self, stmt: Assignment):
|
||||
"""
|
||||
Visits an assignment expression
|
||||
"""
|
||||
|
||||
right = self.eval(visitor.value)
|
||||
distance = self.locals.get(visitor)
|
||||
right = self.eval(stmt.value)
|
||||
distance = self.locals.get(stmt)
|
||||
if distance is not None:
|
||||
self.environment.assign_at(distance, visitor.name, right)
|
||||
self.environment.assign_at(distance, stmt.name, right)
|
||||
else:
|
||||
self.globals.assign(visitor.name, right)
|
||||
self.globals.assign(stmt.name, right)
|
||||
return right
|
||||
|
||||
def visit_block(self, visitor):
|
||||
"""Visits a new scope block"""
|
||||
def visit_block(self, stmt: 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):
|
||||
"""Visits a break statement"""
|
||||
def visit_break(self, stmt: Break):
|
||||
"""
|
||||
Visits a break statement
|
||||
"""
|
||||
|
||||
if self.looping:
|
||||
raise BreakException()
|
||||
raise JAPLError(visitor.token, "'break' outside loop")
|
||||
raise JAPLError(stmt.token, "'break' outside loop")
|
||||
|
||||
def visit_call_expr(self, expr: Expression):
|
||||
"""Visits a call expression"""
|
||||
def visit_call_expr(self, expr: Call):
|
||||
"""
|
||||
Visits a call expression
|
||||
"""
|
||||
|
||||
callee = self.eval(expr.callee)
|
||||
if not isinstance(callee, Callable):
|
||||
|
@ -235,8 +250,10 @@ class Interpreter(object):
|
|||
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
|
||||
return function.call(self, arguments)
|
||||
|
||||
def execute_block(self, statements, scope: Environment):
|
||||
"""Executes a block of statements"""
|
||||
def execute_block(self, statements: List[Statement], scope: Environment):
|
||||
"""
|
||||
Executes a block of statements
|
||||
"""
|
||||
|
||||
prev = self.environment
|
||||
try:
|
||||
|
@ -246,8 +263,10 @@ class Interpreter(object):
|
|||
finally:
|
||||
self.environment = prev
|
||||
|
||||
def visit_return(self, statement):
|
||||
"""Visits a return statement"""
|
||||
def visit_return(self, statement: Return):
|
||||
"""
|
||||
Visits a return statement
|
||||
"""
|
||||
|
||||
if self.in_function:
|
||||
value = None
|
||||
|
@ -257,18 +276,22 @@ class Interpreter(object):
|
|||
else:
|
||||
raise JAPLError(statement.keyword, "'return' outside function")
|
||||
|
||||
def visit_function(self, statement):
|
||||
"""Visits a function"""
|
||||
def visit_function(self, statement: Function):
|
||||
"""
|
||||
Visits a function
|
||||
"""
|
||||
|
||||
function = JAPLFunction(statement, self.environment)
|
||||
self.environment.define(statement.name.lexeme, function)
|
||||
|
||||
def exec(self, statement: Statement):
|
||||
"""Executes a statement"""
|
||||
"""
|
||||
Executes a statement
|
||||
"""
|
||||
|
||||
statement.accept(self)
|
||||
|
||||
def interpret(self, statements):
|
||||
def interpret(self, statements: List[Statement]):
|
||||
"""
|
||||
Executes a JAPL program
|
||||
"""
|
||||
|
@ -276,7 +299,11 @@ class Interpreter(object):
|
|||
for statement in statements:
|
||||
self.exec(statement)
|
||||
|
||||
def resolve(self, expr, depth):
|
||||
"""Stores the result of the name resolution"""
|
||||
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!
|
||||
self.locals[expr] = depth # How many environments to skip!
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from .objects import Token, TokenType, ParseError
|
||||
from typing import List, Union
|
||||
from .meta.tokenobject import Token
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import ParseError
|
||||
from typing import List
|
||||
|
||||
|
||||
class Lexer(object):
|
||||
|
@ -10,7 +12,6 @@ class Lexer(object):
|
|||
are caught here as well.
|
||||
"""
|
||||
|
||||
|
||||
TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
|
||||
"{": TokenType.LB, "}": TokenType.RB,
|
||||
".": TokenType.DOT, ",": TokenType.COMMA,
|
||||
|
@ -30,7 +31,6 @@ class Lexer(object):
|
|||
"this": TokenType.THIS, "super": TokenType.SUPER,
|
||||
"del": TokenType.DEL, "break": TokenType.BREAK}
|
||||
|
||||
|
||||
def __init__(self, source: str):
|
||||
"""Object constructor"""
|
||||
|
||||
|
@ -182,7 +182,6 @@ class Lexer(object):
|
|||
and returns a list of tokens
|
||||
"""
|
||||
|
||||
|
||||
while not self.done():
|
||||
self.start = self.current
|
||||
self.scan_token()
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
from .tokentype import TokenType
|
||||
|
||||
|
||||
class JAPLError(BaseException):
|
||||
"""JAPL's exceptions base class"""
|
||||
|
||||
def __repr__(self):
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class ParseError(JAPLError):
|
||||
"""An error occurred while parsing"""
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.args) > 1:
|
||||
token, message = self.args
|
||||
message, token = self.args
|
||||
if token.kind == TokenType.EOF:
|
||||
return f"Unexpected error while parsing at line {token.line}, at end: {message}"
|
||||
else:
|
||||
|
|
|
@ -4,14 +4,15 @@ from .tokenobject import Token
|
|||
from typing import List
|
||||
|
||||
|
||||
class Expression(ABC):
|
||||
class Expression(object):
|
||||
"""
|
||||
An object representing a JAPL expression.
|
||||
This is an abstract base class and is not
|
||||
meant to be instantiated directly, inherit
|
||||
from it instead!
|
||||
This class is not meant to be instantiated directly,
|
||||
inherit from it instead!
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""
|
||||
|
@ -20,23 +21,24 @@ class Expression(ABC):
|
|||
"""
|
||||
|
||||
@abstractmethod
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_literal(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_binary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_grouping(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_unary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Binary(Expression, Expression.Visitor):
|
||||
class Binary(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
@ -46,7 +48,7 @@ class Binary(Expression, Expression.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Unary(Expression, Expression.Visitor):
|
||||
class Unary(Expression):
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
|
@ -55,7 +57,7 @@ class Unary(Expression, Expression.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Literal(Expression, Expression.Visitor):
|
||||
class Literal(Expression):
|
||||
value: object
|
||||
|
||||
def accept(self, visitor):
|
||||
|
@ -63,7 +65,7 @@ class Literal(Expression, Expression.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Grouping(Expression, Expression.Visitor):
|
||||
class Grouping(Expression):
|
||||
expr: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
|
@ -71,29 +73,29 @@ class Grouping(Expression, Expression.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Variable(Expression, Expression.Visitor):
|
||||
class Variable(Expression):
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_var_expr(self)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name.lexeme)
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assignment(Expression, Expression.Visitor):
|
||||
class Assignment(Expression):
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_assign(self)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name.lexeme)
|
||||
return super().__hash__()
|
||||
|
||||
@dataclass
|
||||
class Logical(Expression, Expression.Visitor):
|
||||
class Logical(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
@ -103,7 +105,7 @@ class Logical(Expression, Expression.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Call(Expression, Expression.Visitor):
|
||||
class Call(Expression):
|
||||
callee: Expression
|
||||
paren: Token
|
||||
arguments: List[Expression] = ()
|
||||
|
|
|
@ -2,25 +2,63 @@ from abc import ABC, abstractmethod
|
|||
from dataclasses import dataclass
|
||||
from .expression import Expression
|
||||
from .tokenobject import Token
|
||||
from typing import List
|
||||
from typing import List, Any
|
||||
|
||||
|
||||
class Statement(ABC):
|
||||
class Statement(object):
|
||||
"""
|
||||
An Abstract Base Class representing
|
||||
JAPL's statements
|
||||
A Base Class representing JAPL statements
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""Wrapper to implement the Visitor Pattern"""
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@dataclass
|
||||
class Print(Statement, Statement.Visitor):
|
||||
class Print(Statement):
|
||||
"""
|
||||
The print statement
|
||||
"""
|
||||
|
@ -32,7 +70,7 @@ class Print(Statement, Statement.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class StatementExpr(Statement, Statement.Visitor):
|
||||
class StatementExpr(Statement):
|
||||
"""
|
||||
An expression statement
|
||||
"""
|
||||
|
@ -44,7 +82,7 @@ class StatementExpr(Statement, Statement.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Var(Statement, Statement.Visitor):
|
||||
class Var(Statement):
|
||||
"""
|
||||
A var statement
|
||||
"""
|
||||
|
@ -57,19 +95,19 @@ class Var(Statement, Statement.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Del(Statement, Statement.Visitor):
|
||||
class Del(Statement):
|
||||
"""
|
||||
A del statement
|
||||
"""
|
||||
|
||||
name: Token
|
||||
name: Any
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_del(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block(Statement, Statement.Visitor):
|
||||
class Block(Statement):
|
||||
"""A block statement"""
|
||||
|
||||
statements: List[Statement]
|
||||
|
@ -77,8 +115,9 @@ class Block(Statement, Statement.Visitor):
|
|||
def accept(self, visitor):
|
||||
visitor.visit_block(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class If(Statement, Statement.Visitor):
|
||||
class If(Statement):
|
||||
"""An if statement"""
|
||||
|
||||
condition: Expression
|
||||
|
@ -90,7 +129,7 @@ class If(Statement, Statement.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class While(Statement, Statement.Visitor):
|
||||
class While(Statement):
|
||||
"""A while statement"""
|
||||
|
||||
condition: Expression
|
||||
|
@ -100,8 +139,9 @@ class While(Statement, Statement.Visitor):
|
|||
print("porcodio")
|
||||
visitor.visit_while(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Break(Statement, Statement.Visitor):
|
||||
class Break(Statement):
|
||||
"""A break statement"""
|
||||
|
||||
token: Token
|
||||
|
@ -109,8 +149,9 @@ class Break(Statement, Statement.Visitor):
|
|||
def accept(self, visitor):
|
||||
visitor.visit_break(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Function(Statement, Statement.Visitor):
|
||||
class Function(Statement):
|
||||
"""A function statement"""
|
||||
|
||||
name: Token
|
||||
|
@ -122,7 +163,7 @@ class Function(Statement, Statement.Visitor):
|
|||
|
||||
|
||||
@dataclass
|
||||
class Return(Statement, Statement.Visitor, BaseException):
|
||||
class Return(Statement, BaseException):
|
||||
"""A return statement"""
|
||||
|
||||
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.statement import Print, StatementExpr, Var, Del, Block, \
|
||||
If, While, Break, Function, Return
|
||||
from .meta.expression import Variable, Assignment, Logical, Call
|
||||
from typing import List
|
||||
|
||||
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
|
||||
|
||||
|
||||
class Parser(object):
|
||||
|
@ -26,7 +27,7 @@ class Parser(object):
|
|||
return True
|
||||
return False
|
||||
|
||||
def throw(self, message: str, token: Token) -> ParseError:
|
||||
def throw(self, token: Token, message: str) -> ParseError:
|
||||
"""Returns ParseError with the given message"""
|
||||
|
||||
return ParseError(message, token)
|
||||
|
@ -41,10 +42,10 @@ class Parser(object):
|
|||
break
|
||||
else:
|
||||
token_type = self.peek().kind
|
||||
if token_type in (TokenType.IF, TokenType.CLASS,
|
||||
TokenType.VAR, TokenType.FOR,
|
||||
TokenType.WHILE, TokenType.PRINT,
|
||||
TokenType.RETURN, TokenType.FUN):
|
||||
if token_type in (
|
||||
TokenType.IF, TokenType.CLASS, TokenType.VAR, TokenType.FOR, TokenType.WHILE, TokenType.PRINT,
|
||||
TokenType.RETURN, TokenType.FUN
|
||||
):
|
||||
return
|
||||
self.step()
|
||||
|
||||
|
@ -63,11 +64,13 @@ class Parser(object):
|
|||
return self.tokens[self.current - 1]
|
||||
|
||||
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
|
||||
any of the given token type(s)
|
||||
|
@ -81,16 +84,15 @@ class Parser(object):
|
|||
|
||||
def consume(self, token_type, message: str):
|
||||
"""
|
||||
Consumes a token, raises an error
|
||||
with the given message if the current token
|
||||
differs from the expected one
|
||||
Consumes a token, raises an error
|
||||
with the given message if the current token
|
||||
differs from the expected one
|
||||
"""
|
||||
|
||||
if self.check(token_type):
|
||||
return self.step()
|
||||
raise self.throw(self.peek(), message)
|
||||
|
||||
|
||||
def primary(self):
|
||||
"""Parses unary expressions (literals)"""
|
||||
|
||||
|
@ -184,9 +186,7 @@ class Parser(object):
|
|||
"""
|
||||
|
||||
expr: Expression = self.addition()
|
||||
while self.match(TokenType.GT, TokenType.GE,
|
||||
TokenType.LT, TokenType.LE,
|
||||
TokenType.NE):
|
||||
while self.match(TokenType.GT, TokenType.GE, TokenType.LT, TokenType.LE, TokenType.NE):
|
||||
operator: Token = self.previous()
|
||||
right: Expression = self.addition()
|
||||
expr = Binary(expr, operator, right)
|
||||
|
@ -371,14 +371,14 @@ class Parser(object):
|
|||
return self.expression_statement()
|
||||
|
||||
def var_declaration(self):
|
||||
"""Parses a var declaration"""
|
||||
"""Parses a var declaration"""
|
||||
|
||||
name = self.consume(TokenType.ID, "Expecting a variable name")
|
||||
init = None
|
||||
if self.match(TokenType.EQ):
|
||||
init = self.expression()
|
||||
self.consume(TokenType.SEMICOLON, "Missing semicolon after declaration")
|
||||
return Var(name, init)
|
||||
name = self.consume(TokenType.ID, "Expecting a variable name")
|
||||
init = None
|
||||
if self.match(TokenType.EQ):
|
||||
init = self.expression()
|
||||
self.consume(TokenType.SEMICOLON, "Missing semicolon after declaration")
|
||||
return Var(name, init)
|
||||
|
||||
def function(self, kind: str):
|
||||
"""Parses a function declaration"""
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
from .meta.exceptions import JAPLError
|
||||
from .meta.expression import Expression
|
||||
from .meta.statement import Statement
|
||||
from functools import singledispatchmethod
|
||||
try:
|
||||
from functools import singledispatchmethod
|
||||
except ImportError:
|
||||
from singledispatchmethod import singledispatchmethod
|
||||
from typing import List, Union
|
||||
from collections import deque
|
||||
|
||||
|
|
|
@ -1,29 +1,11 @@
|
|||
from abc import ABC, abstractmethod
|
||||
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):
|
||||
class Callable(object):
|
||||
"""A generic callable"""
|
||||
|
||||
def call(self):
|
||||
...
|
||||
def call(self, interpreter, arguments):
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self, arity):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity: int = arity
|
||||
self.arity = arity
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from ..meta.exceptions import ReturnException
|
|||
class Clock(Callable):
|
||||
"""JAPL's wrapper around time.time"""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *_):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 0
|
||||
|
@ -22,7 +22,7 @@ class Clock(Callable):
|
|||
class Type(Callable):
|
||||
"""JAPL's wrapper around type"""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *_):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 1
|
||||
|
@ -38,11 +38,11 @@ class JAPLFunction(Callable):
|
|||
"""A generic wrapper for user-defined functions"""
|
||||
|
||||
def __init__(self, declaration, closure):
|
||||
"""Object constructor"""
|
||||
"""Object constructor"""
|
||||
|
||||
self.declaration = declaration
|
||||
self.arity = len(self.declaration.params)
|
||||
self.closure = closure
|
||||
self.declaration = declaration
|
||||
self.arity = len(self.declaration.params)
|
||||
self.closure = closure
|
||||
|
||||
def call(self, interpreter, arguments):
|
||||
scope = Environment(self.closure)
|
||||
|
@ -63,7 +63,7 @@ class JAPLFunction(Callable):
|
|||
class Truthy(Callable):
|
||||
"""JAPL's wrapper around bool"""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *_):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 1
|
||||
|
@ -78,7 +78,7 @@ class Truthy(Callable):
|
|||
class Stringify(Callable):
|
||||
"""JAPL's wrapper around str()"""
|
||||
|
||||
def __init__(self, *args):
|
||||
def __init__(self, *_):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 1
|
||||
|
@ -88,4 +88,3 @@ class Stringify(Callable):
|
|||
|
||||
def __repr__(self):
|
||||
return f"<built-in function stringify>"
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from JAPL.lexer import Lexer
|
||||
from JAPL.meta.exceptions import ParseError, JAPLError
|
||||
from JAPL.resolver import Resolver
|
||||
from sys import stderr
|
||||
from JAPL.parser import Parser
|
||||
from JAPL.interpreter import Interpreter
|
||||
|
||||
|
@ -9,7 +8,6 @@ from JAPL.interpreter import Interpreter
|
|||
class JAPL(object):
|
||||
"""Wrapper around JAPL's interpreter, lexer and parser"""
|
||||
|
||||
|
||||
interpreter = Interpreter()
|
||||
resolver = Resolver(interpreter)
|
||||
|
||||
|
@ -34,7 +32,7 @@ class JAPL(object):
|
|||
print(f"Error' '{file}', permission denied")
|
||||
except JAPLError as err:
|
||||
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}")
|
||||
else:
|
||||
print(f"An exception occurred, details below\n\n{err}")
|
||||
|
@ -49,27 +47,27 @@ class JAPL(object):
|
|||
source = input(">>> ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
exit()
|
||||
if not source:
|
||||
continue
|
||||
lexer = Lexer(source)
|
||||
try:
|
||||
tokens = lexer.lex()
|
||||
except ParseError as err:
|
||||
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
|
||||
else:
|
||||
if not source:
|
||||
continue
|
||||
lexer = Lexer(source)
|
||||
try:
|
||||
ast = Parser(tokens).parse()
|
||||
tokens = lexer.lex()
|
||||
except ParseError as err:
|
||||
token, message = err.args
|
||||
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
|
||||
else:
|
||||
try:
|
||||
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}")
|
||||
ast = Parser(tokens).parse()
|
||||
except ParseError as err:
|
||||
message, token = err.args
|
||||
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
else:
|
||||
if result is not None:
|
||||
print(repr(result))
|
||||
try:
|
||||
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}")
|
||||
else:
|
||||
if result is not None:
|
||||
print(repr(result))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
singledispatchmethod
|
Loading…
Reference in New Issue