added missing files

This commit is contained in:
Productive2 2020-10-22 10:19:00 +02:00
parent 1daae9c30a
commit ccc78148d8
28 changed files with 2388 additions and 1099 deletions

4
JAPL/__init__.py Normal file
View File

@ -0,0 +1,4 @@
from .lexer import Lexer
from .parser import Parser
from .interpreter import Interpreter

View File

@ -1,352 +1,352 @@
import operator import operator
from typing import List from typing import List
from .types.callable import Callable from .types.callable import Callable
from .types.japlclass import JAPLClass from .types.japlclass import JAPLClass
from .types.instance import JAPLInstance from .types.instance import JAPLInstance
from .meta.environment import Environment from .meta.environment import Environment
from .meta.tokentype import TokenType from .meta.tokentype import TokenType
from .meta.exceptions import JAPLError, BreakException, ReturnException from .meta.exceptions import JAPLError, BreakException, ReturnException
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set
from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class
class Interpreter(Expression.Visitor, Statement.Visitor): class Interpreter(Expression.Visitor, Statement.Visitor):
""" """
An interpreter for the JAPL An interpreter for the JAPL
programming language programming language
""" """
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv, OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt, TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq, TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow} TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
def __init__(self): def __init__(self):
"""Object constructor""" """Object constructor"""
self.environment = Environment() self.environment = Environment()
self.locals = {} self.locals = {}
self.globals = self.environment self.globals = self.environment
self.globals.define("clock", Clock()) self.globals.define("clock", Clock())
self.globals.define("type", Type()) self.globals.define("type", Type())
self.globals.define("truthy", Truthy()) self.globals.define("truthy", Truthy())
self.globals.define("stringify", Stringify()) self.globals.define("stringify", Stringify())
self.globals.define("print", PrintFunction()) self.globals.define("print", PrintFunction())
self.globals.define("isinstance", IsInstance()) self.globals.define("isinstance", IsInstance())
self.globals.define("issuperclass", IsSuperclass()) self.globals.define("issuperclass", IsSuperclass())
self.globals.define("issubclass", IsSubclass()) self.globals.define("issubclass", IsSubclass())
def number_operand(self, op, operand): def number_operand(self, op, operand):
""" """
An helper method to check if the operand An helper method to check if the operand
to a unary operator is a number to a unary operator is a number
""" """
if isinstance(operand, (int, float)): if isinstance(operand, (int, float)):
return return
raise JAPLError(op, raise JAPLError(op,
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'") f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
def compatible_operands(self, op, left, right): def compatible_operands(self, op, left, right):
""" """
Helper method to check types when doing binary Helper method to check types when doing binary
operations operations
""" """
if op.kind == TokenType.SLASH and right == 0: if op.kind == TokenType.SLASH and right == 0:
raise JAPLError(op, "Cannot divide by 0") raise JAPLError(op, "Cannot divide by 0")
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))): elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
if op.kind not in (TokenType.DEQ, TokenType.NE): if op.kind not in (TokenType.DEQ, TokenType.NE):
raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
return return
elif isinstance(left, (int, float)) and isinstance(right, (int, float)): elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
return return
elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE): elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
if isinstance(left, str) and isinstance(right, str): if isinstance(left, str) and isinstance(right, str):
return return
elif isinstance(left, str) and isinstance(right, int): elif isinstance(left, str) and isinstance(right, int):
return return
elif isinstance(left, int) and isinstance(right, str): elif isinstance(left, int) and isinstance(right, str):
return return
raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
def visit_literal(self, expr: Literal): def visit_literal(self, expr: Literal):
""" """
Visits a Literal node in the Abstract Syntax Tree, Visits a Literal node in the Abstract Syntax Tree,
returning its value to the visitor returning its value to the visitor
""" """
return expr.value return expr.value
def visit_logical(self, expr: Logical): def visit_logical(self, expr: Logical):
"""Visits a logical node""" """Visits a logical node"""
left = self.eval(expr.left) left = self.eval(expr.left)
if expr.operator.kind == TokenType.OR: if expr.operator.kind == TokenType.OR:
if bool(left): if bool(left):
return left return left
elif not bool(left): elif not bool(left):
return self.eval(expr.right) return self.eval(expr.right)
return self.eval(expr.right) return self.eval(expr.right)
def eval(self, expr: Expression): def eval(self, expr: Expression):
""" """
Evaluates an expression by calling its accept() Evaluates an expression by calling its accept()
method and passing self to it. This mechanism is known method and passing self to it. This mechanism is known
as the 'Visitor Pattern': the expression object will as the 'Visitor Pattern': the expression object will
later call the interpreter's appropriate method to later call the interpreter's appropriate method to
evaluate itself evaluate itself
""" """
return expr.accept(self) return expr.accept(self)
def visit_grouping(self, grouping: Grouping): def visit_grouping(self, grouping: Grouping):
""" """
Visits a Grouping node in the Abstract Syntax Tree, Visits a Grouping node in the Abstract Syntax Tree,
recursively evaluating its subexpressions recursively evaluating its subexpressions
""" """
return self.eval(grouping.expr) return self.eval(grouping.expr)
def visit_unary(self, expr: Unary): def visit_unary(self, expr: Unary):
""" """
Visits a Unary node in the Abstract Syntax Teee, Visits a Unary node in the Abstract Syntax Teee,
returning the negation of the given object, if returning the negation of the given object, if
the operation is supported the operation is supported
""" """
right = self.eval(expr.right) right = self.eval(expr.right)
self.number_operand(expr.operator, right) self.number_operand(expr.operator, right)
if expr.operator.kind == TokenType.NEG: if expr.operator.kind == TokenType.NEG:
return not right return not right
return -right return -right
def visit_binary(self, expr: Binary): def visit_binary(self, expr: Binary):
""" """
Visits a Binary node in the Abstract Syntax Tree, Visits a Binary node in the Abstract Syntax Tree,
recursively evaulating both operands first and then recursively evaulating both operands first and then
performing the operation specified by the operator performing the operation specified by the operator
""" """
left = self.eval(expr.left) left = self.eval(expr.left)
right = self.eval(expr.right) right = self.eval(expr.right)
self.compatible_operands(expr.operator, left, right) self.compatible_operands(expr.operator, left, right)
return self.OPS[expr.operator.kind](left, right) return self.OPS[expr.operator.kind](left, right)
def visit_statement_expr(self, stmt: StatementExpr): def visit_statement_expr(self, stmt: StatementExpr):
""" """
Visits an expression statement and evaluates it Visits an expression statement and evaluates it
""" """
self.eval(stmt.expression) self.eval(stmt.expression)
def visit_if(self, statement: If): def visit_if(self, statement: If):
""" """
Visits an If node and evaluates it Visits an If node and evaluates it
""" """
if self.eval(statement.condition): if self.eval(statement.condition):
self.exec(statement.then_branch) self.exec(statement.then_branch)
elif statement.else_branch: elif statement.else_branch:
self.exec(statement.else_branch) self.exec(statement.else_branch)
def visit_class(self, stmt: Class): def visit_class(self, stmt: Class):
"""Visits a class declaration""" """Visits a class declaration"""
superclass = None superclass = None
if stmt.superclass: if stmt.superclass:
superclass = self.eval(stmt.superclass) superclass = self.eval(stmt.superclass)
if not isinstance(superclass, JAPLClass): if not isinstance(superclass, JAPLClass):
raise JAPLError(stmt.superclass.name, "Superclass must be a class") raise JAPLError(stmt.superclass.name, "Superclass must be a class")
self.environment.define(stmt.name.lexeme, None) self.environment.define(stmt.name.lexeme, None)
if superclass: if superclass:
environment = Environment(self.environment) environment = Environment(self.environment)
environment.define("super", superclass) environment.define("super", superclass)
else: else:
environment = self.environment environment = self.environment
methods = {} methods = {}
for method in stmt.methods: for method in stmt.methods:
func = JAPLFunction(method, environment) func = JAPLFunction(method, environment)
methods[method.name.lexeme] = func methods[method.name.lexeme] = func
klass = JAPLClass(stmt.name.lexeme, methods, superclass) klass = JAPLClass(stmt.name.lexeme, methods, superclass)
if superclass: if superclass:
self.environment = environment.enclosing self.environment = environment.enclosing
self.environment.assign(stmt.name, klass) self.environment.assign(stmt.name, klass)
def visit_while(self, statement: While): def visit_while(self, statement: While):
""" """
Visits a while node and executes it Visits a while node and executes it
""" """
while self.eval(statement.condition): while self.eval(statement.condition):
try: try:
self.exec(statement.body) self.exec(statement.body)
except BreakException: except BreakException:
break break
def visit_var_stmt(self, stmt: Var): def visit_var_stmt(self, stmt: Var):
""" """
Visits a var statement Visits a var statement
""" """
val = None val = None
if stmt.init: if stmt.init:
val = self.eval(stmt.init) val = self.eval(stmt.init)
self.environment.define(stmt.name.lexeme, val) self.environment.define(stmt.name.lexeme, val)
def lookup(self, name, expr: Expression): def lookup(self, name, expr: Expression):
""" """
Performs name lookups in the closest scope Performs name lookups in the closest scope
""" """
distance = self.locals.get(expr) distance = self.locals.get(expr)
if distance is not None: if distance is not None:
return self.environment.get_at(distance, name.lexeme) return self.environment.get_at(distance, name.lexeme)
else: else:
return self.globals.get(name) return self.globals.get(name)
def visit_var_expr(self, expr: Variable): def visit_var_expr(self, expr: Variable):
""" """
Visits a var expression Visits a var expression
""" """
return self.lookup(expr.name, expr) return self.lookup(expr.name, expr)
def visit_del(self, stmt: Del): def visit_del(self, stmt: Del):
""" """
Visits a del expression Visits a del expression
""" """
return self.environment.delete(stmt.name) return self.environment.delete(stmt.name)
def visit_assign(self, stmt: Assignment): def visit_assign(self, stmt: Assignment):
""" """
Visits an assignment expression Visits an assignment expression
""" """
right = self.eval(stmt.value) right = self.eval(stmt.value)
distance = self.locals.get(stmt) distance = self.locals.get(stmt)
if distance is not None: if distance is not None:
self.environment.assign_at(distance, stmt.name, right) self.environment.assign_at(distance, stmt.name, right)
else: else:
self.globals.assign(stmt.name, right) self.globals.assign(stmt.name, right)
return right return right
def visit_block(self, stmt: Block): def visit_block(self, stmt: Block):
""" """
Visits a new scope block Visits a new scope block
""" """
return self.execute_block(stmt.statements, Environment(self.environment)) return self.execute_block(stmt.statements, Environment(self.environment))
def visit_break(self, stmt: Break): def visit_break(self, stmt: Break):
""" """
Visits a break statement Visits a break statement
""" """
raise BreakException() raise BreakException()
def visit_call_expr(self, expr: Call): def visit_call_expr(self, expr: Call):
""" """
Visits a call expression Visits a call expression
""" """
callee = self.eval(expr.callee) callee = self.eval(expr.callee)
if not isinstance(callee, Callable): if not isinstance(callee, Callable):
raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable") raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable")
arguments = [] arguments = []
for argument in expr.arguments: for argument in expr.arguments:
arguments.append(self.eval(argument)) arguments.append(self.eval(argument))
function = callee function = callee
if function.arity != len(arguments): if function.arity != len(arguments):
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}") raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
return function.call(self, arguments) return function.call(self, arguments)
def execute_block(self, statements: List[Statement], scope: Environment): def execute_block(self, statements: List[Statement], scope: Environment):
""" """
Executes a block of statements Executes a block of statements
""" """
prev = self.environment prev = self.environment
try: try:
self.environment = scope self.environment = scope
for statement in statements: for statement in statements:
self.exec(statement) self.exec(statement)
finally: finally:
self.environment = prev self.environment = prev
def visit_return(self, statement: Return): def visit_return(self, statement: Return):
""" """
Visits a return statement Visits a return statement
""" """
value = None value = None
if statement.value: if statement.value:
value = self.eval(statement.value) value = self.eval(statement.value)
raise ReturnException(value) raise ReturnException(value)
def visit_function(self, statement: Function): def visit_function(self, statement: Function):
""" """
Visits a function Visits a function
""" """
function = JAPLFunction(statement, self.environment) function = JAPLFunction(statement, self.environment)
self.environment.define(statement.name.lexeme, function) self.environment.define(statement.name.lexeme, function)
def visit_get(self, expr: Get): def visit_get(self, expr: Get):
"""Visits property get expressions and evaluates them""" """Visits property get expressions and evaluates them"""
obj = self.eval(expr.object) obj = self.eval(expr.object)
if isinstance(obj, JAPLInstance): if isinstance(obj, JAPLInstance):
return obj.get(expr.name) return obj.get(expr.name)
raise JAPLError(expr.name, "Only instances have properties") raise JAPLError(expr.name, "Only instances have properties")
def visit_set(self, expr: Set): def visit_set(self, expr: Set):
"""Visits property set expressions and evaluates them""" """Visits property set expressions and evaluates them"""
obj = self.eval(expr.object) obj = self.eval(expr.object)
if not isinstance(obj, JAPLInstance): if not isinstance(obj, JAPLInstance):
raise JAPLError(expr, "Only instances have fields") raise JAPLError(expr, "Only instances have fields")
value = self.eval(expr.value) value = self.eval(expr.value)
obj.set(expr.name, value) obj.set(expr.name, value)
def visit_this(self, expr): def visit_this(self, expr):
"""Evaluates 'this' expressions""" """Evaluates 'this' expressions"""
return self.lookup(expr.keyword, expr) return self.lookup(expr.keyword, expr)
def visit_super(self, expr): def visit_super(self, expr):
"""Evaluates 'super' expressions""" """Evaluates 'super' expressions"""
distance = self.locals.get(expr) distance = self.locals.get(expr)
superclass = self.environment.get_at(distance, "super") superclass = self.environment.get_at(distance, "super")
instance = self.environment.get_at(distance - 1, "this") instance = self.environment.get_at(distance - 1, "this")
meth = superclass.get_method(expr.method.lexeme) meth = superclass.get_method(expr.method.lexeme)
if not meth: if not meth:
raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'") raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'")
return meth.bind(instance) return meth.bind(instance)
def exec(self, statement: Statement): def exec(self, statement: Statement):
""" """
Executes a statement Executes a statement
""" """
statement.accept(self) statement.accept(self)
def interpret(self, statements: List[Statement]): def interpret(self, statements: List[Statement]):
""" """
Executes a JAPL program Executes a JAPL program
""" """
for statement in statements: for statement in statements:
self.exec(statement) self.exec(statement)
def resolve(self, expr: Expression, depth: int): def resolve(self, expr: Expression, depth: int):
""" """
Stores the result of the name resolution: this Stores the result of the name resolution: this
info will be used later to know exactly in which info will be used later to know exactly in which
environment to look up a given variable environment to look up a given variable
""" """
self.locals[expr] = depth # How many environments to skip! self.locals[expr] = depth # How many environments to skip!

