added missing files
This commit is contained in:
parent
1daae9c30a
commit
ccc78148d8
|
@ -0,0 +1,4 @@
|
||||||
|
from .lexer import Lexer
|
||||||
|
from .parser import Parser
|
||||||
|
from .interpreter import Interpreter
|
||||||
|
|
|
@ -1,352 +1,352 @@
|
||||||
import operator
|
import operator
|
||||||
from typing import List
|
from typing import List
|
||||||
from .types.callable import Callable
|
from .types.callable import Callable
|
||||||
from .types.japlclass import JAPLClass
|
from .types.japlclass import JAPLClass
|
||||||
from .types.instance import JAPLInstance
|
from .types.instance import JAPLInstance
|
||||||
from .meta.environment import Environment
|
from .meta.environment import Environment
|
||||||
from .meta.tokentype import TokenType
|
from .meta.tokentype import TokenType
|
||||||
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
||||||
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass
|
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass
|
||||||
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set
|
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set
|
||||||
from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
|
from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
|
||||||
|
|
||||||
|
|
||||||
class Interpreter(Expression.Visitor, Statement.Visitor):
|
class Interpreter(Expression.Visitor, Statement.Visitor):
|
||||||
"""
|
"""
|
||||||
An interpreter for the JAPL
|
An interpreter for the JAPL
|
||||||
programming language
|
programming language
|
||||||
"""
|
"""
|
||||||
|
|
||||||
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
|
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.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.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}
|
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Object constructor"""
|
"""Object constructor"""
|
||||||
|
|
||||||
self.environment = Environment()
|
self.environment = Environment()
|
||||||
self.locals = {}
|
self.locals = {}
|
||||||
self.globals = self.environment
|
self.globals = self.environment
|
||||||
self.globals.define("clock", Clock())
|
self.globals.define("clock", Clock())
|
||||||
self.globals.define("type", Type())
|
self.globals.define("type", Type())
|
||||||
self.globals.define("truthy", Truthy())
|
self.globals.define("truthy", Truthy())
|
||||||
self.globals.define("stringify", Stringify())
|
self.globals.define("stringify", Stringify())
|
||||||
self.globals.define("print", PrintFunction())
|
self.globals.define("print", PrintFunction())
|
||||||
self.globals.define("isinstance", IsInstance())
|
self.globals.define("isinstance", IsInstance())
|
||||||
self.globals.define("issuperclass", IsSuperclass())
|
self.globals.define("issuperclass", IsSuperclass())
|
||||||
self.globals.define("issubclass", IsSubclass())
|
self.globals.define("issubclass", IsSubclass())
|
||||||
|
|
||||||
def number_operand(self, op, 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(operand, (int, float)):
|
if isinstance(operand, (int, float)):
|
||||||
return
|
return
|
||||||
raise JAPLError(op,
|
raise JAPLError(op,
|
||||||
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
|
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
|
||||||
|
|
||||||
def compatible_operands(self, op, 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 op.kind == TokenType.SLASH and right == 0:
|
if op.kind == TokenType.SLASH and right == 0:
|
||||||
raise JAPLError(op, "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 op.kind not in (TokenType.DEQ, TokenType.NE):
|
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__}'")
|
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 op.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 '{op.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, expr: Literal):
|
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 expr.value
|
return expr.value
|
||||||
|
|
||||||
def visit_logical(self, expr: Logical):
|
def visit_logical(self, expr: Logical):
|
||||||
"""Visits a logical node"""
|
"""Visits a logical node"""
|
||||||
|
|
||||||
left = self.eval(expr.left)
|
left = self.eval(expr.left)
|
||||||
if expr.operator.kind == TokenType.OR:
|
if expr.operator.kind == TokenType.OR:
|
||||||
if bool(left):
|
if bool(left):
|
||||||
return left
|
return left
|
||||||
elif not bool(left):
|
elif not bool(left):
|
||||||
return self.eval(expr.right)
|
return self.eval(expr.right)
|
||||||
return self.eval(expr.right)
|
return self.eval(expr.right)
|
||||||
|
|
||||||
def eval(self, expr: Expression):
|
def eval(self, expr: Expression):
|
||||||
"""
|
"""
|
||||||
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 interpreter's appropriate 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: Grouping):
|
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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.eval(grouping.expr)
|
return self.eval(grouping.expr)
|
||||||
|
|
||||||
def visit_unary(self, expr: Unary):
|
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
|
||||||
the operation is supported
|
the operation is supported
|
||||||
"""
|
"""
|
||||||
|
|
||||||
right = self.eval(expr.right)
|
right = self.eval(expr.right)
|
||||||
self.number_operand(expr.operator, right)
|
self.number_operand(expr.operator, right)
|
||||||
if expr.operator.kind == TokenType.NEG:
|
if expr.operator.kind == TokenType.NEG:
|
||||||
return not right
|
return not right
|
||||||
return -right
|
return -right
|
||||||
|
|
||||||
def visit_binary(self, expr: Binary):
|
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
|
||||||
performing the operation specified by the operator
|
performing the operation specified by the operator
|
||||||
"""
|
"""
|
||||||
|
|
||||||
left = self.eval(expr.left)
|
left = self.eval(expr.left)
|
||||||
right = self.eval(expr.right)
|
right = self.eval(expr.right)
|
||||||
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_statement_expr(self, stmt: StatementExpr):
|
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: If):
|
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_class(self, stmt: Class):
|
def visit_class(self, stmt: Class):
|
||||||
"""Visits a class declaration"""
|
"""Visits a class declaration"""
|
||||||
|
|
||||||
superclass = None
|
superclass = None
|
||||||
if stmt.superclass:
|
if stmt.superclass:
|
||||||
superclass = self.eval(stmt.superclass)
|
superclass = self.eval(stmt.superclass)
|
||||||
if not isinstance(superclass, JAPLClass):
|
if not isinstance(superclass, JAPLClass):
|
||||||
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
|
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
|
||||||
self.environment.define(stmt.name.lexeme, None)
|
self.environment.define(stmt.name.lexeme, None)
|
||||||
if superclass:
|
if superclass:
|
||||||
environment = Environment(self.environment)
|
environment = Environment(self.environment)
|
||||||
environment.define("super", superclass)
|
environment.define("super", superclass)
|
||||||
else:
|
else:
|
||||||
environment = self.environment
|
environment = self.environment
|
||||||
methods = {}
|
methods = {}
|
||||||
for method in stmt.methods:
|
for method in stmt.methods:
|
||||||
func = JAPLFunction(method, environment)
|
func = JAPLFunction(method, environment)
|
||||||
methods[method.name.lexeme] = func
|
methods[method.name.lexeme] = func
|
||||||
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
|
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
|
||||||
if superclass:
|
if superclass:
|
||||||
self.environment = environment.enclosing
|
self.environment = environment.enclosing
|
||||||
self.environment.assign(stmt.name, klass)
|
self.environment.assign(stmt.name, klass)
|
||||||
|
|
||||||
def visit_while(self, statement: While):
|
def visit_while(self, statement: While):
|
||||||
"""
|
"""
|
||||||
Visits a while node and executes it
|
Visits a while node and executes it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
while self.eval(statement.condition):
|
while self.eval(statement.condition):
|
||||||
try:
|
try:
|
||||||
self.exec(statement.body)
|
self.exec(statement.body)
|
||||||
except BreakException:
|
except BreakException:
|
||||||
break
|
break
|
||||||
|
|
||||||
def visit_var_stmt(self, stmt: Var):
|
def visit_var_stmt(self, stmt: Var):
|
||||||
"""
|
"""
|
||||||
Visits 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: Expression):
|
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:
|
||||||
return self.environment.get_at(distance, name.lexeme)
|
return self.environment.get_at(distance, name.lexeme)
|
||||||
else:
|
else:
|
||||||
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: Del):
|
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, stmt: Assignment):
|
def visit_assign(self, stmt: Assignment):
|
||||||
"""
|
"""
|
||||||
Visits an assignment expression
|
Visits an assignment expression
|
||||||
"""
|
"""
|
||||||
|
|
||||||
right = self.eval(stmt.value)
|
right = self.eval(stmt.value)
|
||||||
distance = self.locals.get(stmt)
|
distance = self.locals.get(stmt)
|
||||||
if distance is not None:
|
if distance is not None:
|
||||||
self.environment.assign_at(distance, stmt.name, right)
|
self.environment.assign_at(distance, stmt.name, right)
|
||||||
else:
|
else:
|
||||||
self.globals.assign(stmt.name, right)
|
self.globals.assign(stmt.name, right)
|
||||||
return right
|
return right
|
||||||
|
|
||||||
def visit_block(self, stmt: Block):
|
def visit_block(self, stmt: Block):
|
||||||
"""
|
"""
|
||||||
Visits a new scope block
|
Visits a new scope block
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.execute_block(stmt.statements, Environment(self.environment))
|
return self.execute_block(stmt.statements, Environment(self.environment))
|
||||||
|
|
||||||
def visit_break(self, stmt: Break):
|
def visit_break(self, stmt: Break):
|
||||||
"""
|
"""
|
||||||
Visits a break statement
|
Visits a break statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
raise BreakException()
|
raise BreakException()
|
||||||
|
|
||||||
def visit_call_expr(self, expr: Call):
|
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):
|
||||||
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
|
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
|
||||||
arguments = []
|
arguments = []
|
||||||
for argument in expr.arguments:
|
for argument in expr.arguments:
|
||||||
arguments.append(self.eval(argument))
|
arguments.append(self.eval(argument))
|
||||||
function = callee
|
function = callee
|
||||||
if function.arity != len(arguments):
|
if function.arity != len(arguments):
|
||||||
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: List[Statement], 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:
|
||||||
self.environment = scope
|
self.environment = scope
|
||||||
for statement in statements:
|
for statement in statements:
|
||||||
self.exec(statement)
|
self.exec(statement)
|
||||||
finally:
|
finally:
|
||||||
self.environment = prev
|
self.environment = prev
|
||||||
|
|
||||||
def visit_return(self, statement: Return):
|
def visit_return(self, statement: Return):
|
||||||
"""
|
"""
|
||||||
Visits a return statement
|
Visits a return statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value = None
|
value = None
|
||||||
if statement.value:
|
if statement.value:
|
||||||
value = self.eval(statement.value)
|
value = self.eval(statement.value)
|
||||||
raise ReturnException(value)
|
raise ReturnException(value)
|
||||||
|
|
||||||
def visit_function(self, statement: Function):
|
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 visit_get(self, expr: Get):
|
def visit_get(self, expr: Get):
|
||||||
"""Visits property get expressions and evaluates them"""
|
"""Visits property get expressions and evaluates them"""
|
||||||
|
|
||||||
obj = self.eval(expr.object)
|
obj = self.eval(expr.object)
|
||||||
if isinstance(obj, JAPLInstance):
|
if isinstance(obj, JAPLInstance):
|
||||||
return obj.get(expr.name)
|
return obj.get(expr.name)
|
||||||
raise JAPLError(expr.name, "Only instances have properties")
|
raise JAPLError(expr.name, "Only instances have properties")
|
||||||
|
|
||||||
def visit_set(self, expr: Set):
|
def visit_set(self, expr: Set):
|
||||||
"""Visits property set expressions and evaluates them"""
|
"""Visits property set expressions and evaluates them"""
|
||||||
|
|
||||||
obj = self.eval(expr.object)
|
obj = self.eval(expr.object)
|
||||||
if not isinstance(obj, JAPLInstance):
|
if not isinstance(obj, JAPLInstance):
|
||||||
raise JAPLError(expr, "Only instances have fields")
|
raise JAPLError(expr, "Only instances have fields")
|
||||||
value = self.eval(expr.value)
|
value = self.eval(expr.value)
|
||||||
obj.set(expr.name, value)
|
obj.set(expr.name, value)
|
||||||
|
|
||||||
def visit_this(self, expr):
|
def visit_this(self, expr):
|
||||||
"""Evaluates 'this' expressions"""
|
"""Evaluates 'this' expressions"""
|
||||||
|
|
||||||
return self.lookup(expr.keyword, expr)
|
return self.lookup(expr.keyword, expr)
|
||||||
|
|
||||||
def visit_super(self, expr):
|
def visit_super(self, expr):
|
||||||
"""Evaluates 'super' expressions"""
|
"""Evaluates 'super' expressions"""
|
||||||
|
|
||||||
distance = self.locals.get(expr)
|
distance = self.locals.get(expr)
|
||||||
superclass = self.environment.get_at(distance, "super")
|
superclass = self.environment.get_at(distance, "super")
|
||||||
instance = self.environment.get_at(distance - 1, "this")
|
instance = self.environment.get_at(distance - 1, "this")
|
||||||
meth = superclass.get_method(expr.method.lexeme)
|
meth = superclass.get_method(expr.method.lexeme)
|
||||||
if not meth:
|
if not meth:
|
||||||
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
|
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
|
||||||
return meth.bind(instance)
|
return meth.bind(instance)
|
||||||
|
|
||||||
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: List[Statement]):
|
def interpret(self, statements: List[Statement]):
|
||||||
"""
|
"""
|
||||||
Executes a JAPL program
|
Executes a JAPL program
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for statement in statements:
|
for statement in statements:
|
||||||
self.exec(statement)
|
self.exec(statement)
|
||||||
|
|
||||||
def resolve(self, expr: Expression, depth: int):
|
def resolve(self, expr: Expression, depth: int):
|
||||||
"""
|
"""
|
||||||
Stores the result of the name resolution: this
|
Stores the result of the name resolution: this
|
||||||
info will be used later to know exactly in which
|
info will be used later to know exactly in which
|
||||||
environment to look up a given variable
|
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!
|
||||||
|
|
424
JAPL/lexer.py
424
JAPL/lexer.py
|
@ -1,212 +1,212 @@
|
||||||
from .meta.tokenobject import Token
|
from .meta.tokenobject import Token
|
||||||
from .meta.tokentype import TokenType
|
from .meta.tokentype import TokenType
|
||||||
from .meta.exceptions import ParseError
|
from .meta.exceptions import ParseError
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class Lexer(object):
|
class Lexer(object):
|
||||||
"""
|
"""
|
||||||
A simple tokenizer for the JAPL programming
|
A simple tokenizer for the JAPL programming
|
||||||
language, scans a input source file and
|
language, scans a input source file and
|
||||||
produces a list of tokens. Some errors
|
produces a list of tokens. Some errors
|
||||||
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,
|
||||||
"-": TokenType.MINUS, "+": TokenType.PLUS,
|
"-": TokenType.MINUS, "+": TokenType.PLUS,
|
||||||
";": TokenType.SEMICOLON, "*": TokenType.STAR,
|
";": TokenType.SEMICOLON, "*": TokenType.STAR,
|
||||||
">": TokenType.GT, "<": TokenType.LT,
|
">": TokenType.GT, "<": TokenType.LT,
|
||||||
"=": TokenType.EQ, "!": TokenType.NEG,
|
"=": TokenType.EQ, "!": TokenType.NEG,
|
||||||
"/": TokenType.SLASH, "%": TokenType.MOD}
|
"/": TokenType.SLASH, "%": TokenType.MOD}
|
||||||
|
|
||||||
RESERVED = {"or": TokenType.OR, "and": TokenType.AND,
|
RESERVED = {"or": TokenType.OR, "and": TokenType.AND,
|
||||||
"class": TokenType.CLASS, "fun": TokenType.FUN,
|
"class": TokenType.CLASS, "fun": TokenType.FUN,
|
||||||
"if": TokenType.IF, "else": TokenType.ELSE,
|
"if": TokenType.IF, "else": TokenType.ELSE,
|
||||||
"for": TokenType.FOR, "while": TokenType.WHILE,
|
"for": TokenType.FOR, "while": TokenType.WHILE,
|
||||||
"var": TokenType.VAR, "nil": TokenType.NIL,
|
"var": TokenType.VAR, "nil": TokenType.NIL,
|
||||||
"true": TokenType.TRUE, "false": TokenType.FALSE,
|
"true": TokenType.TRUE, "false": TokenType.FALSE,
|
||||||
"return": TokenType.RETURN,
|
"return": TokenType.RETURN,
|
||||||
"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"""
|
||||||
|
|
||||||
self.source = source
|
self.source = source
|
||||||
self.tokens: List[Token] = []
|
self.tokens: List[Token] = []
|
||||||
self.line: int = 1 # Points to the line being lexed
|
self.line: int = 1 # Points to the line being lexed
|
||||||
self.start: int = 0 # The position of the first character of the current lexeme
|
self.start: int = 0 # The position of the first character of the current lexeme
|
||||||
self.current: int = 0 # The position of the current character being lexed
|
self.current: int = 0 # The position of the current character being lexed
|
||||||
|
|
||||||
def step(self) -> str:
|
def step(self) -> str:
|
||||||
"""
|
"""
|
||||||
'Steps' one character in the source code and returns it
|
'Steps' one character in the source code and returns it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.done():
|
if self.done():
|
||||||
return ""
|
return ""
|
||||||
self.current += 1
|
self.current += 1
|
||||||
return self.source[self.current - 1]
|
return self.source[self.current - 1]
|
||||||
|
|
||||||
def peek(self) -> str:
|
def peek(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the current character without consuming it
|
Returns the current character without consuming it
|
||||||
or an empty string if all text has been consumed
|
or an empty string if all text has been consumed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.done():
|
if self.done():
|
||||||
return ""
|
return ""
|
||||||
return self.source[self.current]
|
return self.source[self.current]
|
||||||
|
|
||||||
def peek_next(self) -> str:
|
def peek_next(self) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the next character after self.current
|
Returns the next character after self.current
|
||||||
or an empty string if the input has been consumed
|
or an empty string if the input has been consumed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.current + 1 >= len(self.source):
|
if self.current + 1 >= len(self.source):
|
||||||
return ""
|
return ""
|
||||||
return self.source[self.current + 1]
|
return self.source[self.current + 1]
|
||||||
|
|
||||||
def string(self, delimiter: str):
|
def string(self, delimiter: str):
|
||||||
"""Parses a string literal"""
|
"""Parses a string literal"""
|
||||||
|
|
||||||
while self.peek() != delimiter and not self.done():
|
while self.peek() != delimiter and not self.done():
|
||||||
if self.peek() == "\n":
|
if self.peek() == "\n":
|
||||||
self.line += 1
|
self.line += 1
|
||||||
self.step()
|
self.step()
|
||||||
if self.done():
|
if self.done():
|
||||||
raise ParseError(f"unterminated string literal at line {self.line}")
|
raise ParseError(f"unterminated string literal at line {self.line}")
|
||||||
self.step() # Consume the '"'
|
self.step() # Consume the '"'
|
||||||
value = self.source[self.start + 1:self.current - 1] # Get the actual string
|
value = self.source[self.start + 1:self.current - 1] # Get the actual string
|
||||||
self.tokens.append(self.create_token(TokenType.STR, value))
|
self.tokens.append(self.create_token(TokenType.STR, value))
|
||||||
|
|
||||||
def number(self):
|
def number(self):
|
||||||
"""Parses a number literal"""
|
"""Parses a number literal"""
|
||||||
|
|
||||||
convert = int
|
convert = int
|
||||||
while self.peek().isdigit():
|
while self.peek().isdigit():
|
||||||
self.step()
|
self.step()
|
||||||
if self.peek() == ".":
|
if self.peek() == ".":
|
||||||
self.step() # Consume the "."
|
self.step() # Consume the "."
|
||||||
while self.peek().isdigit():
|
while self.peek().isdigit():
|
||||||
self.step()
|
self.step()
|
||||||
convert = float
|
convert = float
|
||||||
self.tokens.append(self.create_token(TokenType.NUM,
|
self.tokens.append(self.create_token(TokenType.NUM,
|
||||||
convert(self.source[self.start:self.current])))
|
convert(self.source[self.start:self.current])))
|
||||||
|
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
"""Parses identifiers and reserved keywords"""
|
"""Parses identifiers and reserved keywords"""
|
||||||
|
|
||||||
while self.peek().isalnum() or self.is_identifier(self.peek()):
|
while self.peek().isalnum() or self.is_identifier(self.peek()):
|
||||||
self.step()
|
self.step()
|
||||||
kind = TokenType.ID
|
kind = TokenType.ID
|
||||||
value = self.source[self.start:self.current]
|
value = self.source[self.start:self.current]
|
||||||
if self.RESERVED.get(value, None):
|
if self.RESERVED.get(value, None):
|
||||||
kind = self.RESERVED[value]
|
kind = self.RESERVED[value]
|
||||||
self.tokens.append(self.create_token(kind))
|
self.tokens.append(self.create_token(kind))
|
||||||
|
|
||||||
def comment(self):
|
def comment(self):
|
||||||
"""Handles multi-line comments"""
|
"""Handles multi-line comments"""
|
||||||
|
|
||||||
closed = False
|
closed = False
|
||||||
while not self.done():
|
while not self.done():
|
||||||
end = self.peek() + self.peek_next()
|
end = self.peek() + self.peek_next()
|
||||||
if end == "/*": # Nested comments
|
if end == "/*": # Nested comments
|
||||||
self.step()
|
self.step()
|
||||||
self.step()
|
self.step()
|
||||||
self.comment()
|
self.comment()
|
||||||
elif end == "*/":
|
elif end == "*/":
|
||||||
closed = True
|
closed = True
|
||||||
self.step() # Consume the two ends
|
self.step() # Consume the two ends
|
||||||
self.step()
|
self.step()
|
||||||
break
|
break
|
||||||
self.step()
|
self.step()
|
||||||
if self.done() and not closed:
|
if self.done() and not closed:
|
||||||
raise ParseError(f"Unexpected EOF at line {self.line}")
|
raise ParseError(f"Unexpected EOF at line {self.line}")
|
||||||
|
|
||||||
def match(self, char: str) -> bool:
|
def match(self, char: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if the current character in self.source matches
|
Returns True if the current character in self.source matches
|
||||||
the given character
|
the given character
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.done():
|
if self.done():
|
||||||
return False
|
return False
|
||||||
elif self.source[self.current] != char:
|
elif self.source[self.current] != char:
|
||||||
return False
|
return False
|
||||||
self.current += 1
|
self.current += 1
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def done(self) -> bool:
|
def done(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Helper method that's used by the lexer
|
Helper method that's used by the lexer
|
||||||
to know if all source has been consumed
|
to know if all source has been consumed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.current >= len(self.source)
|
return self.current >= len(self.source)
|
||||||
|
|
||||||
def create_token(self, kind: TokenType, literal: object = None) -> Token:
|
def create_token(self, kind: TokenType, literal: object = None) -> Token:
|
||||||
"""
|
"""
|
||||||
Creates and returns a token object
|
Creates and returns a token object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return Token(kind, self.source[self.start:self.current], literal, self.line)
|
return Token(kind, self.source[self.start:self.current], literal, self.line)
|
||||||
|
|
||||||
def is_identifier(self, char: str):
|
def is_identifier(self, char: str):
|
||||||
"""Returns if a character can be an identifier"""
|
"""Returns if a character can be an identifier"""
|
||||||
|
|
||||||
if char.isalpha() or char in ("_", ): # More coming soon
|
if char.isalpha() or char in ("_", ): # More coming soon
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def scan_token(self):
|
def scan_token(self):
|
||||||
"""
|
"""
|
||||||
Scans for a single token and adds it to
|
Scans for a single token and adds it to
|
||||||
self.tokens
|
self.tokens
|
||||||
"""
|
"""
|
||||||
|
|
||||||
char = self.step()
|
char = self.step()
|
||||||
if char in (" ", "\t", "\r"): # Useless characters
|
if char in (" ", "\t", "\r"): # Useless characters
|
||||||
return
|
return
|
||||||
elif char == "\n": # New line
|
elif char == "\n": # New line
|
||||||
self.line += 1
|
self.line += 1
|
||||||
elif char in ("'", '"'): # A string literal
|
elif char in ("'", '"'): # A string literal
|
||||||
self.string(char)
|
self.string(char)
|
||||||
elif char.isdigit():
|
elif char.isdigit():
|
||||||
self.number()
|
self.number()
|
||||||
elif self.is_identifier(char): # Identifier or reserved keyword
|
elif self.is_identifier(char): # Identifier or reserved keyword
|
||||||
self.identifier()
|
self.identifier()
|
||||||
elif char in self.TOKENS:
|
elif char in self.TOKENS:
|
||||||
if char == "/" and self.match("/"):
|
if char == "/" and self.match("/"):
|
||||||
while self.peek() != "\n" and not self.done():
|
while self.peek() != "\n" and not self.done():
|
||||||
self.step() # Who cares about comments?
|
self.step() # Who cares about comments?
|
||||||
elif char == "/" and self.match("*"):
|
elif char == "/" and self.match("*"):
|
||||||
self.comment()
|
self.comment()
|
||||||
elif char == "=" and self.match("="):
|
elif char == "=" and self.match("="):
|
||||||
self.tokens.append(self.create_token(TokenType.DEQ))
|
self.tokens.append(self.create_token(TokenType.DEQ))
|
||||||
elif char == ">" and self.match("="):
|
elif char == ">" and self.match("="):
|
||||||
self.tokens.append(self.create_token(TokenType.GE))
|
self.tokens.append(self.create_token(TokenType.GE))
|
||||||
elif char == "<" and self.match("="):
|
elif char == "<" and self.match("="):
|
||||||
self.tokens.append(self.create_token(TokenType.LE))
|
self.tokens.append(self.create_token(TokenType.LE))
|
||||||
elif char == "!" and self.match("="):
|
elif char == "!" and self.match("="):
|
||||||
self.tokens.append(self.create_token(TokenType.NE))
|
self.tokens.append(self.create_token(TokenType.NE))
|
||||||
elif char == "*" and self.match("*"):
|
elif char == "*" and self.match("*"):
|
||||||
self.tokens.append(self.create_token(TokenType.POW))
|
self.tokens.append(self.create_token(TokenType.POW))
|
||||||
else:
|
else:
|
||||||
self.tokens.append(self.create_token(self.TOKENS[char]))
|
self.tokens.append(self.create_token(self.TOKENS[char]))
|
||||||
else:
|
else:
|
||||||
raise ParseError(f"unexpected character '{char}' at line {self.line}")
|
raise ParseError(f"unexpected character '{char}' at line {self.line}")
|
||||||
|
|
||||||
def lex(self) -> List[Token]:
|
def lex(self) -> List[Token]:
|
||||||
"""
|
"""
|
||||||
Performs lexical analysis on self.source
|
Performs lexical analysis on self.source
|
||||||
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()
|
||||||
self.tokens.append(Token(TokenType.EOF, "", None, self.line))
|
self.tokens.append(Token(TokenType.EOF, "", None, self.line))
|
||||||
return self.tokens
|
return self.tokens
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
class ClassType(Enum):
|
class ClassType(Enum):
|
||||||
|
|
||||||
NONE = auto()
|
NONE = auto()
|
||||||
CLASS = auto()
|
CLASS = auto()
|
||||||
|
|
|
@ -1,71 +1,71 @@
|
||||||
from .exceptions import JAPLError
|
from .exceptions import JAPLError
|
||||||
from .tokenobject import Token
|
from .tokenobject import Token
|
||||||
from .expression import Variable
|
from .expression import Variable
|
||||||
|
|
||||||
|
|
||||||
class Environment(object):
|
class Environment(object):
|
||||||
"""
|
"""
|
||||||
A wrapper around a hashmap representing
|
A wrapper around a hashmap representing
|
||||||
a scope
|
a scope
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, enclosing=None):
|
def __init__(self, enclosing=None):
|
||||||
"""Object constructor"""
|
"""Object constructor"""
|
||||||
|
|
||||||
self.map = {}
|
self.map = {}
|
||||||
self.enclosing = enclosing
|
self.enclosing = enclosing
|
||||||
|
|
||||||
def define(self, name: str, attr: object):
|
def define(self, name: str, attr: object):
|
||||||
"""Defines a new variable in the scope"""
|
"""Defines a new variable in the scope"""
|
||||||
|
|
||||||
self.map[name] = attr
|
self.map[name] = attr
|
||||||
|
|
||||||
def get(self, name: Token):
|
def get(self, name: Token):
|
||||||
"""Gets a variable"""
|
"""Gets a variable"""
|
||||||
|
|
||||||
if name.lexeme in self.map:
|
if name.lexeme in self.map:
|
||||||
return self.map[name.lexeme]
|
return self.map[name.lexeme]
|
||||||
elif self.enclosing:
|
elif self.enclosing:
|
||||||
return self.enclosing.get(name)
|
return self.enclosing.get(name)
|
||||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||||
|
|
||||||
def get_at(self, distance, name):
|
def get_at(self, distance, name):
|
||||||
"""Gets a variable in a specific scope"""
|
"""Gets a variable in a specific scope"""
|
||||||
|
|
||||||
return self.ancestor(distance).map.get(name)
|
return self.ancestor(distance).map.get(name)
|
||||||
|
|
||||||
def ancestor(self, distance):
|
def ancestor(self, distance):
|
||||||
"""Finds the scope specified by distance"""
|
"""Finds the scope specified by distance"""
|
||||||
|
|
||||||
env = self
|
env = self
|
||||||
for _ in range(distance):
|
for _ in range(distance):
|
||||||
env = env.enclosing
|
env = env.enclosing
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def assign_at(self, distance, name, value):
|
def assign_at(self, distance, name, value):
|
||||||
"""Same as get_at, but assigns instead of retrieving"""
|
"""Same as get_at, but assigns instead of retrieving"""
|
||||||
|
|
||||||
self.ancestor(distance).map[name.lexeme] = value
|
self.ancestor(distance).map[name.lexeme] = value
|
||||||
|
|
||||||
def delete(self, var):
|
def delete(self, var):
|
||||||
"""Deletes a variable"""
|
"""Deletes a variable"""
|
||||||
|
|
||||||
if var.name.lexeme in self.map:
|
if var.name.lexeme in self.map:
|
||||||
del self.map[var.name.lexeme]
|
del self.map[var.name.lexeme]
|
||||||
elif self.enclosing:
|
elif self.enclosing:
|
||||||
self.enclosing.delete(var)
|
self.enclosing.delete(var)
|
||||||
else:
|
else:
|
||||||
raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'")
|
raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'")
|
||||||
|
|
||||||
def assign(self, name: Token, value: object):
|
def assign(self, name: Token, value: object):
|
||||||
"""Assigns a variable"""
|
"""Assigns a variable"""
|
||||||
|
|
||||||
if name.lexeme in self.map:
|
if name.lexeme in self.map:
|
||||||
if isinstance(value, Variable):
|
if isinstance(value, Variable):
|
||||||
self.map[name.lexeme] = self.get(value.name)
|
self.map[name.lexeme] = self.get(value.name)
|
||||||
else:
|
else:
|
||||||
self.map[name.lexeme] = value
|
self.map[name.lexeme] = value
|
||||||
elif self.enclosing:
|
elif self.enclosing:
|
||||||
self.enclosing.assign(name, value)
|
self.enclosing.assign(name, value)
|
||||||
else:
|
else:
|
||||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||||
|
|
|
@ -1,32 +1,32 @@
|
||||||
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:
|
||||||
message, token = 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:
|
||||||
return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}"
|
return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}"
|
||||||
return self.args[0]
|
return self.args[0]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
class BreakException(JAPLError):
|
class BreakException(JAPLError):
|
||||||
"""Notifies a loop that it's time to break"""
|
"""Notifies a loop that it's time to break"""
|
||||||
|
|
||||||
|
|
||||||
class ReturnException(JAPLError):
|
class ReturnException(JAPLError):
|
||||||
"""Notifies a function that it's time to return"""
|
"""Notifies a function that it's time to return"""
|
||||||
|
|
|
@ -1,167 +1,167 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from .tokenobject import Token
|
from .tokenobject import Token
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class Expression(object):
|
class Expression(object):
|
||||||
"""
|
"""
|
||||||
An object representing a JAPL expression.
|
An object representing a JAPL expression.
|
||||||
This class is not meant to be instantiated directly,
|
This class is not meant to be instantiated directly,
|
||||||
inherit from it instead!
|
inherit from it instead!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
class Visitor(ABC):
|
class Visitor(ABC):
|
||||||
"""
|
"""
|
||||||
Visitor abstract base class to implement
|
Visitor abstract base class to implement
|
||||||
the Visitor pattern
|
the Visitor pattern
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_literal(self, visitor):
|
def visit_literal(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_binary(self, visitor):
|
def visit_binary(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_grouping(self, visitor):
|
def visit_grouping(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_unary(self, visitor):
|
def visit_unary(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_get(self, visitor):
|
def visit_get(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_set(self, visitor):
|
def visit_set(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Binary(Expression):
|
class Binary(Expression):
|
||||||
left: Expression
|
left: Expression
|
||||||
operator: Token
|
operator: Token
|
||||||
right: Expression
|
right: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_binary(self)
|
return visitor.visit_binary(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Unary(Expression):
|
class Unary(Expression):
|
||||||
operator: Token
|
operator: Token
|
||||||
right: Expression
|
right: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_unary(self)
|
return visitor.visit_unary(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Literal(Expression):
|
class Literal(Expression):
|
||||||
value: object
|
value: object
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_literal(self)
|
return visitor.visit_literal(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Grouping(Expression):
|
class Grouping(Expression):
|
||||||
expr: Expression
|
expr: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_grouping(self)
|
return visitor.visit_grouping(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Variable(Expression):
|
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 super().__hash__()
|
return super().__hash__()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Assignment(Expression):
|
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 super().__hash__()
|
return super().__hash__()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Logical(Expression):
|
class Logical(Expression):
|
||||||
left: Expression
|
left: Expression
|
||||||
operator: Token
|
operator: Token
|
||||||
right: Expression
|
right: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_logical(self)
|
return visitor.visit_logical(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Call(Expression):
|
class Call(Expression):
|
||||||
callee: Expression
|
callee: Expression
|
||||||
paren: Token
|
paren: Token
|
||||||
arguments: List[Expression] = ()
|
arguments: List[Expression] = ()
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_call_expr(self)
|
return visitor.visit_call_expr(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Get(Expression):
|
class Get(Expression):
|
||||||
object: Expression
|
object: Expression
|
||||||
name: Token
|
name: Token
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_get(self)
|
return visitor.visit_get(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Set(Expression):
|
class Set(Expression):
|
||||||
object: Expression
|
object: Expression
|
||||||
name: Token
|
name: Token
|
||||||
value: Expression
|
value: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_set(self)
|
return visitor.visit_set(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class This(Expression):
|
class This(Expression):
|
||||||
keyword: Token
|
keyword: Token
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_this(self)
|
return visitor.visit_this(self)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return super().__hash__()
|
return super().__hash__()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Super(Expression):
|
class Super(Expression):
|
||||||
keyword: Token
|
keyword: Token
|
||||||
method: Token
|
method: Token
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_super(self)
|
return visitor.visit_super(self)
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return super().__hash__()
|
return super().__hash__()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
class FunctionType(Enum):
|
class FunctionType(Enum):
|
||||||
NONE = auto()
|
NONE = auto()
|
||||||
FUNCTION = auto()
|
FUNCTION = auto()
|
||||||
METHOD = auto()
|
METHOD = auto()
|
||||||
INIT = auto()
|
INIT = auto()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
|
||||||
class LoopType(Enum):
|
class LoopType(Enum):
|
||||||
|
|
||||||
NONE = auto()
|
NONE = auto()
|
||||||
WHILE = auto()
|
WHILE = auto()
|
||||||
|
|
|
@ -1,173 +1,173 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from .expression import Expression, Variable
|
from .expression import Expression, Variable
|
||||||
from .tokenobject import Token
|
from .tokenobject import Token
|
||||||
from typing import List, Any
|
from typing import List, Any
|
||||||
|
|
||||||
|
|
||||||
class Statement(object):
|
class Statement(object):
|
||||||
"""
|
"""
|
||||||
A Base Class representing JAPL statements
|
A Base Class representing JAPL statements
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
class Visitor(ABC):
|
class Visitor(ABC):
|
||||||
"""Wrapper to implement the Visitor Pattern"""
|
"""Wrapper to implement the Visitor Pattern"""
|
||||||
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_statement_expr(self, visitor):
|
def visit_statement_expr(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_var_stmt(self, visitor):
|
def visit_var_stmt(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_del(self, visitor):
|
def visit_del(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_block(self, visitor):
|
def visit_block(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_if(self, visitor):
|
def visit_if(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_while(self, visitor):
|
def visit_while(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_break(self, visitor):
|
def visit_break(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_function(self, visitor):
|
def visit_function(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def visit_return(self, visitor):
|
def visit_return(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def visit_class(self, visitor):
|
def visit_class(self, visitor):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class StatementExpr(Statement):
|
class StatementExpr(Statement):
|
||||||
"""
|
"""
|
||||||
An expression statement
|
An expression statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
expression: Expression
|
expression: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_statement_expr(self)
|
visitor.visit_statement_expr(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Var(Statement):
|
class Var(Statement):
|
||||||
"""
|
"""
|
||||||
A var statement
|
A var statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: Token
|
name: Token
|
||||||
init: Expression = None
|
init: Expression = None
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_var_stmt(self)
|
visitor.visit_var_stmt(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Del(Statement):
|
class Del(Statement):
|
||||||
"""
|
"""
|
||||||
A del statement
|
A del statement
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: Any
|
name: Any
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_del(self)
|
visitor.visit_del(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Block(Statement):
|
class Block(Statement):
|
||||||
"""A block statement"""
|
"""A block statement"""
|
||||||
|
|
||||||
statements: List[Statement]
|
statements: List[Statement]
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_block(self)
|
visitor.visit_block(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class If(Statement):
|
class If(Statement):
|
||||||
"""An if statement"""
|
"""An if statement"""
|
||||||
|
|
||||||
condition: Expression
|
condition: Expression
|
||||||
then_branch: Statement
|
then_branch: Statement
|
||||||
else_branch: Statement
|
else_branch: Statement
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_if(self)
|
visitor.visit_if(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class While(Statement):
|
class While(Statement):
|
||||||
"""A while statement"""
|
"""A while statement"""
|
||||||
|
|
||||||
condition: Expression
|
condition: Expression
|
||||||
body: Statement
|
body: Statement
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_while(self)
|
visitor.visit_while(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Break(Statement):
|
class Break(Statement):
|
||||||
"""A break statement"""
|
"""A break statement"""
|
||||||
|
|
||||||
token: Token
|
token: Token
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_break(self)
|
visitor.visit_break(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Function(Statement):
|
class Function(Statement):
|
||||||
"""A function statement"""
|
"""A function statement"""
|
||||||
|
|
||||||
name: Token
|
name: Token
|
||||||
params: List[Token]
|
params: List[Token]
|
||||||
body: List[Statement]
|
body: List[Statement]
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_function(self)
|
visitor.visit_function(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Return(Statement, BaseException):
|
class Return(Statement, BaseException):
|
||||||
"""A return statement"""
|
"""A return statement"""
|
||||||
|
|
||||||
keyword: Token
|
keyword: Token
|
||||||
value: Expression
|
value: Expression
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_return(self)
|
visitor.visit_return(self)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Class(Statement):
|
class Class(Statement):
|
||||||
"""A class statement"""
|
"""A class statement"""
|
||||||
|
|
||||||
name: Token
|
name: Token
|
||||||
methods: list
|
methods: list
|
||||||
superclass: Variable
|
superclass: Variable
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
visitor.visit_class(self)
|
visitor.visit_class(self)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from .tokentype import TokenType
|
from .tokentype import TokenType
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Token(object):
|
class Token(object):
|
||||||
"""The representation of a JAPL token"""
|
"""The representation of a JAPL token"""
|
||||||
|
|
||||||
kind: TokenType
|
kind: TokenType
|
||||||
lexeme: str
|
lexeme: str
|
||||||
literal: object
|
literal: object
|
||||||
line: int
|
line: int
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
class TokenType(Enum):
|
class TokenType(Enum):
|
||||||
"""
|
"""
|
||||||
An enumeration for all JAPL types
|
An enumeration for all JAPL types
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LP = auto()
|
LP = auto()
|
||||||
RP = auto()
|
RP = auto()
|
||||||
LB = auto()
|
LB = auto()
|
||||||
RB = auto()
|
RB = auto()
|
||||||
COMMA = auto()
|
COMMA = auto()
|
||||||
DOT = auto()
|
DOT = auto()
|
||||||
PLUS = auto()
|
PLUS = auto()
|
||||||
MINUS = auto()
|
MINUS = auto()
|
||||||
SLASH = auto()
|
SLASH = auto()
|
||||||
SEMICOLON = auto()
|
SEMICOLON = auto()
|
||||||
STAR = auto()
|
STAR = auto()
|
||||||
|
|
||||||
|
|
||||||
NEG = auto()
|
NEG = auto()
|
||||||
NE = auto()
|
NE = auto()
|
||||||
EQ = auto()
|
EQ = auto()
|
||||||
DEQ = auto()
|
DEQ = auto()
|
||||||
GT = auto()
|
GT = auto()
|
||||||
LT = auto()
|
LT = auto()
|
||||||
GE = auto()
|
GE = auto()
|
||||||
LE = auto()
|
LE = auto()
|
||||||
MOD = auto()
|
MOD = auto()
|
||||||
POW = auto()
|
POW = auto()
|
||||||
|
|
||||||
ID = auto()
|
ID = auto()
|
||||||
STR = auto()
|
STR = auto()
|
||||||
NUM = auto()
|
NUM = auto()
|
||||||
|
|
||||||
|
|
||||||
AND = auto()
|
AND = auto()
|
||||||
CLASS = auto()
|
CLASS = auto()
|
||||||
ELSE = auto()
|
ELSE = auto()
|
||||||
FOR = auto()
|
FOR = auto()
|
||||||
FUN = auto()
|
FUN = auto()
|
||||||
FALSE = auto()
|
FALSE = auto()
|
||||||
IF = auto()
|
IF = auto()
|
||||||
NIL = auto()
|
NIL = auto()
|
||||||
OR = auto()
|
OR = auto()
|
||||||
RETURN = auto()
|
RETURN = auto()
|
||||||
SUPER = auto()
|
SUPER = auto()
|
||||||
THIS = auto()
|
THIS = auto()
|
||||||
TRUE = auto()
|
TRUE = auto()
|
||||||
VAR = auto()
|
VAR = auto()
|
||||||
WHILE = auto()
|
WHILE = auto()
|
||||||
DEL = auto()
|
DEL = auto()
|
||||||
BREAK = auto()
|
BREAK = auto()
|
||||||
|
|
||||||
EOF = auto()
|
EOF = auto()
|
||||||
|
|
|
@ -0,0 +1,444 @@
|
||||||
|
from .meta.exceptions import ParseError
|
||||||
|
from .meta.tokentype import TokenType
|
||||||
|
from .meta.tokenobject import Token
|
||||||
|
from typing import List, Union
|
||||||
|
from .meta.expression import Variable, Assignment, Logical, Call, Binary, Unary, Literal, Grouping, Expression, Get, Set, This, Super
|
||||||
|
from .meta.statement import StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Class
|
||||||
|
|
||||||
|
|
||||||
|
class Parser(object):
|
||||||
|
"""A simple recursive-descent top-down parser"""
|
||||||
|
|
||||||
|
def __init__(self, tokens: List[Token]):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.tokens = tokens
|
||||||
|
self.current: int = 0
|
||||||
|
|
||||||
|
def check(self, token_type):
|
||||||
|
"""
|
||||||
|
Helper method for self.match
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.done():
|
||||||
|
return False
|
||||||
|
elif self.peek().kind == token_type:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def throw(self, token: Token, message: str) -> ParseError:
|
||||||
|
"""Returns ParseError with the given message"""
|
||||||
|
|
||||||
|
return ParseError(token, message)
|
||||||
|
|
||||||
|
def synchronize(self):
|
||||||
|
"""Synchronizes the parser's state to recover after
|
||||||
|
an error occurred while parsing"""
|
||||||
|
|
||||||
|
self.step()
|
||||||
|
while not self.done():
|
||||||
|
if self.previous().kind == TokenType.SEMICOLON:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
token_type = self.peek().kind
|
||||||
|
if token_type in (
|
||||||
|
TokenType.IF, TokenType.CLASS, TokenType.VAR, TokenType.FOR, TokenType.WHILE,
|
||||||
|
TokenType.RETURN, TokenType.FUN
|
||||||
|
):
|
||||||
|
return
|
||||||
|
self.step()
|
||||||
|
|
||||||
|
def peek(self):
|
||||||
|
"""
|
||||||
|
Returns a token without consuming it
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.tokens[self.current]
|
||||||
|
|
||||||
|
def previous(self):
|
||||||
|
"""
|
||||||
|
Returns the most recently consumed token
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.tokens[self.current - 1]
|
||||||
|
|
||||||
|
def done(self):
|
||||||
|
"""
|
||||||
|
Returns True if we reached EOF
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.peek().kind == TokenType.EOF
|
||||||
|
|
||||||
|
def match(self, *types: Union[TokenType, List[TokenType]]):
|
||||||
|
"""
|
||||||
|
Checks if the current token matches
|
||||||
|
any of the given token type(s)
|
||||||
|
"""
|
||||||
|
|
||||||
|
for token_type in types:
|
||||||
|
if self.check(token_type):
|
||||||
|
self.step()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.check(token_type):
|
||||||
|
return self.step()
|
||||||
|
raise self.throw(self.peek(), message)
|
||||||
|
|
||||||
|
def primary(self):
|
||||||
|
"""Parses unary expressions (literals)"""
|
||||||
|
|
||||||
|
if self.match(TokenType.FALSE):
|
||||||
|
return Literal(False)
|
||||||
|
elif self.match(TokenType.TRUE):
|
||||||
|
return Literal(True)
|
||||||
|
elif self.match(TokenType.NIL):
|
||||||
|
return Literal(None)
|
||||||
|
elif self.match(TokenType.NUM, TokenType.STR):
|
||||||
|
return Literal(self.previous().literal)
|
||||||
|
elif self.match(TokenType.LP):
|
||||||
|
expr: Expression = self.expression()
|
||||||
|
self.consume(TokenType.RP, "Unexpected error while parsing parenthesized expression")
|
||||||
|
return Grouping(expr)
|
||||||
|
elif self.match(TokenType.ID):
|
||||||
|
return Variable(self.previous())
|
||||||
|
elif self.match(TokenType.SUPER):
|
||||||
|
keyword = self.previous()
|
||||||
|
self.consume(TokenType.DOT, "Expecting '.' after 'super'")
|
||||||
|
method = self.consume(TokenType.ID, "Expecting superclass method name")
|
||||||
|
return Super(keyword, method)
|
||||||
|
elif self.match(TokenType.THIS):
|
||||||
|
return This(self.previous())
|
||||||
|
raise self.throw(self.peek(), "Invalid syntax")
|
||||||
|
|
||||||
|
def finish_call(self, callee):
|
||||||
|
"""Parses a function call"""
|
||||||
|
|
||||||
|
arguments = []
|
||||||
|
if not self.check(TokenType.RP):
|
||||||
|
while True:
|
||||||
|
if len(arguments) >= 255:
|
||||||
|
raise self.throw(self.peek(), "Cannot have more than 255 arguments")
|
||||||
|
arguments.append(self.expression())
|
||||||
|
if not self.match(TokenType.COMMA):
|
||||||
|
break
|
||||||
|
paren = self.consume(TokenType.RP, "Unexpected error while parsing call")
|
||||||
|
return Call(callee, paren, arguments)
|
||||||
|
|
||||||
|
def call(self):
|
||||||
|
"""Parses call expressions"""
|
||||||
|
|
||||||
|
expr = self.primary()
|
||||||
|
while True:
|
||||||
|
if self.match(TokenType.LP):
|
||||||
|
expr = self.finish_call(expr)
|
||||||
|
elif self.match(TokenType.DOT):
|
||||||
|
name = self.consume(TokenType.ID, "Expecting property after '.'")
|
||||||
|
expr = Get(expr, name)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def unary(self):
|
||||||
|
"""Parses unary expressions"""
|
||||||
|
|
||||||
|
if self.match(TokenType.NEG, TokenType.MINUS):
|
||||||
|
operator: Token = self.previous()
|
||||||
|
right: Expression = self.unary()
|
||||||
|
return Unary(operator, right)
|
||||||
|
return self.call()
|
||||||
|
|
||||||
|
def pow(self):
|
||||||
|
"""Parses pow expressions"""
|
||||||
|
|
||||||
|
expr: Expression = self.unary()
|
||||||
|
while self.match(TokenType.POW):
|
||||||
|
operator: Token = self.previous()
|
||||||
|
right: Expression = self.unary()
|
||||||
|
expr = Binary(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def multiplication(self):
|
||||||
|
"""
|
||||||
|
Parses multiplications and divisions
|
||||||
|
"""
|
||||||
|
|
||||||
|
expr: Expression = self.pow()
|
||||||
|
while self.match(TokenType.STAR, TokenType.SLASH, TokenType.MOD):
|
||||||
|
operator: Token = self.previous()
|
||||||
|
right: Expression = self.pow()
|
||||||
|
expr = Binary(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def addition(self):
|
||||||
|
"""
|
||||||
|
Parses additions and subtractions
|
||||||
|
"""
|
||||||
|
|
||||||
|
expr: Expression = self.multiplication()
|
||||||
|
while self.match(TokenType.PLUS, TokenType.MINUS):
|
||||||
|
operator: Token = self.previous()
|
||||||
|
right: Expression = self.multiplication()
|
||||||
|
expr = Binary(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def comparison(self):
|
||||||
|
"""
|
||||||
|
Parses comparison expressions
|
||||||
|
"""
|
||||||
|
|
||||||
|
expr: Expression = self.addition()
|
||||||
|
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)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def equality(self):
|
||||||
|
"""
|
||||||
|
Parses equality expressions
|
||||||
|
"""
|
||||||
|
|
||||||
|
expr: Expression = self.comparison()
|
||||||
|
while self.match(TokenType.NEG, TokenType.DEQ):
|
||||||
|
operator: Token = self.previous()
|
||||||
|
right: Expression = self.comparison()
|
||||||
|
expr = Binary(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def logical_and(self):
|
||||||
|
"""Parses a logical and expression"""
|
||||||
|
|
||||||
|
expr = self.equality()
|
||||||
|
while self.match(TokenType.AND):
|
||||||
|
operator = self.previous()
|
||||||
|
right = self.equality()
|
||||||
|
expr = Logical(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def logical_or(self):
|
||||||
|
"""Parses a logical or expression"""
|
||||||
|
|
||||||
|
expr = self.logical_and()
|
||||||
|
while self.match(TokenType.OR):
|
||||||
|
operator = self.previous()
|
||||||
|
right = self.logical_and()
|
||||||
|
expr = Logical(expr, operator, right)
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def assignment(self):
|
||||||
|
"""
|
||||||
|
Parses an assignment expression
|
||||||
|
"""
|
||||||
|
|
||||||
|
expr = self.logical_or()
|
||||||
|
if self.match(TokenType.EQ):
|
||||||
|
eq = self.previous()
|
||||||
|
value = self.assignment()
|
||||||
|
if isinstance(expr, Variable):
|
||||||
|
name = expr.name
|
||||||
|
return Assignment(name, value)
|
||||||
|
elif isinstance(expr, Get):
|
||||||
|
return Set(expr.object, expr.name, value)
|
||||||
|
raise self.throw(eq, "Invalid syntax")
|
||||||
|
return expr
|
||||||
|
|
||||||
|
def expression(self):
|
||||||
|
"""
|
||||||
|
Parses an expression
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.assignment()
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
"""Steps 1 token forward"""
|
||||||
|
|
||||||
|
if not self.done():
|
||||||
|
self.current += 1
|
||||||
|
return self.previous()
|
||||||
|
|
||||||
|
def del_statement(self):
|
||||||
|
"""Returns a del AST node"""
|
||||||
|
|
||||||
|
value = self.expression()
|
||||||
|
self.consume(TokenType.SEMICOLON, "Missing semicolon after statement")
|
||||||
|
return Del(value)
|
||||||
|
|
||||||
|
def expression_statement(self):
|
||||||
|
"""Returns a StatemenrExpr AST node"""
|
||||||
|
|
||||||
|
value = self.expression()
|
||||||
|
self.consume(TokenType.SEMICOLON, "Missing semicolon after statement")
|
||||||
|
return StatementExpr(value)
|
||||||
|
|
||||||
|
def block(self):
|
||||||
|
"""Returns a new environment to enable block scoping"""
|
||||||
|
|
||||||
|
statements = []
|
||||||
|
while not self.check(TokenType.RB) and not self.done():
|
||||||
|
statements.append(self.declaration())
|
||||||
|
self.consume(TokenType.RB, "Unexpected end of block")
|
||||||
|
return statements
|
||||||
|
|
||||||
|
def if_statement(self):
|
||||||
|
"""Parses an IF statement"""
|
||||||
|
|
||||||
|
self.consume(TokenType.LP, "The if condition must be parenthesized")
|
||||||
|
cond = self.expression()
|
||||||
|
self.consume(TokenType.RP, "The if condition must be parenthesized")
|
||||||
|
then_branch = self.statement()
|
||||||
|
else_branch = None
|
||||||
|
if self.match(TokenType.ELSE):
|
||||||
|
else_branch = self.statement()
|
||||||
|
return If(cond, then_branch, else_branch)
|
||||||
|
|
||||||
|
def while_statement(self):
|
||||||
|
"""Parses a while statement"""
|
||||||
|
|
||||||
|
self.consume(TokenType.LP, "The while condition must be parenthesized")
|
||||||
|
cond = self.expression()
|
||||||
|
self.consume(TokenType.RP, "The while condition must be parenthesized")
|
||||||
|
body = self.statement()
|
||||||
|
return While(cond, body)
|
||||||
|
|
||||||
|
def for_statement(self):
|
||||||
|
"""Parses a for statement"""
|
||||||
|
|
||||||
|
self.consume(TokenType.LP, "The for condition must be parenthesized")
|
||||||
|
if self.match(TokenType.SEMICOLON):
|
||||||
|
init = None
|
||||||
|
elif self.match(TokenType.VAR):
|
||||||
|
init = self.var_declaration()
|
||||||
|
else:
|
||||||
|
init = self.expression_statement()
|
||||||
|
condition = None
|
||||||
|
if not self.check(TokenType.SEMICOLON):
|
||||||
|
condition = self.expression()
|
||||||
|
self.consume(TokenType.SEMICOLON, "Missing semicolon after loop condition")
|
||||||
|
incr = None
|
||||||
|
if not self.check(TokenType.RP):
|
||||||
|
incr = self.expression()
|
||||||
|
self.consume(TokenType.RP, "The for condition must be parenthesized")
|
||||||
|
body = self.statement()
|
||||||
|
if incr:
|
||||||
|
body = Block([body, StatementExpr(incr)])
|
||||||
|
if not condition:
|
||||||
|
condition = Literal(True)
|
||||||
|
body = While(condition, body)
|
||||||
|
if init:
|
||||||
|
body = Block([init, body])
|
||||||
|
return body
|
||||||
|
|
||||||
|
def break_statement(self):
|
||||||
|
"""Parses a break statement"""
|
||||||
|
|
||||||
|
if self.check(TokenType.SEMICOLON):
|
||||||
|
return self.step()
|
||||||
|
raise ParseError(self.peek(), "Invalid syntax")
|
||||||
|
|
||||||
|
def return_statement(self):
|
||||||
|
"""Parses a return statement"""
|
||||||
|
|
||||||
|
keyword = self.previous()
|
||||||
|
value = None
|
||||||
|
if not self.check(TokenType.SEMICOLON):
|
||||||
|
value = self.expression()
|
||||||
|
self.consume(TokenType.SEMICOLON, "Missing semicolon after statement")
|
||||||
|
return Return(keyword, value)
|
||||||
|
|
||||||
|
def statement(self):
|
||||||
|
"""Parses a statement"""
|
||||||
|
|
||||||
|
if self.match(TokenType.IF):
|
||||||
|
return self.if_statement()
|
||||||
|
elif self.match(TokenType.RETURN):
|
||||||
|
return self.return_statement()
|
||||||
|
elif self.match(TokenType.FOR):
|
||||||
|
return self.for_statement()
|
||||||
|
elif self.match(TokenType.WHILE):
|
||||||
|
return self.while_statement()
|
||||||
|
elif self.match(TokenType.BREAK):
|
||||||
|
return Break(self.break_statement())
|
||||||
|
elif self.match(TokenType.LB):
|
||||||
|
return Block(self.block())
|
||||||
|
elif self.match(TokenType.DEL):
|
||||||
|
return self.del_statement()
|
||||||
|
return self.expression_statement()
|
||||||
|
|
||||||
|
def var_declaration(self):
|
||||||
|
"""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)
|
||||||
|
|
||||||
|
def function(self, kind: str):
|
||||||
|
"""Parses a function declaration"""
|
||||||
|
|
||||||
|
name = self.consume(TokenType.ID, f"Expecting {kind} name")
|
||||||
|
self.consume(TokenType.LP, f"Expecting parenthesis after {kind} name")
|
||||||
|
parameters = []
|
||||||
|
if not self.check(TokenType.RP):
|
||||||
|
while True:
|
||||||
|
if len(parameters) >= 255:
|
||||||
|
raise self.throw(self.peek(), "Cannot have more than 255 arguments")
|
||||||
|
parameter = self.consume(TokenType.ID, "Expecting parameter name")
|
||||||
|
if parameter in parameters:
|
||||||
|
raise self.throw(self.peek(), "Multiple parameters with the same name in function declaration are not allowed")
|
||||||
|
parameters.append(parameter)
|
||||||
|
if not self.match(TokenType.COMMA):
|
||||||
|
break
|
||||||
|
self.consume(TokenType.RP, "Unexpected error while parsing function declaration")
|
||||||
|
self.consume(TokenType.LB, f"Expecting '{{' before {kind} body")
|
||||||
|
body = self.block()
|
||||||
|
return Function(name, parameters, body)
|
||||||
|
|
||||||
|
def class_declaration(self):
|
||||||
|
"""Parses a class declaration"""
|
||||||
|
|
||||||
|
name = self.consume(TokenType.ID, "Expecting class name")
|
||||||
|
superclass = None
|
||||||
|
if self.match(TokenType.LT):
|
||||||
|
self.consume(TokenType.ID, "Expecting superclass name")
|
||||||
|
superclass = Variable(self.previous())
|
||||||
|
self.consume(TokenType.LB, "Expecting '{' before class body")
|
||||||
|
methods = []
|
||||||
|
while not self.check(TokenType.RB) and not self.done():
|
||||||
|
methods.append(self.function("method"))
|
||||||
|
self.consume(TokenType.RB, "Expecting '}' after class body")
|
||||||
|
return Class(name, methods, superclass)
|
||||||
|
|
||||||
|
def declaration(self):
|
||||||
|
"""Parses a declaration"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.match(TokenType.CLASS):
|
||||||
|
return self.class_declaration()
|
||||||
|
elif self.match(TokenType.FUN):
|
||||||
|
return self.function("function")
|
||||||
|
elif self.match(TokenType.VAR):
|
||||||
|
return self.var_declaration()
|
||||||
|
return self.statement()
|
||||||
|
except ParseError:
|
||||||
|
self.synchronize()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Starts to parse
|
||||||
|
"""
|
||||||
|
|
||||||
|
statements = []
|
||||||
|
while not self.done():
|
||||||
|
statements.append(self.declaration())
|
||||||
|
return statements
|
|
@ -0,0 +1,281 @@
|
||||||
|
from .meta.exceptions import JAPLError
|
||||||
|
from .meta.expression import Expression
|
||||||
|
from .meta.statement import Statement
|
||||||
|
from .meta.functiontype import FunctionType
|
||||||
|
from .meta.classtype import ClassType
|
||||||
|
from .meta.looptype import LoopType
|
||||||
|
try:
|
||||||
|
from functools import singledispatchmethod
|
||||||
|
except ImportError:
|
||||||
|
from singledispatchmethod import singledispatchmethod # Backport
|
||||||
|
from typing import List, Union
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
|
class Resolver(Expression.Visitor, Statement.Visitor):
|
||||||
|
"""
|
||||||
|
This class serves the purpose of correctly resolving
|
||||||
|
name bindings (even with closures) efficiently
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, interpreter):
|
||||||
|
"""
|
||||||
|
Object constructor
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.interpreter = interpreter
|
||||||
|
self.scopes = deque()
|
||||||
|
self.current_function = FunctionType.NONE
|
||||||
|
self.current_loop = LoopType.NONE
|
||||||
|
self.current_class = ClassType.NONE
|
||||||
|
|
||||||
|
@singledispatchmethod
|
||||||
|
def resolve(self, stmt_or_expr: Union[Statement, Expression, List[Statement]]):
|
||||||
|
"""Generic method to dispatch statements/expressions"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def begin_scope(self):
|
||||||
|
"""
|
||||||
|
Opens a new scope
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.scopes.append({})
|
||||||
|
|
||||||
|
def end_scope(self):
|
||||||
|
"""
|
||||||
|
Ends a scope
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.scopes.pop()
|
||||||
|
|
||||||
|
@resolve.register
|
||||||
|
def resolve_statement(self, stmt: Statement):
|
||||||
|
"""
|
||||||
|
Resolves names for the given group
|
||||||
|
of statements
|
||||||
|
"""
|
||||||
|
|
||||||
|
stmt.accept(self)
|
||||||
|
|
||||||
|
@resolve.register
|
||||||
|
def resolve_expression(self, expression: Expression):
|
||||||
|
"""
|
||||||
|
Resolves an expression
|
||||||
|
"""
|
||||||
|
|
||||||
|
return expression.accept(self)
|
||||||
|
|
||||||
|
@resolve.register
|
||||||
|
def resolve_statements(self, stmt: list):
|
||||||
|
"""Resolves multiple statements"""
|
||||||
|
|
||||||
|
for statement in stmt:
|
||||||
|
self.resolve(statement)
|
||||||
|
|
||||||
|
def declare(self, name):
|
||||||
|
"""
|
||||||
|
Declares a new variable
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.scopes:
|
||||||
|
return
|
||||||
|
scope = self.scopes[-1]
|
||||||
|
if name.lexeme in scope:
|
||||||
|
raise JAPLError(name, "Cannot re-declare the same variable in local scope, use assignment instead")
|
||||||
|
scope[name.lexeme] = False
|
||||||
|
|
||||||
|
def define(self, name):
|
||||||
|
"""
|
||||||
|
Defines a new variable
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.scopes:
|
||||||
|
return
|
||||||
|
scope = self.scopes[-1]
|
||||||
|
scope[name.lexeme] = True
|
||||||
|
|
||||||
|
def visit_block(self, block):
|
||||||
|
"""Starts name resolution on a given block"""
|
||||||
|
|
||||||
|
self.begin_scope()
|
||||||
|
self.resolve(block.statements)
|
||||||
|
self.end_scope()
|
||||||
|
|
||||||
|
def visit_var_stmt(self, stmt):
|
||||||
|
"""Visits a var statement node"""
|
||||||
|
|
||||||
|
self.declare(stmt.name)
|
||||||
|
if stmt.init:
|
||||||
|
self.resolve(stmt.init)
|
||||||
|
self.define(stmt.name)
|
||||||
|
|
||||||
|
def visit_var_expr(self, expr):
|
||||||
|
"""Visits a var expression node"""
|
||||||
|
|
||||||
|
if self.scopes and self.scopes[-1].get(expr.name.lexeme) is False:
|
||||||
|
raise JAPLError(expr.name, f"Cannot read local variable in its own initializer")
|
||||||
|
self.resolve_local(expr, expr.name)
|
||||||
|
|
||||||
|
def resolve_local(self, expr, name):
|
||||||
|
"""Resolves local variables"""
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for scope in reversed(self.scopes):
|
||||||
|
if name.lexeme in scope:
|
||||||
|
self.interpreter.resolve(expr, i)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def resolve_function(self, function, function_type: FunctionType):
|
||||||
|
"""Resolves function objects"""
|
||||||
|
|
||||||
|
enclosing = self.current_function
|
||||||
|
self.current_function = function_type
|
||||||
|
self.begin_scope()
|
||||||
|
for param in function.params:
|
||||||
|
self.declare(param)
|
||||||
|
self.define(param)
|
||||||
|
self.resolve(function.body)
|
||||||
|
self.end_scope()
|
||||||
|
self.current_function = enclosing
|
||||||
|
|
||||||
|
def visit_assign(self, expr):
|
||||||
|
"""Visits an assignment expression"""
|
||||||
|
|
||||||
|
self.resolve(expr.value)
|
||||||
|
self.resolve_local(expr, expr.name)
|
||||||
|
|
||||||
|
def visit_function(self, stmt):
|
||||||
|
"""Visits a function statement"""
|
||||||
|
|
||||||
|
self.declare(stmt.name)
|
||||||
|
self.define(stmt.name)
|
||||||
|
self.resolve_function(stmt, FunctionType.FUNCTION)
|
||||||
|
|
||||||
|
def visit_class(self, stmt):
|
||||||
|
"""Visits a class statement"""
|
||||||
|
|
||||||
|
enclosing = self.current_class
|
||||||
|
self.current_class = ClassType.CLASS
|
||||||
|
self.declare(stmt.name)
|
||||||
|
self.define(stmt.name)
|
||||||
|
if stmt.superclass:
|
||||||
|
if stmt.superclass.name.lexeme == stmt.name.lexeme:
|
||||||
|
raise JAPLError(stmt.name, "A class cannot inherit from itself")
|
||||||
|
self.resolve(stmt.superclass)
|
||||||
|
self.begin_scope()
|
||||||
|
self.scopes[-1]["super"] = True
|
||||||
|
self.begin_scope()
|
||||||
|
self.scopes[-1]["this"] = True
|
||||||
|
for method in stmt.methods:
|
||||||
|
ftype = FunctionType.METHOD
|
||||||
|
if method.name.lexeme == "init":
|
||||||
|
ftype = FunctionType.INIT
|
||||||
|
self.resolve_function(method, ftype)
|
||||||
|
self.end_scope()
|
||||||
|
if stmt.superclass:
|
||||||
|
self.end_scope()
|
||||||
|
self.current_class = enclosing
|
||||||
|
|
||||||
|
def visit_statement_expr(self, stmt):
|
||||||
|
"""Visits a statement expression node"""
|
||||||
|
|
||||||
|
self.resolve(stmt.expression)
|
||||||
|
|
||||||
|
def visit_if(self, stmt):
|
||||||
|
"""Visits an if statement node"""
|
||||||
|
|
||||||
|
self.resolve(stmt.condition)
|
||||||
|
self.resolve(stmt.then_branch)
|
||||||
|
if stmt.else_branch:
|
||||||
|
self.resolve(stmt.else_branch)
|
||||||
|
|
||||||
|
def visit_return(self, stmt):
|
||||||
|
"""Visits a return statement node"""
|
||||||
|
|
||||||
|
if self.current_function == FunctionType.NONE:
|
||||||
|
raise JAPLError(stmt.keyword, "'return' outside function")
|
||||||
|
elif self.current_function == FunctionType.INIT:
|
||||||
|
raise JAPLError(stmt.keyword, "Cannot explicitly return from constructor")
|
||||||
|
elif stmt.value is not None:
|
||||||
|
self.resolve(stmt.value)
|
||||||
|
|
||||||
|
def visit_while(self, stmt):
|
||||||
|
"""Visits a while statement node"""
|
||||||
|
|
||||||
|
loop = self.current_loop
|
||||||
|
self.current_loop = LoopType.WHILE
|
||||||
|
self.resolve(stmt.condition)
|
||||||
|
self.resolve(stmt.body)
|
||||||
|
self.current_loop = loop
|
||||||
|
|
||||||
|
def visit_binary(self, expr):
|
||||||
|
"""Visits a binary expression node"""
|
||||||
|
|
||||||
|
self.resolve(expr.left)
|
||||||
|
self.resolve(expr.right)
|
||||||
|
|
||||||
|
def visit_call_expr(self, expr):
|
||||||
|
"""Visits a call expression node"""
|
||||||
|
|
||||||
|
self.resolve(expr.callee)
|
||||||
|
for argument in expr.arguments:
|
||||||
|
self.resolve(argument)
|
||||||
|
|
||||||
|
def visit_grouping(self, expr):
|
||||||
|
"""Visits a grouping expression"""
|
||||||
|
|
||||||
|
self.resolve(expr.expr)
|
||||||
|
|
||||||
|
def visit_literal(self, expr):
|
||||||
|
"""Visits a literal node"""
|
||||||
|
|
||||||
|
return # Literal has no subexpressions and does not reference variables
|
||||||
|
|
||||||
|
def visit_logical(self, expr):
|
||||||
|
"""Visits a logical node"""
|
||||||
|
|
||||||
|
self.visit_binary(expr) # No need to short circuit, so it's the same!
|
||||||
|
|
||||||
|
def visit_unary(self, expr):
|
||||||
|
"""Visits a unary node"""
|
||||||
|
|
||||||
|
self.resolve(expr.right)
|
||||||
|
|
||||||
|
def visit_del(self, stmt):
|
||||||
|
"""Visits a del statement"""
|
||||||
|
|
||||||
|
self.resolve(stmt.name)
|
||||||
|
|
||||||
|
def visit_break(self, stmt):
|
||||||
|
"""Visits a break statement"""
|
||||||
|
|
||||||
|
if self.current_loop == LoopType.NONE:
|
||||||
|
raise JAPLError("'break' outside loop")
|
||||||
|
|
||||||
|
def visit_get(self, expr):
|
||||||
|
"""Visits a property get expression"""
|
||||||
|
|
||||||
|
self.resolve(expr.object)
|
||||||
|
|
||||||
|
def visit_set(self, expr):
|
||||||
|
"""Visits a property set expression"""
|
||||||
|
|
||||||
|
self.resolve(expr.value)
|
||||||
|
self.resolve(expr.object)
|
||||||
|
|
||||||
|
def visit_this(self, expr):
|
||||||
|
"""Visits a 'this' expression"""
|
||||||
|
|
||||||
|
if self.current_class == ClassType.NONE:
|
||||||
|
raise JAPLError(expr.keyword, "'this' outside class")
|
||||||
|
self.resolve_local(expr, expr.keyword)
|
||||||
|
|
||||||
|
def visit_super(self, expr):
|
||||||
|
"""Visits a 'super' expression"""
|
||||||
|
|
||||||
|
if self.current_class == ClassType.NONE:
|
||||||
|
raise JAPLError(expr.keyword, "'super' outside class")
|
||||||
|
self.resolve_local(expr, expr.keyword)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
class Callable(object):
|
||||||
|
"""A generic callable"""
|
||||||
|
|
||||||
|
def call(self, interpreter, arguments):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __init__(self, arity):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = arity
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
from ..meta.exceptions import JAPLError
|
||||||
|
from ..meta.tokenobject import Token
|
||||||
|
|
||||||
|
|
||||||
|
class JAPLInstance:
|
||||||
|
"""A class instance"""
|
||||||
|
|
||||||
|
def __init__(self, klass):
|
||||||
|
self.klass = klass
|
||||||
|
self.fields = {}
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<instance of '{self.klass.name}'>"
|
||||||
|
|
||||||
|
def get(self, name: Token):
|
||||||
|
if name.lexeme in self.fields:
|
||||||
|
return self.fields[name.lexeme]
|
||||||
|
meth = self.klass.get_method(name.lexeme)
|
||||||
|
if meth:
|
||||||
|
return meth.bind(self)
|
||||||
|
raise JAPLError(name, f"Undefined property '{name.lexeme}'")
|
||||||
|
|
||||||
|
def set(self, name: Token, value: object):
|
||||||
|
self.fields[name.lexeme] = value
|
|
@ -0,0 +1,35 @@
|
||||||
|
from .callable import Callable
|
||||||
|
from .instance import JAPLInstance
|
||||||
|
|
||||||
|
|
||||||
|
class JAPLClass(Callable):
|
||||||
|
"""A JAPL class"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, methods: dict, superclass):
|
||||||
|
self.name = name
|
||||||
|
self.methods = methods
|
||||||
|
self.superclass = superclass
|
||||||
|
if self.get_method("init"):
|
||||||
|
self.arity = self.get_method("init").arity
|
||||||
|
else:
|
||||||
|
self.arity = 0
|
||||||
|
|
||||||
|
def get_method(self, name: str):
|
||||||
|
if name in self.methods:
|
||||||
|
return self.methods[name]
|
||||||
|
superclass = self.superclass
|
||||||
|
while superclass:
|
||||||
|
if name in superclass.methods:
|
||||||
|
return superclass.methods[name]
|
||||||
|
superclass = superclass.superclass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<class '{self.name}'>"
|
||||||
|
|
||||||
|
def call(self, interpreter, arguments):
|
||||||
|
instance = JAPLInstance(self)
|
||||||
|
constructor = self.get_method("init")
|
||||||
|
if constructor:
|
||||||
|
constructor.bind(instance).call(interpreter, arguments)
|
||||||
|
return instance
|
||||||
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
from .callable import Callable
|
||||||
|
import time
|
||||||
|
from ..meta.environment import Environment
|
||||||
|
from ..meta.exceptions import ReturnException
|
||||||
|
from .instance import JAPLInstance
|
||||||
|
from .japlclass import JAPLClass
|
||||||
|
|
||||||
|
|
||||||
|
class Clock(Callable):
|
||||||
|
"""JAPL's wrapper around time.time"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 0
|
||||||
|
|
||||||
|
def call(self, *args):
|
||||||
|
return time.time()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<built-in function clock>"
|
||||||
|
|
||||||
|
|
||||||
|
class Type(Callable):
|
||||||
|
"""JAPL's wrapper around type"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 1
|
||||||
|
|
||||||
|
def call(self, _, obj):
|
||||||
|
return type(obj[0])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<built-in function type>"
|
||||||
|
|
||||||
|
|
||||||
|
class Truthy(Callable):
|
||||||
|
"""JAPL's wrapper around bool"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 1
|
||||||
|
|
||||||
|
def call(self, _, obj):
|
||||||
|
return bool(obj[0])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<built-in function truthy>"
|
||||||
|
|
||||||
|
|
||||||
|
class Stringify(Callable):
|
||||||
|
"""JAPL's wrapper around str()"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 1
|
||||||
|
|
||||||
|
def call(self, _, obj):
|
||||||
|
return str(obj[0])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<built-in function stringify>"
|
||||||
|
|
||||||
|
|
||||||
|
class PrintFunction(Callable):
|
||||||
|
"""The print function"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 1
|
||||||
|
|
||||||
|
def call(self, _, *args):
|
||||||
|
print(*args[0])
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<built-in function print>"
|
||||||
|
|
||||||
|
|
||||||
|
class IsInstance(Callable):
|
||||||
|
"""The isinstance function"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 2
|
||||||
|
|
||||||
|
def call(self, _, args):
|
||||||
|
instance, klass = args
|
||||||
|
if not isinstance(instance, JAPLInstance):
|
||||||
|
return False
|
||||||
|
elif not isinstance(klass, JAPLClass):
|
||||||
|
return False
|
||||||
|
return instance.klass == klass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<built-in function isinstance>"
|
||||||
|
|
||||||
|
|
||||||
|
class IsSubclass(Callable):
|
||||||
|
"""The isinstance function"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 2
|
||||||
|
|
||||||
|
def call(self, _, args):
|
||||||
|
first, second = args
|
||||||
|
if not isinstance(first, JAPLClass):
|
||||||
|
return False
|
||||||
|
elif not isinstance(second, JAPLClass):
|
||||||
|
return False
|
||||||
|
return first.superclass == second
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<built-in function issubclass>"
|
||||||
|
|
||||||
|
|
||||||
|
class IsSuperclass(Callable):
|
||||||
|
"""The isinstance function"""
|
||||||
|
|
||||||
|
def __init__(self, *_):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.arity = 2
|
||||||
|
|
||||||
|
def call(self, _, args):
|
||||||
|
first, second = args
|
||||||
|
if not isinstance(first, JAPLClass):
|
||||||
|
return False
|
||||||
|
elif not isinstance(second, JAPLClass):
|
||||||
|
return False
|
||||||
|
return second.superclass == first
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<built-in function issuperclass>"
|
||||||
|
|
||||||
|
|
||||||
|
class JAPLFunction(Callable):
|
||||||
|
"""A generic wrapper for user-defined functions"""
|
||||||
|
|
||||||
|
def __init__(self, declaration, closure):
|
||||||
|
"""Object constructor"""
|
||||||
|
|
||||||
|
self.declaration = declaration
|
||||||
|
self._repr = f"<function {self.declaration.name.lexeme}>"
|
||||||
|
self.arity = len(self.declaration.params)
|
||||||
|
self.closure = closure
|
||||||
|
|
||||||
|
def bind(self, obj: object):
|
||||||
|
"""Binds a method to an object"""
|
||||||
|
|
||||||
|
env = Environment(self.closure)
|
||||||
|
env.define("this", obj)
|
||||||
|
func = type(self)(self.declaration, env)
|
||||||
|
func._repr = f"<bound method {func.declaration.name.lexeme} of object {obj.klass.name}>"
|
||||||
|
return func
|
||||||
|
|
||||||
|
def call(self, interpreter, arguments):
|
||||||
|
scope = Environment(self.closure)
|
||||||
|
for name, value in zip(self.declaration.params, arguments):
|
||||||
|
scope.define(name.lexeme, value)
|
||||||
|
try:
|
||||||
|
interpreter.execute_block(self.declaration.body, scope)
|
||||||
|
except ReturnException as error:
|
||||||
|
return error.args[0]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self._repr
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
from JAPL.lexer import Lexer
|
||||||
|
from JAPL.meta.exceptions import ParseError, JAPLError
|
||||||
|
from JAPL.resolver import Resolver
|
||||||
|
from JAPL.parser import Parser
|
||||||
|
from JAPL.interpreter import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
class JAPL(object):
|
||||||
|
"""Wrapper around JAPL's interpreter, lexer and parser"""
|
||||||
|
|
||||||
|
interpreter = Interpreter()
|
||||||
|
resolver = Resolver(interpreter)
|
||||||
|
|
||||||
|
def run(self, file: str):
|
||||||
|
"""Runs a file"""
|
||||||
|
|
||||||
|
if not file:
|
||||||
|
self.repl()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(file) as source_file:
|
||||||
|
source_code = source_file.read()
|
||||||
|
lexer = Lexer(source_code)
|
||||||
|
tokens = lexer.lex()
|
||||||
|
parser = Parser(tokens)
|
||||||
|
ast = parser.parse()
|
||||||
|
self.resolver.resolve(ast)
|
||||||
|
self.interpreter.interpret(ast)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: '{file}', no such file or directory")
|
||||||
|
except PermissionError:
|
||||||
|
print(f"Error' '{file}', permission denied")
|
||||||
|
except JAPLError as err:
|
||||||
|
if len(err.args) == 2:
|
||||||
|
token, message = err.args
|
||||||
|
print(f"An exception occurred at line {token.line}, file '{file}' at '{token.lexeme}': {message}")
|
||||||
|
else:
|
||||||
|
print(f"An exception occurred, details below\n\n{type(err).__name__}: {err}")
|
||||||
|
|
||||||
|
def repl(self):
|
||||||
|
"""Starts an interactive REPL"""
|
||||||
|
|
||||||
|
print("[JAPL 0.1.1 - Interactive REPL]")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
source = input(">>> ")
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
print()
|
||||||
|
return
|
||||||
|
if not source:
|
||||||
|
continue
|
||||||
|
lexer = Lexer(source)
|
||||||
|
try:
|
||||||
|
tokens = lexer.lex()
|
||||||
|
ast = Parser(tokens).parse()
|
||||||
|
self.resolver.resolve(ast)
|
||||||
|
result = self.interpreter.interpret(ast)
|
||||||
|
except ParseError as err:
|
||||||
|
if len(err.args) == 2:
|
||||||
|
token, message = err.args
|
||||||
|
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||||
|
else:
|
||||||
|
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
|
||||||
|
except JAPLError as error:
|
||||||
|
if len(error.args) == 2:
|
||||||
|
token, message = error.args
|
||||||
|
print(f"An exception occurred at line {token.line}, file 'stdin' at '{token.lexeme}': {message}")
|
||||||
|
else:
|
||||||
|
print(f"An exception occurred, details below\n\n{type(error).__name__}: {error}")
|
68
README.md
68
README.md
|
@ -1,2 +1,66 @@
|
||||||
# japl-python
|
# japl
|
||||||
A POC (proof of concept) of the JAPL language written in Python 3
|
JAPL is an interpreted, dynamically-typed, garbage-collected and minimalistic programming language with C- and Java-like syntax.
|
||||||
|
|
||||||
|
# J.. what?
|
||||||
|
|
||||||
|
You may wonder what's the meaning of JAPL: well, it turns out to be an acronym
|
||||||
|
for __Just Another Programming Language__, but beware! Despite the name, the pronounciation is actually the same as "JPL".
|
||||||
|
|
||||||
|
## Some backstory
|
||||||
|
|
||||||
|
JAPL is born thanks to the amazing work of Bob Nystrom that wrote a book available completely for free
|
||||||
|
at [this](https://craftinginterpreters.com) link, where he describes the implementation of a simple language called Lox.
|
||||||
|
|
||||||
|
|
||||||
|
### What has been (or will be) added from Lox
|
||||||
|
|
||||||
|
- Possibility to delete variables with the `del` statement (Currently being reworked)
|
||||||
|
- `break` statement
|
||||||
|
- `continue` statement
|
||||||
|
- Multi-line comments (`/* like this */`)
|
||||||
|
- Nested comments
|
||||||
|
- Modulo division (`%`) and exponentiation (`**`)
|
||||||
|
- `OP_CONSTANT_LONG` is implemented
|
||||||
|
- Differentiation between integers and floating point numbers
|
||||||
|
- `inf` and `nan` types
|
||||||
|
- Possibility to have more than 255 locals in scope at any given time
|
||||||
|
- String slicing, with start:end syntax as well
|
||||||
|
- Strings are not interned (may change in the future)
|
||||||
|
- All entities are actually objects, even builtins
|
||||||
|
- Bitwise operators (AND, OR, XOR, NOT)
|
||||||
|
- Functions default and keyword arguments (__WIP__)
|
||||||
|
- A proper import system (__Coming soon__)
|
||||||
|
- Native asynchronous (`await`/`async fun`) support (__Coming soon__)
|
||||||
|
- Multiple inheritance (__Coming Soon__)
|
||||||
|
- Bytecode optimizations such as constant folding and stack caching (__Coming Soon__)
|
||||||
|
- Arbitrary-precision arithmetic (__Coming soon__)
|
||||||
|
- Generators (__Coming soon__)
|
||||||
|
- A standard library with collections, I/O utilities, scientific modules, etc (__Coming soon__)
|
||||||
|
- Multithreading and multiprocessing support with a global VM Lock like CPython (__Coming soon__)
|
||||||
|
- Multiple GC implementations which can be chosen at runtime or via CLI: bare refcount, refcount + generational GC, M&S (__Coming soon__)
|
||||||
|
- Exceptions (__Coming soon__)
|
||||||
|
- Optional JIT Compilation (__Coming soon__)
|
||||||
|
- Some syntax changes (maybe), e.g. get rid of semicolons
|
||||||
|
- Prototypes based system instead of classes (maybe)
|
||||||
|
|
||||||
|
Other than that, JAPL features closures, function definitions, classes, inheritance and static scoping. You can check
|
||||||
|
the provided example `.jpl` files in the repo to find out more about its syntax.
|
||||||
|
|
||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
This project is currently a WIP (Work in Progress) and is not optimized nor complete.
|
||||||
|
The first version of the interpreter is written in Python, but a bytecode stack-based VM written in nim is being developed right now.
|
||||||
|
|
||||||
|
Also, the design of the language may change at any moment and all the source inside this repo
|
||||||
|
is alpha code quality, for now.
|
||||||
|
|
||||||
|
For other useful information, check the LICENSE file in this repo.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
If you want to contribute, feel free to send a PR!
|
||||||
|
|
||||||
|
Right now there are some major issues with the virtual machine which need to be addressed
|
||||||
|
before the development can proceed, and some help is ~~desperately needed~~ greatly appreciated!
|
||||||
|
|
||||||
|
You can also contact me using the information available [here](https://github.com/nocturn9x)
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Example file to test JAPL's syntax
|
||||||
|
|
||||||
|
// Mathematical expressions
|
||||||
|
|
||||||
|
2 + 2;
|
||||||
|
-1 * 6;
|
||||||
|
3 * (9 / 2); // Parentheses for grouping
|
||||||
|
8 % 2; // Modulo division
|
||||||
|
6 ** 9; // Exponentiation
|
||||||
|
|
||||||
|
// Variable definition and assignment
|
||||||
|
|
||||||
|
var name = "bob"; // Dynamically typed
|
||||||
|
name = "joe"; // Can only be assigned if it's defined
|
||||||
|
del name; // Delete a variable
|
||||||
|
var foo; // Unitialized variables are equal to nil
|
||||||
|
|
||||||
|
// Scoping
|
||||||
|
|
||||||
|
var a = "global";
|
||||||
|
var b = "global1";
|
||||||
|
{ // open a new scope
|
||||||
|
var b = "local"; // Shadow the global variable
|
||||||
|
print a; // This falls back to the global scope
|
||||||
|
print b;
|
||||||
|
}
|
||||||
|
print a;
|
||||||
|
print b; // The outer scope isn't affected
|
||||||
|
|
||||||
|
/*
|
||||||
|
A multiline comment
|
||||||
|
yay!
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Control flow statements
|
||||||
|
|
||||||
|
var n = 0;
|
||||||
|
while (n <= 10) { // While loops
|
||||||
|
if (n <= 5) { // If statements
|
||||||
|
print n;
|
||||||
|
}
|
||||||
|
n = n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i = i + 1) { // For loops
|
||||||
|
print i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
print clock(); // Function calls
|
||||||
|
|
||||||
|
fun count(n) { // Function definitions
|
||||||
|
if (n > 1) count(n - 1); // Recursion works
|
||||||
|
print n;
|
||||||
|
}
|
||||||
|
|
||||||
|
count(3);
|
||||||
|
|
||||||
|
// Closures work too!
|
||||||
|
|
||||||
|
var a = "global";
|
||||||
|
{
|
||||||
|
fun showA() {
|
||||||
|
print a;
|
||||||
|
}
|
||||||
|
|
||||||
|
showA();
|
||||||
|
var a = "block";
|
||||||
|
showA();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested functions
|
||||||
|
|
||||||
|
fun makeCounter() {
|
||||||
|
var i = 0;
|
||||||
|
fun count() {
|
||||||
|
i = i + 1;
|
||||||
|
print i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
var counter = makeCounter();
|
||||||
|
counter(); // "1".
|
||||||
|
counter(); // "2".
|
||||||
|
|
||||||
|
|
||||||
|
// Classes
|
||||||
|
|
||||||
|
class Person {
|
||||||
|
|
||||||
|
init(name) { // Class initializer
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
greet() { // Methods don't use the 'fun' keyword!
|
||||||
|
print "Hello, " + this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bob = Person("Bob"); // Object creation
|
||||||
|
bob.greet(); // Prints Hello, Bob
|
||||||
|
var greetbob = bob.greet; // Functions and methods are first-class objects! (classes are too)
|
||||||
|
greetbob();
|
||||||
|
|
||||||
|
|
||||||
|
class Male < Person { // Male inherits from person
|
||||||
|
|
||||||
|
init(name) {
|
||||||
|
super.init(name); // Inherits constructor behavior
|
||||||
|
this.sex = "male";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
greet() {
|
||||||
|
super.greet(); // Inherits behavior from superclass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mark = Male("Mark");
|
||||||
|
mark.greet();
|
|
@ -0,0 +1,19 @@
|
||||||
|
fun factorial(n) {
|
||||||
|
// Computes the factorial of n iteratively
|
||||||
|
|
||||||
|
if (n < 0) return nil;
|
||||||
|
if (n == 1 or n == 2) return n;
|
||||||
|
var fact = 1;
|
||||||
|
for (var i = 1; i < n + 1; i = i + 1) {
|
||||||
|
fact = fact * i;
|
||||||
|
}
|
||||||
|
return fact;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var start = clock();
|
||||||
|
print "Computing factorials from 0 to 200";
|
||||||
|
for (var i = 0; i < 201; i = i + 1) factorial(i);
|
||||||
|
var result = clock() - start;
|
||||||
|
print "Computed factorials in " + stringify(result) + " seconds";
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// A recursive implementation of the fibonacci sequence
|
||||||
|
|
||||||
|
fun fib(n) {
|
||||||
|
if (n <= 1) return n;
|
||||||
|
return fib(n - 2) + fib(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var start = clock();
|
||||||
|
fib(30);
|
||||||
|
var end = clock() - start;
|
||||||
|
print(end);
|
|
@ -0,0 +1,9 @@
|
||||||
|
import sys
|
||||||
|
from JAPL.wrapper import JAPL
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
JAPL().repl()
|
||||||
|
else:
|
||||||
|
JAPL().run(sys.argv[1])
|
|
@ -0,0 +1 @@
|
||||||
|
singledispatchmethod
|
|
@ -0,0 +1,16 @@
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name="JAPL - Just another programming language",
|
||||||
|
version="0.1.1",
|
||||||
|
author="nocturn9x",
|
||||||
|
author_email="nocturn9x@intellivoid.net",
|
||||||
|
description="The JAPL programming language",
|
||||||
|
packages=setuptools.find_packages(),
|
||||||
|
classifiers=[
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: OS Independent",
|
||||||
|
],
|
||||||
|
python_requires='>=3.6',
|
||||||
|
)
|
Loading…
Reference in New Issue