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
|
||||
from typing import List
|
||||
from .types.callable import Callable
|
||||
from .types.japlclass import JAPLClass
|
||||
from .types.instance import JAPLInstance
|
||||
from .meta.environment import Environment
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
||||
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.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
|
||||
|
||||
|
||||
class Interpreter(Expression.Visitor, Statement.Visitor):
|
||||
"""
|
||||
An interpreter for the JAPL
|
||||
programming language
|
||||
"""
|
||||
|
||||
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
|
||||
TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
|
||||
TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
|
||||
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
|
||||
|
||||
def __init__(self):
|
||||
"""Object constructor"""
|
||||
|
||||
self.environment = Environment()
|
||||
self.locals = {}
|
||||
self.globals = self.environment
|
||||
self.globals.define("clock", Clock())
|
||||
self.globals.define("type", Type())
|
||||
self.globals.define("truthy", Truthy())
|
||||
self.globals.define("stringify", Stringify())
|
||||
self.globals.define("print", PrintFunction())
|
||||
self.globals.define("isinstance", IsInstance())
|
||||
self.globals.define("issuperclass", IsSuperclass())
|
||||
self.globals.define("issubclass", IsSubclass())
|
||||
|
||||
def number_operand(self, op, operand):
|
||||
"""
|
||||
An helper method to check if the operand
|
||||
to a unary operator is a number
|
||||
"""
|
||||
|
||||
if isinstance(operand, (int, float)):
|
||||
return
|
||||
raise JAPLError(op,
|
||||
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
|
||||
|
||||
def compatible_operands(self, op, left, right):
|
||||
"""
|
||||
Helper method to check types when doing binary
|
||||
operations
|
||||
"""
|
||||
|
||||
if op.kind == TokenType.SLASH and right == 0:
|
||||
raise JAPLError(op, "Cannot divide by 0")
|
||||
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
|
||||
if op.kind not in (TokenType.DEQ, TokenType.NE):
|
||||
raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
return
|
||||
elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
||||
return
|
||||
elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
|
||||
if isinstance(left, str) and isinstance(right, str):
|
||||
return
|
||||
elif isinstance(left, str) and isinstance(right, int):
|
||||
return
|
||||
elif isinstance(left, int) and isinstance(right, str):
|
||||
return
|
||||
raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
|
||||
def visit_literal(self, expr: Literal):
|
||||
"""
|
||||
Visits a Literal node in the Abstract Syntax Tree,
|
||||
returning its value to the visitor
|
||||
"""
|
||||
|
||||
return expr.value
|
||||
|
||||
def visit_logical(self, expr: Logical):
|
||||
"""Visits a logical node"""
|
||||
|
||||
left = self.eval(expr.left)
|
||||
if expr.operator.kind == TokenType.OR:
|
||||
if bool(left):
|
||||
return left
|
||||
elif not bool(left):
|
||||
return self.eval(expr.right)
|
||||
return self.eval(expr.right)
|
||||
|
||||
def eval(self, expr: Expression):
|
||||
"""
|
||||
Evaluates an expression by calling its accept()
|
||||
method and passing self to it. This mechanism is known
|
||||
as the 'Visitor Pattern': the expression object will
|
||||
later call the interpreter's appropriate method to
|
||||
evaluate itself
|
||||
"""
|
||||
|
||||
return expr.accept(self)
|
||||
|
||||
def visit_grouping(self, grouping: Grouping):
|
||||
"""
|
||||
Visits a Grouping node in the Abstract Syntax Tree,
|
||||
recursively evaluating its subexpressions
|
||||
"""
|
||||
|
||||
return self.eval(grouping.expr)
|
||||
|
||||
def visit_unary(self, expr: Unary):
|
||||
"""
|
||||
Visits a Unary node in the Abstract Syntax Teee,
|
||||
returning the negation of the given object, if
|
||||
the operation is supported
|
||||
"""
|
||||
|
||||
right = self.eval(expr.right)
|
||||
self.number_operand(expr.operator, right)
|
||||
if expr.operator.kind == TokenType.NEG:
|
||||
return not right
|
||||
return -right
|
||||
|
||||
def visit_binary(self, expr: Binary):
|
||||
"""
|
||||
Visits a Binary node in the Abstract Syntax Tree,
|
||||
recursively evaulating both operands first and then
|
||||
performing the operation specified by the operator
|
||||
"""
|
||||
|
||||
left = self.eval(expr.left)
|
||||
right = self.eval(expr.right)
|
||||
self.compatible_operands(expr.operator, left, right)
|
||||
return self.OPS[expr.operator.kind](left, right)
|
||||
|
||||
def visit_statement_expr(self, stmt: StatementExpr):
|
||||
"""
|
||||
Visits an expression statement and evaluates it
|
||||
"""
|
||||
|
||||
self.eval(stmt.expression)
|
||||
|
||||
def visit_if(self, statement: If):
|
||||
"""
|
||||
Visits an If node and evaluates it
|
||||
"""
|
||||
|
||||
if self.eval(statement.condition):
|
||||
self.exec(statement.then_branch)
|
||||
elif statement.else_branch:
|
||||
self.exec(statement.else_branch)
|
||||
|
||||
def visit_class(self, stmt: Class):
|
||||
"""Visits a class declaration"""
|
||||
|
||||
superclass = None
|
||||
if stmt.superclass:
|
||||
superclass = self.eval(stmt.superclass)
|
||||
if not isinstance(superclass, JAPLClass):
|
||||
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
|
||||
self.environment.define(stmt.name.lexeme, None)
|
||||
if superclass:
|
||||
environment = Environment(self.environment)
|
||||
environment.define("super", superclass)
|
||||
else:
|
||||
environment = self.environment
|
||||
methods = {}
|
||||
for method in stmt.methods:
|
||||
func = JAPLFunction(method, environment)
|
||||
methods[method.name.lexeme] = func
|
||||
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
|
||||
if superclass:
|
||||
self.environment = environment.enclosing
|
||||
self.environment.assign(stmt.name, klass)
|
||||
|
||||
def visit_while(self, statement: While):
|
||||
"""
|
||||
Visits a while node and executes it
|
||||
"""
|
||||
|
||||
while self.eval(statement.condition):
|
||||
try:
|
||||
self.exec(statement.body)
|
||||
except BreakException:
|
||||
break
|
||||
|
||||
def visit_var_stmt(self, stmt: Var):
|
||||
"""
|
||||
Visits a var statement
|
||||
"""
|
||||
|
||||
val = None
|
||||
if stmt.init:
|
||||
val = self.eval(stmt.init)
|
||||
self.environment.define(stmt.name.lexeme, val)
|
||||
|
||||
def lookup(self, name, expr: Expression):
|
||||
"""
|
||||
Performs name lookups in the closest scope
|
||||
"""
|
||||
|
||||
distance = self.locals.get(expr)
|
||||
if distance is not None:
|
||||
return self.environment.get_at(distance, name.lexeme)
|
||||
else:
|
||||
return self.globals.get(name)
|
||||
|
||||
def visit_var_expr(self, expr: Variable):
|
||||
"""
|
||||
Visits a var expression
|
||||
"""
|
||||
|
||||
return self.lookup(expr.name, expr)
|
||||
|
||||
def visit_del(self, stmt: Del):
|
||||
"""
|
||||
Visits a del expression
|
||||
"""
|
||||
|
||||
return self.environment.delete(stmt.name)
|
||||
|
||||
def visit_assign(self, stmt: Assignment):
|
||||
"""
|
||||
Visits an assignment expression
|
||||
"""
|
||||
|
||||
right = self.eval(stmt.value)
|
||||
distance = self.locals.get(stmt)
|
||||
if distance is not None:
|
||||
self.environment.assign_at(distance, stmt.name, right)
|
||||
else:
|
||||
self.globals.assign(stmt.name, right)
|
||||
return right
|
||||
|
||||
def visit_block(self, stmt: Block):
|
||||
"""
|
||||
Visits a new scope block
|
||||
"""
|
||||
|
||||
return self.execute_block(stmt.statements, Environment(self.environment))
|
||||
|
||||
def visit_break(self, stmt: Break):
|
||||
"""
|
||||
Visits a break statement
|
||||
"""
|
||||
|
||||
raise BreakException()
|
||||
|
||||
def visit_call_expr(self, expr: Call):
|
||||
"""
|
||||
Visits a call expression
|
||||
"""
|
||||
|
||||
callee = self.eval(expr.callee)
|
||||
if not isinstance(callee, Callable):
|
||||
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
|
||||
arguments = []
|
||||
for argument in expr.arguments:
|
||||
arguments.append(self.eval(argument))
|
||||
function = callee
|
||||
if function.arity != len(arguments):
|
||||
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
|
||||
return function.call(self, arguments)
|
||||
|
||||
def execute_block(self, statements: List[Statement], scope: Environment):
|
||||
"""
|
||||
Executes a block of statements
|
||||
"""
|
||||
|
||||
prev = self.environment
|
||||
try:
|
||||
self.environment = scope
|
||||
for statement in statements:
|
||||
self.exec(statement)
|
||||
finally:
|
||||
self.environment = prev
|
||||
|
||||
def visit_return(self, statement: Return):
|
||||
"""
|
||||
Visits a return statement
|
||||
"""
|
||||
|
||||
value = None
|
||||
if statement.value:
|
||||
value = self.eval(statement.value)
|
||||
raise ReturnException(value)
|
||||
|
||||
def visit_function(self, statement: Function):
|
||||
"""
|
||||
Visits a function
|
||||
"""
|
||||
|
||||
function = JAPLFunction(statement, self.environment)
|
||||
self.environment.define(statement.name.lexeme, function)
|
||||
|
||||
def visit_get(self, expr: Get):
|
||||
"""Visits property get expressions and evaluates them"""
|
||||
|
||||
obj = self.eval(expr.object)
|
||||
if isinstance(obj, JAPLInstance):
|
||||
return obj.get(expr.name)
|
||||
raise JAPLError(expr.name, "Only instances have properties")
|
||||
|
||||
def visit_set(self, expr: Set):
|
||||
"""Visits property set expressions and evaluates them"""
|
||||
|
||||
obj = self.eval(expr.object)
|
||||
if not isinstance(obj, JAPLInstance):
|
||||
raise JAPLError(expr, "Only instances have fields")
|
||||
value = self.eval(expr.value)
|
||||
obj.set(expr.name, value)
|
||||
|
||||
def visit_this(self, expr):
|
||||
"""Evaluates 'this' expressions"""
|
||||
|
||||
return self.lookup(expr.keyword, expr)
|
||||
|
||||
def visit_super(self, expr):
|
||||
"""Evaluates 'super' expressions"""
|
||||
|
||||
distance = self.locals.get(expr)
|
||||
superclass = self.environment.get_at(distance, "super")
|
||||
instance = self.environment.get_at(distance - 1, "this")
|
||||
meth = superclass.get_method(expr.method.lexeme)
|
||||
if not meth:
|
||||
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
|
||||
return meth.bind(instance)
|
||||
|
||||
def exec(self, statement: Statement):
|
||||
"""
|
||||
Executes a statement
|
||||
"""
|
||||
|
||||
statement.accept(self)
|
||||
|
||||
def interpret(self, statements: List[Statement]):
|
||||
"""
|
||||
Executes a JAPL program
|
||||
"""
|
||||
|
||||
for statement in statements:
|
||||
self.exec(statement)
|
||||
|
||||
def resolve(self, expr: Expression, depth: int):
|
||||
"""
|
||||
Stores the result of the name resolution: this
|
||||
info will be used later to know exactly in which
|
||||
environment to look up a given variable
|
||||
"""
|
||||
|
||||
self.locals[expr] = depth # How many environments to skip!
|
||||
import operator
|
||||
from typing import List
|
||||
from .types.callable import Callable
|
||||
from .types.japlclass import JAPLClass
|
||||
from .types.instance import JAPLInstance
|
||||
from .meta.environment import Environment
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
||||
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.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
|
||||
|
||||
|
||||
class Interpreter(Expression.Visitor, Statement.Visitor):
|
||||
"""
|
||||
An interpreter for the JAPL
|
||||
programming language
|
||||
"""
|
||||
|
||||
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
|
||||
TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
|
||||
TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
|
||||
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
|
||||
|
||||
def __init__(self):
|
||||
"""Object constructor"""
|
||||
|
||||
self.environment = Environment()
|
||||
self.locals = {}
|
||||
self.globals = self.environment
|
||||
self.globals.define("clock", Clock())
|
||||
self.globals.define("type", Type())
|
||||
self.globals.define("truthy", Truthy())
|
||||
self.globals.define("stringify", Stringify())
|
||||
self.globals.define("print", PrintFunction())
|
||||
self.globals.define("isinstance", IsInstance())
|
||||
self.globals.define("issuperclass", IsSuperclass())
|
||||
self.globals.define("issubclass", IsSubclass())
|
||||
|
||||
def number_operand(self, op, operand):
|
||||
"""
|
||||
An helper method to check if the operand
|
||||
to a unary operator is a number
|
||||
"""
|
||||
|
||||
if isinstance(operand, (int, float)):
|
||||
return
|
||||
raise JAPLError(op,
|
||||
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
|
||||
|
||||
def compatible_operands(self, op, left, right):
|
||||
"""
|
||||
Helper method to check types when doing binary
|
||||
operations
|
||||
"""
|
||||
|
||||
if op.kind == TokenType.SLASH and right == 0:
|
||||
raise JAPLError(op, "Cannot divide by 0")
|
||||
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
|
||||
if op.kind not in (TokenType.DEQ, TokenType.NE):
|
||||
raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
return
|
||||
elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
||||
return
|
||||
elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
|
||||
if isinstance(left, str) and isinstance(right, str):
|
||||
return
|
||||
elif isinstance(left, str) and isinstance(right, int):
|
||||
return
|
||||
elif isinstance(left, int) and isinstance(right, str):
|
||||
return
|
||||
raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
|
||||
def visit_literal(self, expr: Literal):
|
||||
"""
|
||||
Visits a Literal node in the Abstract Syntax Tree,
|
||||
returning its value to the visitor
|
||||
"""
|
||||
|
||||
return expr.value
|
||||
|
||||
def visit_logical(self, expr: Logical):
|
||||
"""Visits a logical node"""
|
||||
|
||||
left = self.eval(expr.left)
|
||||
if expr.operator.kind == TokenType.OR:
|
||||
if bool(left):
|
||||
return left
|
||||
elif not bool(left):
|
||||
return self.eval(expr.right)
|
||||
return self.eval(expr.right)
|
||||
|
||||
def eval(self, expr: Expression):
|
||||
"""
|
||||
Evaluates an expression by calling its accept()
|
||||
method and passing self to it. This mechanism is known
|
||||
as the 'Visitor Pattern': the expression object will
|
||||
later call the interpreter's appropriate method to
|
||||
evaluate itself
|
||||
"""
|
||||
|
||||
return expr.accept(self)
|
||||
|
||||
def visit_grouping(self, grouping: Grouping):
|
||||
"""
|
||||
Visits a Grouping node in the Abstract Syntax Tree,
|
||||
recursively evaluating its subexpressions
|
||||
"""
|
||||
|
||||
return self.eval(grouping.expr)
|
||||
|
||||
def visit_unary(self, expr: Unary):
|
||||
"""
|
||||
Visits a Unary node in the Abstract Syntax Teee,
|
||||
returning the negation of the given object, if
|
||||
the operation is supported
|
||||
"""
|
||||
|
||||
right = self.eval(expr.right)
|
||||
self.number_operand(expr.operator, right)
|
||||
if expr.operator.kind == TokenType.NEG:
|
||||
return not right
|
||||
return -right
|
||||
|
||||
def visit_binary(self, expr: Binary):
|
||||
"""
|
||||
Visits a Binary node in the Abstract Syntax Tree,
|
||||
recursively evaulating both operands first and then
|
||||
performing the operation specified by the operator
|
||||
"""
|
||||
|
||||
left = self.eval(expr.left)
|
||||
right = self.eval(expr.right)
|
||||
self.compatible_operands(expr.operator, left, right)
|
||||
return self.OPS[expr.operator.kind](left, right)
|
||||
|
||||
def visit_statement_expr(self, stmt: StatementExpr):
|
||||
"""
|
||||
Visits an expression statement and evaluates it
|
||||
"""
|
||||
|
||||
self.eval(stmt.expression)
|
||||
|
||||
def visit_if(self, statement: If):
|
||||
"""
|
||||
Visits an If node and evaluates it
|
||||
"""
|
||||
|
||||
if self.eval(statement.condition):
|
||||
self.exec(statement.then_branch)
|
||||
elif statement.else_branch:
|
||||
self.exec(statement.else_branch)
|
||||
|
||||
def visit_class(self, stmt: Class):
|
||||
"""Visits a class declaration"""
|
||||
|
||||
superclass = None
|
||||
if stmt.superclass:
|
||||
superclass = self.eval(stmt.superclass)
|
||||
if not isinstance(superclass, JAPLClass):
|
||||
raise JAPLError(stmt.superclass.name, "Superclass must be a class")
|
||||
self.environment.define(stmt.name.lexeme, None)
|
||||
if superclass:
|
||||
environment = Environment(self.environment)
|
||||
environment.define("super", superclass)
|
||||
else:
|
||||
environment = self.environment
|
||||
methods = {}
|
||||
for method in stmt.methods:
|
||||
func = JAPLFunction(method, environment)
|
||||
methods[method.name.lexeme] = func
|
||||
klass = JAPLClass(stmt.name.lexeme, methods, superclass)
|
||||
if superclass:
|
||||
self.environment = environment.enclosing
|
||||
self.environment.assign(stmt.name, klass)
|
||||
|
||||
def visit_while(self, statement: While):
|
||||
"""
|
||||
Visits a while node and executes it
|
||||
"""
|
||||
|
||||
while self.eval(statement.condition):
|
||||
try:
|
||||
self.exec(statement.body)
|
||||
except BreakException:
|
||||
break
|
||||
|
||||
def visit_var_stmt(self, stmt: Var):
|
||||
"""
|
||||
Visits a var statement
|
||||
"""
|
||||
|
||||
val = None
|
||||
if stmt.init:
|
||||
val = self.eval(stmt.init)
|
||||
self.environment.define(stmt.name.lexeme, val)
|
||||
|
||||
def lookup(self, name, expr: Expression):
|
||||
"""
|
||||
Performs name lookups in the closest scope
|
||||
"""
|
||||
|
||||
distance = self.locals.get(expr)
|
||||
if distance is not None:
|
||||
return self.environment.get_at(distance, name.lexeme)
|
||||
else:
|
||||
return self.globals.get(name)
|
||||
|
||||
def visit_var_expr(self, expr: Variable):
|
||||
"""
|
||||
Visits a var expression
|
||||
"""
|
||||
|
||||
return self.lookup(expr.name, expr)
|
||||
|
||||
def visit_del(self, stmt: Del):
|
||||
"""
|
||||
Visits a del expression
|
||||
"""
|
||||
|
||||
return self.environment.delete(stmt.name)
|
||||
|
||||
def visit_assign(self, stmt: Assignment):
|
||||
"""
|
||||
Visits an assignment expression
|
||||
"""
|
||||
|
||||
right = self.eval(stmt.value)
|
||||
distance = self.locals.get(stmt)
|
||||
if distance is not None:
|
||||
self.environment.assign_at(distance, stmt.name, right)
|
||||
else:
|
||||
self.globals.assign(stmt.name, right)
|
||||
return right
|
||||
|
||||
def visit_block(self, stmt: Block):
|
||||
"""
|
||||
Visits a new scope block
|
||||
"""
|
||||
|
||||
return self.execute_block(stmt.statements, Environment(self.environment))
|
||||
|
||||
def visit_break(self, stmt: Break):
|
||||
"""
|
||||
Visits a break statement
|
||||
"""
|
||||
|
||||
raise BreakException()
|
||||
|
||||
def visit_call_expr(self, expr: Call):
|
||||
"""
|
||||
Visits a call expression
|
||||
"""
|
||||
|
||||
callee = self.eval(expr.callee)
|
||||
if not isinstance(callee, Callable):
|
||||
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
|
||||
arguments = []
|
||||
for argument in expr.arguments:
|
||||
arguments.append(self.eval(argument))
|
||||
function = callee
|
||||
if function.arity != len(arguments):
|
||||
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
|
||||
return function.call(self, arguments)
|
||||
|
||||
def execute_block(self, statements: List[Statement], scope: Environment):
|
||||
"""
|
||||
Executes a block of statements
|
||||
"""
|
||||
|
||||
prev = self.environment
|
||||
try:
|
||||
self.environment = scope
|
||||
for statement in statements:
|
||||
self.exec(statement)
|
||||
finally:
|
||||
self.environment = prev
|
||||
|
||||
def visit_return(self, statement: Return):
|
||||
"""
|
||||
Visits a return statement
|
||||
"""
|
||||
|
||||
value = None
|
||||
if statement.value:
|
||||
value = self.eval(statement.value)
|
||||
raise ReturnException(value)
|
||||
|
||||
def visit_function(self, statement: Function):
|
||||
"""
|
||||
Visits a function
|
||||
"""
|
||||
|
||||
function = JAPLFunction(statement, self.environment)
|
||||
self.environment.define(statement.name.lexeme, function)
|
||||
|
||||
def visit_get(self, expr: Get):
|
||||
"""Visits property get expressions and evaluates them"""
|
||||
|
||||
obj = self.eval(expr.object)
|
||||
if isinstance(obj, JAPLInstance):
|
||||
return obj.get(expr.name)
|
||||
raise JAPLError(expr.name, "Only instances have properties")
|
||||
|
||||
def visit_set(self, expr: Set):
|
||||
"""Visits property set expressions and evaluates them"""
|
||||
|
||||
obj = self.eval(expr.object)
|
||||
if not isinstance(obj, JAPLInstance):
|
||||
raise JAPLError(expr, "Only instances have fields")
|
||||
value = self.eval(expr.value)
|
||||
obj.set(expr.name, value)
|
||||
|
||||
def visit_this(self, expr):
|
||||
"""Evaluates 'this' expressions"""
|
||||
|
||||
return self.lookup(expr.keyword, expr)
|
||||
|
||||
def visit_super(self, expr):
|
||||
"""Evaluates 'super' expressions"""
|
||||
|
||||
distance = self.locals.get(expr)
|
||||
superclass = self.environment.get_at(distance, "super")
|
||||
instance = self.environment.get_at(distance - 1, "this")
|
||||
meth = superclass.get_method(expr.method.lexeme)
|
||||
if not meth:
|
||||
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
|
||||
return meth.bind(instance)
|
||||
|
||||
def exec(self, statement: Statement):
|
||||
"""
|
||||
Executes a statement
|
||||
"""
|
||||
|
||||
statement.accept(self)
|
||||
|
||||
def interpret(self, statements: List[Statement]):
|
||||
"""
|
||||
Executes a JAPL program
|
||||
"""
|
||||
|
||||
for statement in statements:
|
||||
self.exec(statement)
|
||||
|
||||
def resolve(self, expr: Expression, depth: int):
|
||||
"""
|
||||
Stores the result of the name resolution: this
|
||||
info will be used later to know exactly in which
|
||||
environment to look up a given variable
|
||||
"""
|
||||
|
||||
self.locals[expr] = depth # How many environments to skip!
|
||||
|
|
424
JAPL/lexer.py
424
JAPL/lexer.py
|
@ -1,212 +1,212 @@
|
|||
from .meta.tokenobject import Token
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import ParseError
|
||||
from typing import List
|
||||
|
||||
|
||||
class Lexer(object):
|
||||
"""
|
||||
A simple tokenizer for the JAPL programming
|
||||
language, scans a input source file and
|
||||
produces a list of tokens. Some errors
|
||||
are caught here as well.
|
||||
"""
|
||||
|
||||
TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
|
||||
"{": TokenType.LB, "}": TokenType.RB,
|
||||
".": TokenType.DOT, ",": TokenType.COMMA,
|
||||
"-": TokenType.MINUS, "+": TokenType.PLUS,
|
||||
";": TokenType.SEMICOLON, "*": TokenType.STAR,
|
||||
">": TokenType.GT, "<": TokenType.LT,
|
||||
"=": TokenType.EQ, "!": TokenType.NEG,
|
||||
"/": TokenType.SLASH, "%": TokenType.MOD}
|
||||
|
||||
RESERVED = {"or": TokenType.OR, "and": TokenType.AND,
|
||||
"class": TokenType.CLASS, "fun": TokenType.FUN,
|
||||
"if": TokenType.IF, "else": TokenType.ELSE,
|
||||
"for": TokenType.FOR, "while": TokenType.WHILE,
|
||||
"var": TokenType.VAR, "nil": TokenType.NIL,
|
||||
"true": TokenType.TRUE, "false": TokenType.FALSE,
|
||||
"return": TokenType.RETURN,
|
||||
"this": TokenType.THIS, "super": TokenType.SUPER,
|
||||
"del": TokenType.DEL, "break": TokenType.BREAK}
|
||||
|
||||
def __init__(self, source: str):
|
||||
"""Object constructor"""
|
||||
|
||||
self.source = source
|
||||
self.tokens: List[Token] = []
|
||||
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.current: int = 0 # The position of the current character being lexed
|
||||
|
||||
def step(self) -> str:
|
||||
"""
|
||||
'Steps' one character in the source code and returns it
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return ""
|
||||
self.current += 1
|
||||
return self.source[self.current - 1]
|
||||
|
||||
def peek(self) -> str:
|
||||
"""
|
||||
Returns the current character without consuming it
|
||||
or an empty string if all text has been consumed
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return ""
|
||||
return self.source[self.current]
|
||||
|
||||
def peek_next(self) -> str:
|
||||
"""
|
||||
Returns the next character after self.current
|
||||
or an empty string if the input has been consumed
|
||||
"""
|
||||
|
||||
if self.current + 1 >= len(self.source):
|
||||
return ""
|
||||
return self.source[self.current + 1]
|
||||
|
||||
def string(self, delimiter: str):
|
||||
"""Parses a string literal"""
|
||||
|
||||
while self.peek() != delimiter and not self.done():
|
||||
if self.peek() == "\n":
|
||||
self.line += 1
|
||||
self.step()
|
||||
if self.done():
|
||||
raise ParseError(f"unterminated string literal at line {self.line}")
|
||||
self.step() # Consume the '"'
|
||||
value = self.source[self.start + 1:self.current - 1] # Get the actual string
|
||||
self.tokens.append(self.create_token(TokenType.STR, value))
|
||||
|
||||
def number(self):
|
||||
"""Parses a number literal"""
|
||||
|
||||
convert = int
|
||||
while self.peek().isdigit():
|
||||
self.step()
|
||||
if self.peek() == ".":
|
||||
self.step() # Consume the "."
|
||||
while self.peek().isdigit():
|
||||
self.step()
|
||||
convert = float
|
||||
self.tokens.append(self.create_token(TokenType.NUM,
|
||||
convert(self.source[self.start:self.current])))
|
||||
|
||||
def identifier(self):
|
||||
"""Parses identifiers and reserved keywords"""
|
||||
|
||||
while self.peek().isalnum() or self.is_identifier(self.peek()):
|
||||
self.step()
|
||||
kind = TokenType.ID
|
||||
value = self.source[self.start:self.current]
|
||||
if self.RESERVED.get(value, None):
|
||||
kind = self.RESERVED[value]
|
||||
self.tokens.append(self.create_token(kind))
|
||||
|
||||
def comment(self):
|
||||
"""Handles multi-line comments"""
|
||||
|
||||
closed = False
|
||||
while not self.done():
|
||||
end = self.peek() + self.peek_next()
|
||||
if end == "/*": # Nested comments
|
||||
self.step()
|
||||
self.step()
|
||||
self.comment()
|
||||
elif end == "*/":
|
||||
closed = True
|
||||
self.step() # Consume the two ends
|
||||
self.step()
|
||||
break
|
||||
self.step()
|
||||
if self.done() and not closed:
|
||||
raise ParseError(f"Unexpected EOF at line {self.line}")
|
||||
|
||||
def match(self, char: str) -> bool:
|
||||
"""
|
||||
Returns True if the current character in self.source matches
|
||||
the given character
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return False
|
||||
elif self.source[self.current] != char:
|
||||
return False
|
||||
self.current += 1
|
||||
return True
|
||||
|
||||
def done(self) -> bool:
|
||||
"""
|
||||
Helper method that's used by the lexer
|
||||
to know if all source has been consumed
|
||||
"""
|
||||
|
||||
return self.current >= len(self.source)
|
||||
|
||||
def create_token(self, kind: TokenType, literal: object = None) -> Token:
|
||||
"""
|
||||
Creates and returns a token object
|
||||
"""
|
||||
|
||||
return Token(kind, self.source[self.start:self.current], literal, self.line)
|
||||
|
||||
def is_identifier(self, char: str):
|
||||
"""Returns if a character can be an identifier"""
|
||||
|
||||
if char.isalpha() or char in ("_", ): # More coming soon
|
||||
return True
|
||||
|
||||
def scan_token(self):
|
||||
"""
|
||||
Scans for a single token and adds it to
|
||||
self.tokens
|
||||
"""
|
||||
|
||||
char = self.step()
|
||||
if char in (" ", "\t", "\r"): # Useless characters
|
||||
return
|
||||
elif char == "\n": # New line
|
||||
self.line += 1
|
||||
elif char in ("'", '"'): # A string literal
|
||||
self.string(char)
|
||||
elif char.isdigit():
|
||||
self.number()
|
||||
elif self.is_identifier(char): # Identifier or reserved keyword
|
||||
self.identifier()
|
||||
elif char in self.TOKENS:
|
||||
if char == "/" and self.match("/"):
|
||||
while self.peek() != "\n" and not self.done():
|
||||
self.step() # Who cares about comments?
|
||||
elif char == "/" and self.match("*"):
|
||||
self.comment()
|
||||
elif char == "=" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.DEQ))
|
||||
elif char == ">" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.GE))
|
||||
elif char == "<" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.LE))
|
||||
elif char == "!" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.NE))
|
||||
elif char == "*" and self.match("*"):
|
||||
self.tokens.append(self.create_token(TokenType.POW))
|
||||
else:
|
||||
self.tokens.append(self.create_token(self.TOKENS[char]))
|
||||
else:
|
||||
raise ParseError(f"unexpected character '{char}' at line {self.line}")
|
||||
|
||||
def lex(self) -> List[Token]:
|
||||
"""
|
||||
Performs lexical analysis on self.source
|
||||
and returns a list of tokens
|
||||
"""
|
||||
|
||||
while not self.done():
|
||||
self.start = self.current
|
||||
self.scan_token()
|
||||
self.tokens.append(Token(TokenType.EOF, "", None, self.line))
|
||||
return self.tokens
|
||||
from .meta.tokenobject import Token
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import ParseError
|
||||
from typing import List
|
||||
|
||||
|
||||
class Lexer(object):
|
||||
"""
|
||||
A simple tokenizer for the JAPL programming
|
||||
language, scans a input source file and
|
||||
produces a list of tokens. Some errors
|
||||
are caught here as well.
|
||||
"""
|
||||
|
||||
TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
|
||||
"{": TokenType.LB, "}": TokenType.RB,
|
||||
".": TokenType.DOT, ",": TokenType.COMMA,
|
||||
"-": TokenType.MINUS, "+": TokenType.PLUS,
|
||||
";": TokenType.SEMICOLON, "*": TokenType.STAR,
|
||||
">": TokenType.GT, "<": TokenType.LT,
|
||||
"=": TokenType.EQ, "!": TokenType.NEG,
|
||||
"/": TokenType.SLASH, "%": TokenType.MOD}
|
||||
|
||||
RESERVED = {"or": TokenType.OR, "and": TokenType.AND,
|
||||
"class": TokenType.CLASS, "fun": TokenType.FUN,
|
||||
"if": TokenType.IF, "else": TokenType.ELSE,
|
||||
"for": TokenType.FOR, "while": TokenType.WHILE,
|
||||
"var": TokenType.VAR, "nil": TokenType.NIL,
|
||||
"true": TokenType.TRUE, "false": TokenType.FALSE,
|
||||
"return": TokenType.RETURN,
|
||||
"this": TokenType.THIS, "super": TokenType.SUPER,
|
||||
"del": TokenType.DEL, "break": TokenType.BREAK}
|
||||
|
||||
def __init__(self, source: str):
|
||||
"""Object constructor"""
|
||||
|
||||
self.source = source
|
||||
self.tokens: List[Token] = []
|
||||
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.current: int = 0 # The position of the current character being lexed
|
||||
|
||||
def step(self) -> str:
|
||||
"""
|
||||
'Steps' one character in the source code and returns it
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return ""
|
||||
self.current += 1
|
||||
return self.source[self.current - 1]
|
||||
|
||||
def peek(self) -> str:
|
||||
"""
|
||||
Returns the current character without consuming it
|
||||
or an empty string if all text has been consumed
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return ""
|
||||
return self.source[self.current]
|
||||
|
||||
def peek_next(self) -> str:
|
||||
"""
|
||||
Returns the next character after self.current
|
||||
or an empty string if the input has been consumed
|
||||
"""
|
||||
|
||||
if self.current + 1 >= len(self.source):
|
||||
return ""
|
||||
return self.source[self.current + 1]
|
||||
|
||||
def string(self, delimiter: str):
|
||||
"""Parses a string literal"""
|
||||
|
||||
while self.peek() != delimiter and not self.done():
|
||||
if self.peek() == "\n":
|
||||
self.line += 1
|
||||
self.step()
|
||||
if self.done():
|
||||
raise ParseError(f"unterminated string literal at line {self.line}")
|
||||
self.step() # Consume the '"'
|
||||
value = self.source[self.start + 1:self.current - 1] # Get the actual string
|
||||
self.tokens.append(self.create_token(TokenType.STR, value))
|
||||
|
||||
def number(self):
|
||||
"""Parses a number literal"""
|
||||
|
||||
convert = int
|
||||
while self.peek().isdigit():
|
||||
self.step()
|
||||
if self.peek() == ".":
|
||||
self.step() # Consume the "."
|
||||
while self.peek().isdigit():
|
||||
self.step()
|
||||
convert = float
|
||||
self.tokens.append(self.create_token(TokenType.NUM,
|
||||
convert(self.source[self.start:self.current])))
|
||||
|
||||
def identifier(self):
|
||||
"""Parses identifiers and reserved keywords"""
|
||||
|
||||
while self.peek().isalnum() or self.is_identifier(self.peek()):
|
||||
self.step()
|
||||
kind = TokenType.ID
|
||||
value = self.source[self.start:self.current]
|
||||
if self.RESERVED.get(value, None):
|
||||
kind = self.RESERVED[value]
|
||||
self.tokens.append(self.create_token(kind))
|
||||
|
||||
def comment(self):
|
||||
"""Handles multi-line comments"""
|
||||
|
||||
closed = False
|
||||
while not self.done():
|
||||
end = self.peek() + self.peek_next()
|
||||
if end == "/*": # Nested comments
|
||||
self.step()
|
||||
self.step()
|
||||
self.comment()
|
||||
elif end == "*/":
|
||||
closed = True
|
||||
self.step() # Consume the two ends
|
||||
self.step()
|
||||
break
|
||||
self.step()
|
||||
if self.done() and not closed:
|
||||
raise ParseError(f"Unexpected EOF at line {self.line}")
|
||||
|
||||
def match(self, char: str) -> bool:
|
||||
"""
|
||||
Returns True if the current character in self.source matches
|
||||
the given character
|
||||
"""
|
||||
|
||||
if self.done():
|
||||
return False
|
||||
elif self.source[self.current] != char:
|
||||
return False
|
||||
self.current += 1
|
||||
return True
|
||||
|
||||
def done(self) -> bool:
|
||||
"""
|
||||
Helper method that's used by the lexer
|
||||
to know if all source has been consumed
|
||||
"""
|
||||
|
||||
return self.current >= len(self.source)
|
||||
|
||||
def create_token(self, kind: TokenType, literal: object = None) -> Token:
|
||||
"""
|
||||
Creates and returns a token object
|
||||
"""
|
||||
|
||||
return Token(kind, self.source[self.start:self.current], literal, self.line)
|
||||
|
||||
def is_identifier(self, char: str):
|
||||
"""Returns if a character can be an identifier"""
|
||||
|
||||
if char.isalpha() or char in ("_", ): # More coming soon
|
||||
return True
|
||||
|
||||
def scan_token(self):
|
||||
"""
|
||||
Scans for a single token and adds it to
|
||||
self.tokens
|
||||
"""
|
||||
|
||||
char = self.step()
|
||||
if char in (" ", "\t", "\r"): # Useless characters
|
||||
return
|
||||
elif char == "\n": # New line
|
||||
self.line += 1
|
||||
elif char in ("'", '"'): # A string literal
|
||||
self.string(char)
|
||||
elif char.isdigit():
|
||||
self.number()
|
||||
elif self.is_identifier(char): # Identifier or reserved keyword
|
||||
self.identifier()
|
||||
elif char in self.TOKENS:
|
||||
if char == "/" and self.match("/"):
|
||||
while self.peek() != "\n" and not self.done():
|
||||
self.step() # Who cares about comments?
|
||||
elif char == "/" and self.match("*"):
|
||||
self.comment()
|
||||
elif char == "=" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.DEQ))
|
||||
elif char == ">" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.GE))
|
||||
elif char == "<" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.LE))
|
||||
elif char == "!" and self.match("="):
|
||||
self.tokens.append(self.create_token(TokenType.NE))
|
||||
elif char == "*" and self.match("*"):
|
||||
self.tokens.append(self.create_token(TokenType.POW))
|
||||
else:
|
||||
self.tokens.append(self.create_token(self.TOKENS[char]))
|
||||
else:
|
||||
raise ParseError(f"unexpected character '{char}' at line {self.line}")
|
||||
|
||||
def lex(self) -> List[Token]:
|
||||
"""
|
||||
Performs lexical analysis on self.source
|
||||
and returns a list of tokens
|
||||
"""
|
||||
|
||||
while not self.done():
|
||||
self.start = self.current
|
||||
self.scan_token()
|
||||
self.tokens.append(Token(TokenType.EOF, "", None, self.line))
|
||||
return self.tokens
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
|
||||
class ClassType(Enum):
|
||||
|
||||
NONE = auto()
|
||||
CLASS = auto()
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class ClassType(Enum):
|
||||
|
||||
NONE = auto()
|
||||
CLASS = auto()
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
from .exceptions import JAPLError
|
||||
from .tokenobject import Token
|
||||
from .expression import Variable
|
||||
|
||||
|
||||
class Environment(object):
|
||||
"""
|
||||
A wrapper around a hashmap representing
|
||||
a scope
|
||||
"""
|
||||
|
||||
def __init__(self, enclosing=None):
|
||||
"""Object constructor"""
|
||||
|
||||
self.map = {}
|
||||
self.enclosing = enclosing
|
||||
|
||||
def define(self, name: str, attr: object):
|
||||
"""Defines a new variable in the scope"""
|
||||
|
||||
self.map[name] = attr
|
||||
|
||||
def get(self, name: Token):
|
||||
"""Gets a variable"""
|
||||
|
||||
if name.lexeme in self.map:
|
||||
return self.map[name.lexeme]
|
||||
elif self.enclosing:
|
||||
return self.enclosing.get(name)
|
||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||
|
||||
def get_at(self, distance, name):
|
||||
"""Gets a variable in a specific scope"""
|
||||
|
||||
return self.ancestor(distance).map.get(name)
|
||||
|
||||
def ancestor(self, distance):
|
||||
"""Finds the scope specified by distance"""
|
||||
|
||||
env = self
|
||||
for _ in range(distance):
|
||||
env = env.enclosing
|
||||
return env
|
||||
|
||||
def assign_at(self, distance, name, value):
|
||||
"""Same as get_at, but assigns instead of retrieving"""
|
||||
|
||||
self.ancestor(distance).map[name.lexeme] = value
|
||||
|
||||
def delete(self, var):
|
||||
"""Deletes a variable"""
|
||||
|
||||
if var.name.lexeme in self.map:
|
||||
del self.map[var.name.lexeme]
|
||||
elif self.enclosing:
|
||||
self.enclosing.delete(var)
|
||||
else:
|
||||
raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'")
|
||||
|
||||
def assign(self, name: Token, value: object):
|
||||
"""Assigns a variable"""
|
||||
|
||||
if name.lexeme in self.map:
|
||||
if isinstance(value, Variable):
|
||||
self.map[name.lexeme] = self.get(value.name)
|
||||
else:
|
||||
self.map[name.lexeme] = value
|
||||
elif self.enclosing:
|
||||
self.enclosing.assign(name, value)
|
||||
else:
|
||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||
from .exceptions import JAPLError
|
||||
from .tokenobject import Token
|
||||
from .expression import Variable
|
||||
|
||||
|
||||
class Environment(object):
|
||||
"""
|
||||
A wrapper around a hashmap representing
|
||||
a scope
|
||||
"""
|
||||
|
||||
def __init__(self, enclosing=None):
|
||||
"""Object constructor"""
|
||||
|
||||
self.map = {}
|
||||
self.enclosing = enclosing
|
||||
|
||||
def define(self, name: str, attr: object):
|
||||
"""Defines a new variable in the scope"""
|
||||
|
||||
self.map[name] = attr
|
||||
|
||||
def get(self, name: Token):
|
||||
"""Gets a variable"""
|
||||
|
||||
if name.lexeme in self.map:
|
||||
return self.map[name.lexeme]
|
||||
elif self.enclosing:
|
||||
return self.enclosing.get(name)
|
||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||
|
||||
def get_at(self, distance, name):
|
||||
"""Gets a variable in a specific scope"""
|
||||
|
||||
return self.ancestor(distance).map.get(name)
|
||||
|
||||
def ancestor(self, distance):
|
||||
"""Finds the scope specified by distance"""
|
||||
|
||||
env = self
|
||||
for _ in range(distance):
|
||||
env = env.enclosing
|
||||
return env
|
||||
|
||||
def assign_at(self, distance, name, value):
|
||||
"""Same as get_at, but assigns instead of retrieving"""
|
||||
|
||||
self.ancestor(distance).map[name.lexeme] = value
|
||||
|
||||
def delete(self, var):
|
||||
"""Deletes a variable"""
|
||||
|
||||
if var.name.lexeme in self.map:
|
||||
del self.map[var.name.lexeme]
|
||||
elif self.enclosing:
|
||||
self.enclosing.delete(var)
|
||||
else:
|
||||
raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'")
|
||||
|
||||
def assign(self, name: Token, value: object):
|
||||
"""Assigns a variable"""
|
||||
|
||||
if name.lexeme in self.map:
|
||||
if isinstance(value, Variable):
|
||||
self.map[name.lexeme] = self.get(value.name)
|
||||
else:
|
||||
self.map[name.lexeme] = value
|
||||
elif self.enclosing:
|
||||
self.enclosing.assign(name, value)
|
||||
else:
|
||||
raise JAPLError(name, f"Undefined name '{name.lexeme}'")
|
||||
|
|
|
@ -1,32 +1,32 @@
|
|||
from .tokentype import TokenType
|
||||
|
||||
|
||||
class JAPLError(BaseException):
|
||||
"""JAPL's exceptions base class"""
|
||||
|
||||
def __repr__(self):
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class ParseError(JAPLError):
|
||||
"""An error occurred while parsing"""
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.args) > 1:
|
||||
message, token = self.args
|
||||
if token.kind == TokenType.EOF:
|
||||
return f"Unexpected error while parsing at line {token.line}, at end: {message}"
|
||||
else:
|
||||
return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}"
|
||||
return self.args[0]
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class BreakException(JAPLError):
|
||||
"""Notifies a loop that it's time to break"""
|
||||
|
||||
|
||||
class ReturnException(JAPLError):
|
||||
"""Notifies a function that it's time to return"""
|
||||
from .tokentype import TokenType
|
||||
|
||||
|
||||
class JAPLError(BaseException):
|
||||
"""JAPL's exceptions base class"""
|
||||
|
||||
def __repr__(self):
|
||||
return self.args[1]
|
||||
|
||||
|
||||
class ParseError(JAPLError):
|
||||
"""An error occurred while parsing"""
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.args) > 1:
|
||||
message, token = self.args
|
||||
if token.kind == TokenType.EOF:
|
||||
return f"Unexpected error while parsing at line {token.line}, at end: {message}"
|
||||
else:
|
||||
return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}"
|
||||
return self.args[0]
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class BreakException(JAPLError):
|
||||
"""Notifies a loop that it's time to break"""
|
||||
|
||||
|
||||
class ReturnException(JAPLError):
|
||||
"""Notifies a function that it's time to return"""
|
||||
|
|
|
@ -1,167 +1,167 @@
|
|||
from dataclasses import dataclass
|
||||
from abc import ABC, abstractmethod
|
||||
from .tokenobject import Token
|
||||
from typing import List
|
||||
|
||||
|
||||
class Expression(object):
|
||||
"""
|
||||
An object representing a JAPL expression.
|
||||
This class is not meant to be instantiated directly,
|
||||
inherit from it instead!
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""
|
||||
Visitor abstract base class to implement
|
||||
the Visitor pattern
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def visit_literal(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_binary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_grouping(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_unary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_get(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_set(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Binary(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_binary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Unary(Expression):
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_unary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Literal(Expression):
|
||||
value: object
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_literal(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Grouping(Expression):
|
||||
expr: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_grouping(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable(Expression):
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_var_expr(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assignment(Expression):
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_assign(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Logical(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_logical(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Call(Expression):
|
||||
callee: Expression
|
||||
paren: Token
|
||||
arguments: List[Expression] = ()
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_call_expr(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Get(Expression):
|
||||
object: Expression
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_get(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Set(Expression):
|
||||
object: Expression
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_set(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class This(Expression):
|
||||
keyword: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_this(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Super(Expression):
|
||||
keyword: Token
|
||||
method: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_super(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from abc import ABC, abstractmethod
|
||||
from .tokenobject import Token
|
||||
from typing import List
|
||||
|
||||
|
||||
class Expression(object):
|
||||
"""
|
||||
An object representing a JAPL expression.
|
||||
This class is not meant to be instantiated directly,
|
||||
inherit from it instead!
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""
|
||||
Visitor abstract base class to implement
|
||||
the Visitor pattern
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def visit_literal(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_binary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_grouping(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_unary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_get(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_set(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Binary(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_binary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Unary(Expression):
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_unary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Literal(Expression):
|
||||
value: object
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_literal(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Grouping(Expression):
|
||||
expr: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_grouping(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable(Expression):
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_var_expr(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Assignment(Expression):
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_assign(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Logical(Expression):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_logical(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Call(Expression):
|
||||
callee: Expression
|
||||
paren: Token
|
||||
arguments: List[Expression] = ()
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_call_expr(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Get(Expression):
|
||||
object: Expression
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_get(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Set(Expression):
|
||||
object: Expression
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_set(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class This(Expression):
|
||||
keyword: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_this(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Super(Expression):
|
||||
keyword: Token
|
||||
method: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_super(self)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
|
||||
class FunctionType(Enum):
|
||||
NONE = auto()
|
||||
FUNCTION = auto()
|
||||
METHOD = auto()
|
||||
INIT = auto()
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class FunctionType(Enum):
|
||||
NONE = auto()
|
||||
FUNCTION = auto()
|
||||
METHOD = auto()
|
||||
INIT = auto()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
|
||||
class LoopType(Enum):
|
||||
|
||||
NONE = auto()
|
||||
WHILE = auto()
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class LoopType(Enum):
|
||||
|
||||
NONE = auto()
|
||||
WHILE = auto()
|
||||
|
|
|
@ -1,173 +1,173 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from .expression import Expression, Variable
|
||||
from .tokenobject import Token
|
||||
from typing import List, Any
|
||||
|
||||
|
||||
class Statement(object):
|
||||
"""
|
||||
A Base Class representing JAPL statements
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""Wrapper to implement the Visitor Pattern"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def visit_statement_expr(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_var_stmt(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_del(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_block(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_if(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_while(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_break(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_function(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_return(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_class(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class StatementExpr(Statement):
|
||||
"""
|
||||
An expression statement
|
||||
"""
|
||||
|
||||
expression: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_statement_expr(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Var(Statement):
|
||||
"""
|
||||
A var statement
|
||||
"""
|
||||
|
||||
name: Token
|
||||
init: Expression = None
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_var_stmt(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Del(Statement):
|
||||
"""
|
||||
A del statement
|
||||
"""
|
||||
|
||||
name: Any
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_del(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block(Statement):
|
||||
"""A block statement"""
|
||||
|
||||
statements: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_block(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class If(Statement):
|
||||
"""An if statement"""
|
||||
|
||||
condition: Expression
|
||||
then_branch: Statement
|
||||
else_branch: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_if(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class While(Statement):
|
||||
"""A while statement"""
|
||||
|
||||
condition: Expression
|
||||
body: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_while(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Break(Statement):
|
||||
"""A break statement"""
|
||||
|
||||
token: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_break(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Function(Statement):
|
||||
"""A function statement"""
|
||||
|
||||
name: Token
|
||||
params: List[Token]
|
||||
body: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_function(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Return(Statement, BaseException):
|
||||
"""A return statement"""
|
||||
|
||||
keyword: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_return(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Class(Statement):
|
||||
"""A class statement"""
|
||||
|
||||
name: Token
|
||||
methods: list
|
||||
superclass: Variable
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_class(self)
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from .expression import Expression, Variable
|
||||
from .tokenobject import Token
|
||||
from typing import List, Any
|
||||
|
||||
|
||||
class Statement(object):
|
||||
"""
|
||||
A Base Class representing JAPL statements
|
||||
"""
|
||||
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
class Visitor(ABC):
|
||||
"""Wrapper to implement the Visitor Pattern"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def visit_statement_expr(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_var_stmt(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_del(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_block(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_if(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_while(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_break(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_function(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def visit_return(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def visit_class(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class StatementExpr(Statement):
|
||||
"""
|
||||
An expression statement
|
||||
"""
|
||||
|
||||
expression: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_statement_expr(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Var(Statement):
|
||||
"""
|
||||
A var statement
|
||||
"""
|
||||
|
||||
name: Token
|
||||
init: Expression = None
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_var_stmt(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Del(Statement):
|
||||
"""
|
||||
A del statement
|
||||
"""
|
||||
|
||||
name: Any
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_del(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block(Statement):
|
||||
"""A block statement"""
|
||||
|
||||
statements: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_block(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class If(Statement):
|
||||
"""An if statement"""
|
||||
|
||||
condition: Expression
|
||||
then_branch: Statement
|
||||
else_branch: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_if(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class While(Statement):
|
||||
"""A while statement"""
|
||||
|
||||
condition: Expression
|
||||
body: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_while(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Break(Statement):
|
||||
"""A break statement"""
|
||||
|
||||
token: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_break(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Function(Statement):
|
||||
"""A function statement"""
|
||||
|
||||
name: Token
|
||||
params: List[Token]
|
||||
body: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_function(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Return(Statement, BaseException):
|
||||
"""A return statement"""
|
||||
|
||||
keyword: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_return(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Class(Statement):
|
||||
"""A class statement"""
|
||||
|
||||
name: Token
|
||||
methods: list
|
||||
superclass: Variable
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_class(self)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from dataclasses import dataclass
|
||||
from .tokentype import TokenType
|
||||
|
||||
@dataclass
|
||||
class Token(object):
|
||||
"""The representation of a JAPL token"""
|
||||
|
||||
kind: TokenType
|
||||
lexeme: str
|
||||
literal: object
|
||||
line: int
|
||||
|
||||
|
||||
from dataclasses import dataclass
|
||||
from .tokentype import TokenType
|
||||
|
||||
@dataclass
|
||||
class Token(object):
|
||||
"""The representation of a JAPL token"""
|
||||
|
||||
kind: TokenType
|
||||
lexeme: str
|
||||
literal: object
|
||||
line: int
|
||||
|
||||
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
from enum import Enum, auto
|
||||
|
||||
class TokenType(Enum):
|
||||
"""
|
||||
An enumeration for all JAPL types
|
||||
"""
|
||||
|
||||
LP = auto()
|
||||
RP = auto()
|
||||
LB = auto()
|
||||
RB = auto()
|
||||
COMMA = auto()
|
||||
DOT = auto()
|
||||
PLUS = auto()
|
||||
MINUS = auto()
|
||||
SLASH = auto()
|
||||
SEMICOLON = auto()
|
||||
STAR = auto()
|
||||
|
||||
|
||||
NEG = auto()
|
||||
NE = auto()
|
||||
EQ = auto()
|
||||
DEQ = auto()
|
||||
GT = auto()
|
||||
LT = auto()
|
||||
GE = auto()
|
||||
LE = auto()
|
||||
MOD = auto()
|
||||
POW = auto()
|
||||
|
||||
ID = auto()
|
||||
STR = auto()
|
||||
NUM = auto()
|
||||
|
||||
|
||||
AND = auto()
|
||||
CLASS = auto()
|
||||
ELSE = auto()
|
||||
FOR = auto()
|
||||
FUN = auto()
|
||||
FALSE = auto()
|
||||
IF = auto()
|
||||
NIL = auto()
|
||||
OR = auto()
|
||||
RETURN = auto()
|
||||
SUPER = auto()
|
||||
THIS = auto()
|
||||
TRUE = auto()
|
||||
VAR = auto()
|
||||
WHILE = auto()
|
||||
DEL = auto()
|
||||
BREAK = auto()
|
||||
|
||||
EOF = auto()
|
||||
from enum import Enum, auto
|
||||
|
||||
class TokenType(Enum):
|
||||
"""
|
||||
An enumeration for all JAPL types
|
||||
"""
|
||||
|
||||
LP = auto()
|
||||
RP = auto()
|
||||
LB = auto()
|
||||
RB = auto()
|
||||
COMMA = auto()
|
||||
DOT = auto()
|
||||
PLUS = auto()
|
||||
MINUS = auto()
|
||||
SLASH = auto()
|
||||
SEMICOLON = auto()
|
||||
STAR = auto()
|
||||
|
||||
|
||||
NEG = auto()
|
||||
NE = auto()
|
||||
EQ = auto()
|
||||
DEQ = auto()
|
||||
GT = auto()
|
||||
LT = auto()
|
||||
GE = auto()
|
||||
LE = auto()
|
||||
MOD = auto()
|
||||
POW = auto()
|
||||
|
||||
ID = auto()
|
||||
STR = auto()
|
||||
NUM = auto()
|
||||
|
||||
|
||||
AND = auto()
|
||||
CLASS = auto()
|
||||
ELSE = auto()
|
||||
FOR = auto()
|
||||
FUN = auto()
|
||||
FALSE = auto()
|
||||
IF = auto()
|
||||
NIL = auto()
|
||||
OR = auto()
|
||||
RETURN = auto()
|
||||
SUPER = auto()
|
||||
THIS = auto()
|
||||
TRUE = auto()
|
||||
VAR = auto()
|
||||
WHILE = auto()
|
||||
DEL = auto()
|
||||
BREAK = 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
|
||||
A POC (proof of concept) of the JAPL language written in Python 3
|
||||
# japl
|
||||
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