View File

@ -1,212 +1,212 @@
from .meta.tokenobject import Token from .meta.tokenobject import Token
from .meta.tokentype import TokenType from .meta.tokentype import TokenType
from .meta.exceptions import ParseError from .meta.exceptions import ParseError
from typing import List from typing import List
class Lexer(object): class Lexer(object):
""" """
A simple tokenizer for the JAPL programming A simple tokenizer for the JAPL programming
language, scans a input source file and language, scans a input source file and
produces a list of tokens. Some errors produces a list of tokens. Some errors
are caught here as well. are caught here as well.
""" """
TOKENS = {"(": TokenType.LP, ")": TokenType.RP, TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
"{": TokenType.LB, "}": TokenType.RB, "{": TokenType.LB, "}": TokenType.RB,
".": TokenType.DOT, ",": TokenType.COMMA, ".": TokenType.DOT, ",": TokenType.COMMA,
"-": TokenType.MINUS, "+": TokenType.PLUS, "-": TokenType.MINUS, "+": TokenType.PLUS,
";": TokenType.SEMICOLON, "*": TokenType.STAR, ";": TokenType.SEMICOLON, "*": TokenType.STAR,
">": TokenType.GT, "<": TokenType.LT, ">": TokenType.GT, "<": TokenType.LT,
"=": TokenType.EQ, "!": TokenType.NEG, "=": TokenType.EQ, "!": TokenType.NEG,
"/": TokenType.SLASH, "%": TokenType.MOD} "/": TokenType.SLASH, "%": TokenType.MOD}
RESERVED = {"or": TokenType.OR, "and": TokenType.AND, RESERVED = {"or": TokenType.OR, "and": TokenType.AND,
"class": TokenType.CLASS, "fun": TokenType.FUN, "class": TokenType.CLASS, "fun": TokenType.FUN,
"if": TokenType.IF, "else": TokenType.ELSE, "if": TokenType.IF, "else": TokenType.ELSE,
"for": TokenType.FOR, "while": TokenType.WHILE, "for": TokenType.FOR, "while": TokenType.WHILE,
"var": TokenType.VAR, "nil": TokenType.NIL, "var": TokenType.VAR, "nil": TokenType.NIL,
"true": TokenType.TRUE, "false": TokenType.FALSE, "true": TokenType.TRUE, "false": TokenType.FALSE,
"return": TokenType.RETURN, "return": TokenType.RETURN,
"this": TokenType.THIS, "super": TokenType.SUPER, "this": TokenType.THIS, "super": TokenType.SUPER,
"del": TokenType.DEL, "break": TokenType.BREAK} "del": TokenType.DEL, "break": TokenType.BREAK}
def __init__(self, source: str): def __init__(self, source: str):
"""Object constructor""" """Object constructor"""
self.source = source self.source = source
self.tokens: List[Token] = [] self.tokens: List[Token] = []
self.line: int = 1 # Points to the line being lexed self.line: int = 1 # Points to the line being lexed
self.start: int = 0 # The position of the first character of the current lexeme self.start: int = 0 # The position of the first character of the current lexeme
self.current: int = 0 # The position of the current character being lexed self.current: int = 0 # The position of the current character being lexed
def step(self) -> str: def step(self) -> str:
""" """
'Steps' one character in the source code and returns it 'Steps' one character in the source code and returns it
""" """
if self.done(): if self.done():
return "" return ""
self.current += 1 self.current += 1
return self.source[self.current - 1] return self.source[self.current - 1]
def peek(self) -> str: def peek(self) -> str:
""" """
Returns the current character without consuming it Returns the current character without consuming it
or an empty string if all text has been consumed or an empty string if all text has been consumed
""" """
if self.done(): if self.done():
return "" return ""
return self.source[self.current] return self.source[self.current]
def peek_next(self) -> str: def peek_next(self) -> str:
""" """
Returns the next character after self.current Returns the next character after self.current
or an empty string if the input has been consumed or an empty string if the input has been consumed
""" """
if self.current + 1 >= len(self.source): if self.current + 1 >= len(self.source):
return "" return ""
return self.source[self.current + 1] return self.source[self.current + 1]
def string(self, delimiter: str): def string(self, delimiter: str):
"""Parses a string literal""" """Parses a string literal"""
while self.peek() != delimiter and not self.done(): while self.peek() != delimiter and not self.done():
if self.peek() == "\n": if self.peek() == "\n":
self.line += 1 self.line += 1
self.step() self.step()
if self.done(): if self.done():
raise ParseError(f"unterminated string literal at line {self.line}") raise ParseError(f"unterminated string literal at line {self.line}")
self.step() # Consume the '"' self.step() # Consume the '"'
value = self.source[self.start + 1:self.current - 1] # Get the actual string value = self.source[self.start + 1:self.current - 1] # Get the actual string
self.tokens.append(self.create_token(TokenType.STR, value)) self.tokens.append(self.create_token(TokenType.STR, value))
def number(self): def number(self):
"""Parses a number literal""" """Parses a number literal"""
convert = int convert = int
while self.peek().isdigit(): while self.peek().isdigit():
self.step() self.step()
if self.peek() == ".": if self.peek() == ".":
self.step() # Consume the "." self.step() # Consume the "."
while self.peek().isdigit(): while self.peek().isdigit():
self.step() self.step()
convert = float convert = float
self.tokens.append(self.create_token(TokenType.NUM, self.tokens.append(self.create_token(TokenType.NUM,
convert(self.source[self.start:self.current]))) convert(self.source[self.start:self.current])))
def identifier(self): def identifier(self):
"""Parses identifiers and reserved keywords""" """Parses identifiers and reserved keywords"""
while self.peek().isalnum() or self.is_identifier(self.peek()): while self.peek().isalnum() or self.is_identifier(self.peek()):
self.step() self.step()
kind = TokenType.ID kind = TokenType.ID
value = self.source[self.start:self.current] value = self.source[self.start:self.current]
if self.RESERVED.get(value, None): if self.RESERVED.get(value, None):
kind = self.RESERVED[value] kind = self.RESERVED[value]
self.tokens.append(self.create_token(kind)) self.tokens.append(self.create_token(kind))
def comment(self): def comment(self):
"""Handles multi-line comments""" """Handles multi-line comments"""
closed = False closed = False
while not self.done(): while not self.done():
end = self.peek() + self.peek_next() end = self.peek() + self.peek_next()
if end == "/*": # Nested comments if end == "/*": # Nested comments
self.step() self.step()
self.step() self.step()
self.comment() self.comment()
elif end == "*/": elif end == "*/":
closed = True closed = True
self.step() # Consume the two ends self.step() # Consume the two ends
self.step() self.step()
break break
self.step() self.step()
if self.done() and not closed: if self.done() and not closed:
raise ParseError(f"Unexpected EOF at line {self.line}") raise ParseError(f"Unexpected EOF at line {self.line}")
def match(self, char: str) -> bool: def match(self, char: str) -> bool:
""" """
Returns True if the current character in self.source matches Returns True if the current character in self.source matches
the given character the given character
""" """
if self.done(): if self.done():
return False return False
elif self.source[self.current] != char: elif self.source[self.current] != char:
return False return False
self.current += 1 self.current += 1
return True return True
def done(self) -> bool: def done(self) -> bool:
""" """
Helper method that's used by the lexer Helper method that's used by the lexer
to know if all source has been consumed to know if all source has been consumed
""" """
return self.current >= len(self.source) return self.current >= len(self.source)
def create_token(self, kind: TokenType, literal: object = None) -> Token: def create_token(self, kind: TokenType, literal: object = None) -> Token:
""" """
Creates and returns a token object Creates and returns a token object
""" """
return Token(kind, self.source[self.start:self.current], literal, self.line) return Token(kind, self.source[self.start:self.current], literal, self.line)
def is_identifier(self, char: str): def is_identifier(self, char: str):
"""Returns if a character can be an identifier""" """Returns if a character can be an identifier"""
if char.isalpha() or char in ("_", ): # More coming soon if char.isalpha() or char in ("_", ): # More coming soon
return True return True
def scan_token(self): def scan_token(self):
""" """
Scans for a single token and adds it to Scans for a single token and adds it to
self.tokens self.tokens
""" """
char = self.step() char = self.step()
if char in (" ", "\t", "\r"): # Useless characters if char in (" ", "\t", "\r"): # Useless characters
return return
elif char == "\n": # New line elif char == "\n": # New line
self.line += 1 self.line += 1
elif char in ("'", '"'): # A string literal elif char in ("'", '"'): # A string literal
self.string(char) self.string(char)
elif char.isdigit(): elif char.isdigit():
self.number() self.number()
elif self.is_identifier(char): # Identifier or reserved keyword elif self.is_identifier(char): # Identifier or reserved keyword
self.identifier() self.identifier()
elif char in self.TOKENS: elif char in self.TOKENS:
if char == "/" and self.match("/"): if char == "/" and self.match("/"):
while self.peek() != "\n" and not self.done(): while self.peek() != "\n" and not self.done():
self.step() # Who cares about comments? self.step() # Who cares about comments?
elif char == "/" and self.match("*"): elif char == "/" and self.match("*"):
self.comment() self.comment()
elif char == "=" and self.match("="): elif char == "=" and self.match("="):
self.tokens.append(self.create_token(TokenType.DEQ)) self.tokens.append(self.create_token(TokenType.DEQ))
elif char == ">" and self.match("="): elif char == ">" and self.match("="):
self.tokens.append(self.create_token(TokenType.GE)) self.tokens.append(self.create_token(TokenType.GE))
elif char == "<" and self.match("="): elif char == "<" and self.match("="):
self.tokens.append(self.create_token(TokenType.LE)) self.tokens.append(self.create_token(TokenType.LE))
elif char == "!" and self.match("="): elif char == "!" and self.match("="):
self.tokens.append(self.create_token(TokenType.NE)) self.tokens.append(self.create_token(TokenType.NE))
elif char == "*" and self.match("*"): elif char == "*" and self.match("*"):
self.tokens.append(self.create_token(TokenType.POW)) self.tokens.append(self.create_token(TokenType.POW))
else: else:
self.tokens.append(self.create_token(self.TOKENS[char])) self.tokens.append(self.create_token(self.TOKENS[char]))
else: else:
raise ParseError(f"unexpected character '{char}' at line {self.line}") raise ParseError(f"unexpected character '{char}' at line {self.line}")
def lex(self) -> List[Token]: def lex(self) -> List[Token]:
""" """
Performs lexical analysis on self.source Performs lexical analysis on self.source
and returns a list of tokens and returns a list of tokens
""" """
while not self.done(): while not self.done():
self.start = self.current self.start = self.current
self.scan_token() self.scan_token()
self.tokens.append(Token(TokenType.EOF, "", None, self.line)) self.tokens.append(Token(TokenType.EOF, "", None, self.line))
return self.tokens return self.tokens

0
JAPL/meta/__init__.py Normal file
View File

View File

@ -1,7 +1,7 @@
from enum import Enum, auto from enum import Enum, auto
class ClassType(Enum): class ClassType(Enum):
NONE = auto() NONE = auto()
CLASS = auto() CLASS = auto()

View File

@ -1,71 +1,71 @@
from .exceptions import JAPLError from .exceptions import JAPLError
from .tokenobject import Token from .tokenobject import Token
from .expression import Variable from .expression import Variable
class Environment(object): class Environment(object):
""" """
A wrapper around a hashmap representing A wrapper around a hashmap representing
a scope a scope
""" """
def __init__(self, enclosing=None): def __init__(self, enclosing=None):
"""Object constructor""" """Object constructor"""
self.map = {} self.map = {}
self.enclosing = enclosing self.enclosing = enclosing
def define(self, name: str, attr: object): def define(self, name: str, attr: object):
"""Defines a new variable in the scope""" """Defines a new variable in the scope"""
self.map[name] = attr self.map[name] = attr
def get(self, name: Token): def get(self, name: Token):
"""Gets a variable""" """Gets a variable"""
if name.lexeme in self.map: if name.lexeme in self.map:
return self.map[name.lexeme] return self.map[name.lexeme]
elif self.enclosing: elif self.enclosing:
return self.enclosing.get(name) return self.enclosing.get(name)
raise JAPLError(name, f"Undefined name '{name.lexeme}'") raise JAPLError(name, f"Undefined name '{name.lexeme}'")
def get_at(self, distance, name): def get_at(self, distance, name):
"""Gets a variable in a specific scope""" """Gets a variable in a specific scope"""
return self.ancestor(distance).map.get(name) return self.ancestor(distance).map.get(name)
def ancestor(self, distance): def ancestor(self, distance):
"""Finds the scope specified by distance""" """Finds the scope specified by distance"""
env = self env = self
for _ in range(distance): for _ in range(distance):
env = env.enclosing env = env.enclosing
return env return env
def assign_at(self, distance, name, value): def assign_at(self, distance, name, value):
"""Same as get_at, but assigns instead of retrieving""" """Same as get_at, but assigns instead of retrieving"""
self.ancestor(distance).map[name.lexeme] = value self.ancestor(distance).map[name.lexeme] = value
def delete(self, var): def delete(self, var):
"""Deletes a variable""" """Deletes a variable"""
if var.name.lexeme in self.map: if var.name.lexeme in self.map:
del self.map[var.name.lexeme] del self.map[var.name.lexeme]
elif self.enclosing: elif self.enclosing:
self.enclosing.delete(var) self.enclosing.delete(var)
else: else:
raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'") raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'")
def assign(self, name: Token, value: object): def assign(self, name: Token, value: object):
"""Assigns a variable""" """Assigns a variable"""
if name.lexeme in self.map: if name.lexeme in self.map:
if isinstance(value, Variable): if isinstance(value, Variable):
self.map[name.lexeme] = self.get(value.name) self.map[name.lexeme] = self.get(value.name)
else: else:
self.map[name.lexeme] = value self.map[name.lexeme] = value
elif self.enclosing: elif self.enclosing:
self.enclosing.assign(name, value) self.enclosing.assign(name, value)
else: else:
raise JAPLError(name, f"Undefined name '{name.lexeme}'") raise JAPLError(name, f"Undefined name '{name.lexeme}'")

View File

@ -1,32 +1,32 @@
from .tokentype import TokenType from .tokentype import TokenType
class JAPLError(BaseException): class JAPLError(BaseException):
"""JAPL's exceptions base class""" """JAPL's exceptions base class"""
def __repr__(self): def __repr__(self):
return self.args[1] return self.args[1]
class ParseError(JAPLError): class ParseError(JAPLError):
"""An error occurred while parsing""" """An error occurred while parsing"""
def __repr__(self): def __repr__(self):
if len(self.args) > 1: if len(self.args) > 1:
message, token = self.args message, token = self.args
if token.kind == TokenType.EOF: if token.kind == TokenType.EOF:
return f"Unexpected error while parsing at line {token.line}, at end: {message}" return f"Unexpected error while parsing at line {token.line}, at end: {message}"
else: else:
return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}" return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}"
return self.args[0] return self.args[0]
def __str__(self): def __str__(self):
return self.__repr__() return self.__repr__()
class BreakException(JAPLError): class BreakException(JAPLError):
"""Notifies a loop that it's time to break""" """Notifies a loop that it's time to break"""
class ReturnException(JAPLError): class ReturnException(JAPLError):
"""Notifies a function that it's time to return""" """Notifies a function that it's time to return"""

View File

@ -1,167 +1,167 @@
from dataclasses import dataclass from dataclasses import dataclass
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .tokenobject import Token from .tokenobject import Token
from typing import List from typing import List
class Expression(object): class Expression(object):
""" """
An object representing a JAPL expression. An object representing a JAPL expression.
This class is not meant to be instantiated directly, This class is not meant to be instantiated directly,
inherit from it instead! inherit from it instead!
""" """
def accept(self, visitor): def accept(self, visitor):
raise NotImplementedError raise NotImplementedError
class Visitor(ABC): class Visitor(ABC):
""" """
Visitor abstract base class to implement Visitor abstract base class to implement
the Visitor pattern the Visitor pattern
""" """
@abstractmethod @abstractmethod
def visit_literal(self, visitor): def visit_literal(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_binary(self, visitor): def visit_binary(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_grouping(self, visitor): def visit_grouping(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_unary(self, visitor): def visit_unary(self, visitor):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def visit_get(self, visitor): def visit_get(self, visitor):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def visit_set(self, visitor): def visit_set(self, visitor):
raise NotImplementedError raise NotImplementedError
@dataclass @dataclass
class Binary(Expression): class Binary(Expression):
left: Expression left: Expression
operator: Token operator: Token
right: Expression right: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_binary(self) return visitor.visit_binary(self)
@dataclass @dataclass
class Unary(Expression): class Unary(Expression):
operator: Token operator: Token
right: Expression right: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_unary(self) return visitor.visit_unary(self)
@dataclass @dataclass
class Literal(Expression): class Literal(Expression):
value: object value: object
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_literal(self) return visitor.visit_literal(self)
@dataclass @dataclass
class Grouping(Expression): class Grouping(Expression):
expr: Expression expr: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_grouping(self) return visitor.visit_grouping(self)
@dataclass @dataclass
class Variable(Expression): class Variable(Expression):
name: Token name: Token
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_var_expr(self) return visitor.visit_var_expr(self)
def __hash__(self): def __hash__(self):
return super().__hash__() return super().__hash__()
@dataclass @dataclass
class Assignment(Expression): class Assignment(Expression):
name: Token name: Token
value: Expression value: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_assign(self) return visitor.visit_assign(self)
def __hash__(self): def __hash__(self):
return super().__hash__() return super().__hash__()
@dataclass @dataclass
class Logical(Expression): class Logical(Expression):
left: Expression left: Expression
operator: Token operator: Token
right: Expression right: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_logical(self) return visitor.visit_logical(self)
@dataclass @dataclass
class Call(Expression): class Call(Expression):
callee: Expression callee: Expression
paren: Token paren: Token
arguments: List[Expression] = () arguments: List[Expression] = ()
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_call_expr(self) return visitor.visit_call_expr(self)
@dataclass @dataclass
class Get(Expression): class Get(Expression):
object: Expression object: Expression
name: Token name: Token
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_get(self) return visitor.visit_get(self)
@dataclass @dataclass
class Set(Expression): class Set(Expression):
object: Expression object: Expression
name: Token name: Token
value: Expression value: Expression
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_set(self) return visitor.visit_set(self)
@dataclass @dataclass
class This(Expression): class This(Expression):
keyword: Token keyword: Token
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_this(self) return visitor.visit_this(self)
def __hash__(self): def __hash__(self):
return super().__hash__() return super().__hash__()
@dataclass @dataclass
class Super(Expression): class Super(Expression):
keyword: Token keyword: Token
method: Token method: Token
def accept(self, visitor): def accept(self, visitor):
return visitor.visit_super(self) return visitor.visit_super(self)
def __hash__(self): def __hash__(self):
return super().__hash__() return super().__hash__()

View File

@ -1,8 +1,8 @@
from enum import Enum, auto from enum import Enum, auto
class FunctionType(Enum): class FunctionType(Enum):
NONE = auto() NONE = auto()
FUNCTION = auto() FUNCTION = auto()
METHOD = auto() METHOD = auto()
INIT = auto() INIT = auto()

View File

@ -1,7 +1,7 @@
from enum import Enum, auto from enum import Enum, auto
class LoopType(Enum): class LoopType(Enum):
NONE = auto() NONE = auto()
WHILE = auto() WHILE = auto()

View File

@ -1,173 +1,173 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass from dataclasses import dataclass
from .expression import Expression, Variable from .expression import Expression, Variable
from .tokenobject import Token from .tokenobject import Token
from typing import List, Any from typing import List, Any
class Statement(object): class Statement(object):
""" """
A Base Class representing JAPL statements A Base Class representing JAPL statements
""" """
def accept(self, visitor): def accept(self, visitor):
raise NotImplementedError raise NotImplementedError
class Visitor(ABC): class Visitor(ABC):
"""Wrapper to implement the Visitor Pattern""" """Wrapper to implement the Visitor Pattern"""
@abstractmethod @abstractmethod
def visit_statement_expr(self, visitor): def visit_statement_expr(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_var_stmt(self, visitor): def visit_var_stmt(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_del(self, visitor): def visit_del(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_block(self, visitor): def visit_block(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_if(self, visitor): def visit_if(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_while(self, visitor): def visit_while(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_break(self, visitor): def visit_break(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_function(self, visitor): def visit_function(self, visitor):
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
def visit_return(self, visitor): def visit_return(self, visitor):
raise NotImplementedError raise NotImplementedError
@staticmethod @staticmethod
def visit_class(self, visitor): def visit_class(self, visitor):
raise NotImplementedError raise NotImplementedError
@dataclass @dataclass
class StatementExpr(Statement): class StatementExpr(Statement):
""" """
An expression statement An expression statement
""" """
expression: Expression expression: Expression
def accept(self, visitor): def accept(self, visitor):
visitor.visit_statement_expr(self) visitor.visit_statement_expr(self)
@dataclass @dataclass
class Var(Statement): class Var(Statement):
""" """
A var statement A var statement
""" """
name: Token name: Token
init: Expression = None init: Expression = None
def accept(self, visitor): def accept(self, visitor):
visitor.visit_var_stmt(self) visitor.visit_var_stmt(self)
@dataclass @dataclass
class Del(Statement): class Del(Statement):
""" """
A del statement A del statement
""" """
name: Any name: Any
def accept(self, visitor): def accept(self, visitor):
visitor.visit_del(self) visitor.visit_del(self)
@dataclass @dataclass
class Block(Statement): class Block(Statement):
"""A block statement""" """A block statement"""
statements: List[Statement] statements: List[Statement]
def accept(self, visitor): def accept(self, visitor):
visitor.visit_block(self) visitor.visit_block(self)
@dataclass @dataclass
class If(Statement): class If(Statement):
"""An if statement""" """An if statement"""
condition: Expression condition: Expression
then_branch: Statement then_branch: Statement
else_branch: Statement else_branch: Statement
def accept(self, visitor): def accept(self, visitor):
visitor.visit_if(self) visitor.visit_if(self)
@dataclass @dataclass
class While(Statement): class While(Statement):
"""A while statement""" """A while statement"""
condition: Expression condition: Expression
body: Statement body: Statement
def accept(self, visitor): def accept(self, visitor):
visitor.visit_while(self) visitor.visit_while(self)
@dataclass @dataclass
class Break(Statement): class Break(Statement):
"""A break statement""" """A break statement"""
token: Token token: Token
def accept(self, visitor): def accept(self, visitor):
visitor.visit_break(self) visitor.visit_break(self)
@dataclass @dataclass
class Function(Statement): class Function(Statement):
"""A function statement""" """A function statement"""
name: Token name: Token
params: List[Token] params: List[Token]
body: List[Statement] body: List[Statement]
def accept(self, visitor): def accept(self, visitor):
visitor.visit_function(self) visitor.visit_function(self)
@dataclass @dataclass
class Return(Statement, BaseException): class Return(Statement, BaseException):
"""A return statement""" """A return statement"""
keyword: Token keyword: Token
value: Expression value: Expression
def accept(self, visitor): def accept(self, visitor):
visitor.visit_return(self) visitor.visit_return(self)
@dataclass @dataclass
class Class(Statement): class Class(Statement):
"""A class statement""" """A class statement"""
name: Token name: Token
methods: list methods: list
superclass: Variable superclass: Variable
def accept(self, visitor): def accept(self, visitor):
visitor.visit_class(self) visitor.visit_class(self)

View File

@ -1,13 +1,13 @@
from dataclasses import dataclass from dataclasses import dataclass
from .tokentype import TokenType from .tokentype import TokenType
@dataclass @dataclass
class Token(object): class Token(object):
"""The representation of a JAPL token""" """The representation of a JAPL token"""
kind: TokenType kind: TokenType
lexeme: str lexeme: str
literal: object literal: object
line: int line: int

View File

@ -1,55 +1,55 @@
from enum import Enum, auto from enum import Enum, auto
class TokenType(Enum): class TokenType(Enum):
""" """
An enumeration for all JAPL types An enumeration for all JAPL types
""" """
LP = auto() LP = auto()
RP = auto() RP = auto()
LB = auto() LB = auto()
RB = auto() RB = auto()
COMMA = auto() COMMA = auto()
DOT = auto() DOT = auto()
PLUS = auto() PLUS = auto()
MINUS = auto() MINUS = auto()
SLASH = auto() SLASH = auto()
SEMICOLON = auto() SEMICOLON = auto()
STAR = auto() STAR = auto()
NEG = auto() NEG = auto()
NE = auto() NE = auto()
EQ = auto() EQ = auto()
DEQ = auto() DEQ = auto()
GT = auto() GT = auto()
LT = auto() LT = auto()
GE = auto() GE = auto()
LE = auto() LE = auto()
MOD = auto() MOD = auto()
POW = auto() POW = auto()
ID = auto() ID = auto()
STR = auto() STR = auto()
NUM = auto() NUM = auto()
AND = auto() AND = auto()
CLASS = auto() CLASS = auto()
ELSE = auto() ELSE = auto()
FOR = auto() FOR = auto()
FUN = auto() FUN = auto()
FALSE = auto() FALSE = auto()
IF = auto() IF = auto()
NIL = auto() NIL = auto()
OR = auto() OR = auto()
RETURN = auto() RETURN = auto()
SUPER = auto() SUPER = auto()
THIS = auto() THIS = auto()
TRUE = auto() TRUE = auto()
VAR = auto() VAR = auto()
WHILE = auto() WHILE = auto()
DEL = auto() DEL = auto()
BREAK = auto() BREAK = auto()
EOF = auto() EOF = auto()

444
JAPL/parser.py Normal file
View File

@ -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

281
JAPL/resolver.py Normal file
View File

@ -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
JAPL/types/__init__.py Normal file
View File

11
JAPL/types/callable.py Normal file
View File

@ -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

24
JAPL/types/instance.py Normal file
View File

@ -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

35
JAPL/types/japlclass.py Normal file
View File

@ -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

175
JAPL/types/native.py Normal file
View File

@ -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

69
JAPL/wrapper.py Normal file
View File

@ -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}")

View File

@ -1,2 +1,66 @@
# japl-python # japl
A POC (proof of concept) of the JAPL language written in Python 3 JAPL is an interpreted, dynamically-typed, garbage-collected and minimalistic programming language with C- and Java-like syntax.
# J.. what?
You may wonder what's the meaning of JAPL: well, it turns out to be an acronym
for __Just Another Programming Language__, but beware! Despite the name, the pronounciation is actually the same as "JPL".
## Some backstory
JAPL is born thanks to the amazing work of Bob Nystrom that wrote a book available completely for free
at [this](https://craftinginterpreters.com) link, where he describes the implementation of a simple language called Lox.
### What has been (or will be) added from Lox
- Possibility to delete variables with the `del` statement (Currently being reworked)
- `break` statement
- `continue` statement
- Multi-line comments (`/* like this */`)
- Nested comments
- Modulo division (`%`) and exponentiation (`**`)
- `OP_CONSTANT_LONG` is implemented
- Differentiation between integers and floating point numbers
- `inf` and `nan` types
- Possibility to have more than 255 locals in scope at any given time
- String slicing, with start:end syntax as well
- Strings are not interned (may change in the future)
- All entities are actually objects, even builtins
- Bitwise operators (AND, OR, XOR, NOT)
- Functions default and keyword arguments (__WIP__)
- A proper import system (__Coming soon__)
- Native asynchronous (`await`/`async fun`) support (__Coming soon__)
- Multiple inheritance (__Coming Soon__)
- Bytecode optimizations such as constant folding and stack caching (__Coming Soon__)
- Arbitrary-precision arithmetic (__Coming soon__)
- Generators (__Coming soon__)
- A standard library with collections, I/O utilities, scientific modules, etc (__Coming soon__)
- Multithreading and multiprocessing support with a global VM Lock like CPython (__Coming soon__)
- Multiple GC implementations which can be chosen at runtime or via CLI: bare refcount, refcount + generational GC, M&S (__Coming soon__)
- Exceptions (__Coming soon__)
- Optional JIT Compilation (__Coming soon__)
- Some syntax changes (maybe), e.g. get rid of semicolons
- Prototypes based system instead of classes (maybe)
Other than that, JAPL features closures, function definitions, classes, inheritance and static scoping. You can check
the provided example `.jpl` files in the repo to find out more about its syntax.
### Disclaimer
This project is currently a WIP (Work in Progress) and is not optimized nor complete.
The first version of the interpreter is written in Python, but a bytecode stack-based VM written in nim is being developed right now.
Also, the design of the language may change at any moment and all the source inside this repo
is alpha code quality, for now.
For other useful information, check the LICENSE file in this repo.
### Contributing
If you want to contribute, feel free to send a PR!
Right now there are some major issues with the virtual machine which need to be addressed
before the development can proceed, and some help is ~~desperately needed~~ greatly appreciated!
You can also contact me using the information available [here](https://github.com/nocturn9x)

125
examples/examples.jpl Normal file
View File

@ -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();

19
examples/factorial.jpl Normal file
View File

@ -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";

12
examples/fib.jpl Normal file
View File

@ -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);

9
japl.py Normal file
View File

@ -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])

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
singledispatchmethod

16
setup.py Normal file
View File

@ -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',
)