mirror of https://github.com/japl-lang/japl.git
Added JAPL 0.1.1
This commit is contained in:
parent
a5dac14f2b
commit
3623efc798
|
@ -0,0 +1,4 @@
|
|||
from .lexer import Lexer
|
||||
from .parser import Parser
|
||||
from .interpreter import Interpreter
|
||||
|
|
@ -0,0 +1,263 @@
|
|||
from .meta.expression import Expression, Variable, Call
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.exceptions import JAPLError, BreakException, ReturnException
|
||||
from .meta.statement import Statement
|
||||
from .meta.environment import Environment
|
||||
from .types.callable import Callable
|
||||
import operator
|
||||
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify
|
||||
from .wrapper import JAPL
|
||||
|
||||
|
||||
class Interpreter(object):
|
||||
"""
|
||||
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.globals = self.environment
|
||||
self.globals.define("clock", Clock())
|
||||
self.globals.define("type", Type())
|
||||
self.globals.define("truthy", Truthy())
|
||||
self.globals.define("stringify", Stringify())
|
||||
self.looping = False
|
||||
self.in_function = False
|
||||
|
||||
def number_operand(self, operator, operand):
|
||||
"""
|
||||
An helper method to check if the operand
|
||||
to a unary operator is a number
|
||||
"""
|
||||
|
||||
if isinstance(operand, (int, float)):
|
||||
return
|
||||
raise JAPLError(operator, f"Unsupported unary operator '{operator.lexeme}' for object of type '{type(operand).__name__}'")
|
||||
|
||||
def compatible_operands(self, operator, left, right):
|
||||
"""
|
||||
Helper method to check types when doing binary
|
||||
operations
|
||||
"""
|
||||
|
||||
if operator.kind == TokenType.SLASH and right == 0:
|
||||
raise JAPLError(operator, "Cannot divide by 0")
|
||||
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
|
||||
if operator.kind not in (TokenType.DEQ, TokenType.NE):
|
||||
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
return
|
||||
elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
||||
return
|
||||
elif operator.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
|
||||
if isinstance(left, str) and isinstance(right, str):
|
||||
return
|
||||
elif isinstance(left, str) and isinstance(right, int):
|
||||
return
|
||||
elif isinstance(left, int) and isinstance(right, str):
|
||||
return
|
||||
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
|
||||
|
||||
def visit_literal(self, visitor: Expression.Visitor):
|
||||
"""
|
||||
Visits a Literal node in the Abstract Syntax Tree,
|
||||
returning its value to the visitor
|
||||
"""
|
||||
|
||||
return visitor.value
|
||||
|
||||
def visit_logical(self, expr):
|
||||
"""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 interprerer's appropiate method to
|
||||
evaluate itself
|
||||
"""
|
||||
|
||||
return expr.accept(self)
|
||||
|
||||
def visit_grouping(self, grouping: Expression):
|
||||
"""
|
||||
Visits a Grouping node in the Abstract Syntax Tree,
|
||||
recursively evaluating its subexpressions
|
||||
"""
|
||||
|
||||
return self.eval(grouping.expr)
|
||||
|
||||
def visit_unary(self, expr: Expression):
|
||||
"""
|
||||
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: Expression):
|
||||
"""
|
||||
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_print(self, stmt: Statement):
|
||||
"""
|
||||
Visits the print statement node in the AST and
|
||||
evaluates its expression before printing it to
|
||||
stdout
|
||||
"""
|
||||
|
||||
val = self.eval(stmt.expression)
|
||||
print(val)
|
||||
|
||||
def visit_statement_expr(self, stmt: Statement):
|
||||
"""Visits an expression statement and evaluates it"""
|
||||
|
||||
self.eval(stmt.expression)
|
||||
|
||||
def visit_if(self, statement):
|
||||
"""Visits an If node and evaluates it"""
|
||||
|
||||
if self.eval(statement.condition):
|
||||
self.exec(statement.then_branch)
|
||||
elif statement.else_branch:
|
||||
self.exec(statement.else_branch)
|
||||
|
||||
def visit_while(self, statement):
|
||||
"""Visits a while node and executes it"""
|
||||
|
||||
self.looping = True
|
||||
while self.eval(statement.condition):
|
||||
try:
|
||||
self.exec(statement.body)
|
||||
except BreakException:
|
||||
break
|
||||
self.looping = False
|
||||
|
||||
def visit_var_stmt(self, stmt: Statement):
|
||||
"""Vitits a var statement"""
|
||||
|
||||
val = None
|
||||
if stmt.init:
|
||||
val = self.eval(stmt.init)
|
||||
self.environment.define(stmt.name.lexeme, val)
|
||||
|
||||
def visit_var_expr(self, expr: Variable):
|
||||
"""Visits a var expression"""
|
||||
|
||||
return self.environment.get(expr.name)
|
||||
|
||||
def visit_del(self, stmt: Statement):
|
||||
"""Visits a del expression"""
|
||||
|
||||
return self.environment.delete(stmt.name)
|
||||
|
||||
def visit_assign(self, visitor):
|
||||
"""Visits an assignment expression"""
|
||||
|
||||
right = self.eval(visitor.value)
|
||||
self.environment.assign(visitor.name, right)
|
||||
|
||||
def visit_block(self, visitor):
|
||||
"""Visits a new scope block"""
|
||||
|
||||
return self.execute_block(visitor.statements, Environment(self.environment))
|
||||
|
||||
def visit_break(self, visitor):
|
||||
"""Visits a break statement"""
|
||||
|
||||
if self.looping:
|
||||
raise BreakException()
|
||||
raise JAPLError(visitor.token, "'break' outside loop")
|
||||
|
||||
def visit_call_expr(self, expr: Expression):
|
||||
"""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, 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):
|
||||
"""Visits a return statement"""
|
||||
|
||||
if self.in_function:
|
||||
value = None
|
||||
if statement.value:
|
||||
value = self.eval(statement.value)
|
||||
raise ReturnException(value)
|
||||
else:
|
||||
raise JAPLError(statement.keyword, "'return' outside function")
|
||||
|
||||
def visit_function(self, statement):
|
||||
"""Visits a function"""
|
||||
|
||||
function = JAPLFunction(statement)
|
||||
self.environment.define(statement.name.lexeme, function)
|
||||
|
||||
def exec(self, statement: Statement):
|
||||
"""Executes a statement"""
|
||||
|
||||
statement.accept(self)
|
||||
|
||||
def interpret(self, statements):
|
||||
"""
|
||||
Executes a JAPL program
|
||||
"""
|
||||
|
||||
for statement in statements:
|
||||
self.exec(statement)
|
|
@ -0,0 +1,190 @@
|
|||
from .objects import Token, TokenType, ParseError
|
||||
from typing import List, Union
|
||||
|
||||
|
||||
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,
|
||||
"print": TokenType.PRINT, "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
|
||||
"""
|
||||
|
||||
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 match(self, char: str) -> bool:
|
||||
"""
|
||||
Returns True if the next 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.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
|
|
@ -0,0 +1,53 @@
|
|||
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 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}'")
|
|
@ -0,0 +1,30 @@
|
|||
from .tokentype import TokenType
|
||||
|
||||
class JAPLError(BaseException):
|
||||
"""JAPL's exceptions base class"""
|
||||
|
||||
def __repr__(self):
|
||||
return self.args[1]
|
||||
|
||||
class ParseError(JAPLError):
|
||||
"""An error occurred while parsing"""
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.args) > 1:
|
||||
token, message = self.args
|
||||
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"""
|
|
@ -0,0 +1,106 @@
|
|||
from dataclasses import dataclass
|
||||
from abc import ABC, abstractmethod
|
||||
from .tokenobject import Token
|
||||
from typing import List
|
||||
|
||||
|
||||
class Expression(ABC):
|
||||
"""
|
||||
An object representing a JAPL expression.
|
||||
This is an abstract base class and is not
|
||||
meant to be instantiated directly, inherit
|
||||
from it instead!
|
||||
"""
|
||||
|
||||
|
||||
class Visitor(ABC):
|
||||
"""
|
||||
Visitor abstract base class to implement
|
||||
the Visitor pattern
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_literal(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_binary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_grouping(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_unary(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
@dataclass
|
||||
class Binary(Expression, Expression.Visitor):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_binary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Unary(Expression, Expression.Visitor):
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_unary(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Literal(Expression, Expression.Visitor):
|
||||
value: object
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_literal(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Grouping(Expression, Expression.Visitor):
|
||||
expr: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_grouping(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Variable(Expression, Expression.Visitor):
|
||||
name: str
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_var_expr(self)
|
||||
|
||||
@dataclass
|
||||
class Assignment(Expression, Expression.Visitor):
|
||||
name: Token
|
||||
value: Expression
|
||||
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_assign(self)
|
||||
|
||||
@dataclass
|
||||
class Logical(Expression, Expression.Visitor):
|
||||
left: Expression
|
||||
operator: Token
|
||||
right: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_logical(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Call(Expression, Expression.Visitor):
|
||||
callee: Expression
|
||||
paren: Token
|
||||
arguments: List[Expression]
|
||||
|
||||
def accept(self, visitor):
|
||||
return visitor.visit_call_expr(self)
|
|
@ -0,0 +1,136 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from .expression import Expression
|
||||
from .tokenobject import Token
|
||||
from typing import List
|
||||
|
||||
|
||||
class Statement(ABC):
|
||||
"""
|
||||
An Abstract Base Class representing
|
||||
JAPL's statements
|
||||
"""
|
||||
|
||||
class Visitor(ABC):
|
||||
"""Wrapper to implement the Visitor Pattern"""
|
||||
|
||||
@abstractmethod
|
||||
def accept(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_print(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
def visit_expr(self, visitor):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass
|
||||
class Print(Statement, Statement.Visitor):
|
||||
"""
|
||||
The print statement
|
||||
"""
|
||||
|
||||
expression: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_print(self)
|
||||
|
||||
@dataclass
|
||||
class StatementExpr(Statement, Statement.Visitor):
|
||||
"""
|
||||
An expression statement
|
||||
"""
|
||||
|
||||
expression: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_statement_expr(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Var(Statement, Statement.Visitor):
|
||||
"""
|
||||
A var statement
|
||||
"""
|
||||
|
||||
name: Token
|
||||
init: Expression = None
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_var_stmt(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Del(Statement, Statement.Visitor):
|
||||
"""
|
||||
A del statement
|
||||
"""
|
||||
|
||||
name: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_del(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Block(Statement, Statement.Visitor):
|
||||
"""A block statement"""
|
||||
|
||||
statements: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_block(self)
|
||||
|
||||
@dataclass
|
||||
class If(Statement, Statement.Visitor):
|
||||
"""An if statement"""
|
||||
|
||||
condition: Expression
|
||||
then_branch: Statement
|
||||
else_branch: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_if(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class While(Statement, Statement.Visitor):
|
||||
"""A while statement"""
|
||||
|
||||
condition: Expression
|
||||
body: Statement
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_while(self)
|
||||
|
||||
@dataclass
|
||||
class Break(Statement, Statement.Visitor):
|
||||
"""A break statement"""
|
||||
|
||||
token: Token
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_break(self)
|
||||
|
||||
@dataclass
|
||||
class Function(Statement, Statement.Visitor):
|
||||
"""A function statement"""
|
||||
|
||||
name: Token
|
||||
params: List[Token]
|
||||
body: List[Statement]
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_function(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Return(Statement, Statement.Visitor, BaseException):
|
||||
"""A return statement"""
|
||||
|
||||
keyword: Token
|
||||
value: Expression
|
||||
|
||||
def accept(self, visitor):
|
||||
visitor.visit_return(self)
|
|
@ -0,0 +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
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
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()
|
||||
PRINT = auto()
|
||||
RETURN = auto()
|
||||
SUPER = auto()
|
||||
THIS = auto()
|
||||
TRUE = auto()
|
||||
VAR = auto()
|
||||
WHILE = auto()
|
||||
DEL = auto()
|
||||
BREAK = auto()
|
||||
|
||||
EOF = auto()
|
|
@ -0,0 +1,5 @@
|
|||
from .meta.statement import Statement, Print
|
||||
from .meta.expression import Expression, Unary, Binary, Grouping, Literal
|
||||
from .meta.exceptions import ParseError, JAPLError
|
||||
from .meta.tokentype import TokenType
|
||||
from .meta.tokenobject import Token
|
|
@ -0,0 +1,422 @@
|
|||
from .objects import Expression, Token, TokenType, Binary, Unary, \
|
||||
Literal, Grouping, ParseError, JAPLError
|
||||
from .meta.statement import Print, StatementExpr, Var, Del, Block, \
|
||||
If, While, Break, Function, Return
|
||||
from .meta.expression import Variable, Assignment, Logical, Call
|
||||
from typing import List
|
||||
|
||||
|
||||
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, message: str, token: Token) -> ParseError:
|
||||
"""Returns ParseError with the given message"""
|
||||
|
||||
return ParseError(message, token)
|
||||
|
||||
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.PRINT,
|
||||
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: list):
|
||||
"""
|
||||
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())
|
||||
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)
|
||||
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)
|
||||
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 print_statement(self):
|
||||
"""Returns a Print AST node"""
|
||||
|
||||
value = self.expression()
|
||||
self.consume(TokenType.SEMICOLON, "Missing semicolon after print statement")
|
||||
return Print(value)
|
||||
|
||||
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.PRINT):
|
||||
return self.print_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")
|
||||
parameters.append(self.consume(TokenType.ID, "Expecting parameter name"))
|
||||
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 declaration(self):
|
||||
"""Parses a declaration"""
|
||||
|
||||
try:
|
||||
if 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,29 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
|
||||
class CallableBase(ABC):
|
||||
"""Abstract base class for callables"""
|
||||
|
||||
def __init__(self, arity):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity: int = arity
|
||||
|
||||
@abstractmethod
|
||||
def call(self, interpreter, arguments):
|
||||
"""Calls the callable"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Callable(CallableBase):
|
||||
"""A generic callable"""
|
||||
|
||||
def call(self):
|
||||
...
|
||||
|
||||
def __init__(self, arity):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity: int = arity
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
from .callable import Callable
|
||||
import time
|
||||
from ..meta.environment import Environment
|
||||
from ..meta.exceptions import ReturnException
|
||||
|
||||
|
||||
class Clock(Callable):
|
||||
"""JAPL's wrapper around time.time"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""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, *args):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 1
|
||||
|
||||
def call(self, _, obj):
|
||||
return type(obj[0])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<built-in function type>"
|
||||
|
||||
|
||||
class JAPLFunction(Callable):
|
||||
"""A generic wrapper for user-defined functions"""
|
||||
|
||||
def __init__(self, declaration):
|
||||
"""Object constructor"""
|
||||
|
||||
self.declaration = declaration
|
||||
self.arity = len(self.declaration.params)
|
||||
|
||||
def call(self, interpreter, arguments):
|
||||
scope = Environment(interpreter.globals)
|
||||
for name, value in zip(self.declaration.params, arguments):
|
||||
scope.define(name.lexeme, value)
|
||||
interpreter.in_function = True
|
||||
try:
|
||||
interpreter.execute_block(self.declaration.body, scope)
|
||||
except ReturnException as error:
|
||||
interpreter.in_function = False
|
||||
return error.args[0]
|
||||
interpreter.in_function = False
|
||||
|
||||
def __repr__(self):
|
||||
return f"<function {self.declaration.name.lexeme}>"
|
||||
|
||||
|
||||
class Truthy(Callable):
|
||||
"""JAPL's wrapper around bool"""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""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, *args):
|
||||
"""Object constructor"""
|
||||
|
||||
self.arity = 1
|
||||
|
||||
def call(self, _, obj):
|
||||
return str(obj[0])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<built-in function stringify>"
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
from JAPL.lexer import Lexer
|
||||
from JAPL.meta.exceptions import ParseError, JAPLError
|
||||
from sys import stderr
|
||||
|
||||
|
||||
class JAPL(object):
|
||||
"""Wrapper around JAPL's interpreter, lexer and parser"""
|
||||
|
||||
def run(self, file: str):
|
||||
"""Runs a file"""
|
||||
|
||||
from JAPL.parser import Parser # Avoids circular imports
|
||||
from JAPL.interpreter import Interpreter
|
||||
|
||||
self.interpreter = Interpreter()
|
||||
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.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{err}")
|
||||
|
||||
def repl(self):
|
||||
"""Starts an interactive REPL"""
|
||||
|
||||
from JAPL.parser import Parser # Avoids circular imports
|
||||
from JAPL.interpreter import Interpreter
|
||||
|
||||
self.interpreter = Interpreter()
|
||||
print("[JAPL 0.1.1 - Interactive REPL]")
|
||||
while True:
|
||||
try:
|
||||
source = input(">>> ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
exit()
|
||||
if not source:
|
||||
continue
|
||||
lexer = Lexer(source)
|
||||
try:
|
||||
tokens = lexer.lex()
|
||||
except ParseError as err:
|
||||
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
|
||||
else:
|
||||
try:
|
||||
ast = Parser(tokens).parse()
|
||||
except ParseError as err:
|
||||
token, message = err.args
|
||||
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
else:
|
||||
try:
|
||||
result = self.interpreter.interpret(ast)
|
||||
except JAPLError as error:
|
||||
token, message = error.args
|
||||
print(f"A runtime exception occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
else:
|
||||
if result is not None:
|
||||
print(repr(result))
|
|
@ -0,0 +1,51 @@
|
|||
// Example file to test JAPL's syntax
|
||||
|
||||
// Mathematical expressions
|
||||
|
||||
2 + 2;
|
||||
-1 * 6;
|
||||
3 * (9 / 2); // Parentheses for grouping
|
||||
|
||||
// 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
|
||||
print foo;
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
// Control flow statements
|
||||
|
||||
for (var i = 0; i < 10; i = i + 1) { // For loops
|
||||
print i;
|
||||
}
|
||||
|
||||
var n = 0;
|
||||
while (n <= 10) { // While loops
|
||||
if (n <= 5) { // If statements
|
||||
print n;
|
||||
}
|
||||
n = n + 1;
|
||||
}
|
||||
|
||||
print clock(); // Function calls
|
||||
|
||||
fun count(n) { // Function definitions
|
||||
if (n > 1) count(n - 1);
|
||||
print n;
|
||||
}
|
||||
|
||||
count(3);
|
|
@ -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,10 @@
|
|||
// A recursive implementation of the fibonacci sequence
|
||||
|
||||
fun fib(n) {
|
||||
if (n <= 1) return n;
|
||||
return fib(n - 2) + fib(n - 1);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 20; i = i + 1) {
|
||||
print fib(i);
|
||||
}
|
|
@ -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,39 @@
|
|||
from JAPL.lexer import Lexer
|
||||
from JAPL.parser import Parser
|
||||
from JAPL.interpreter import Interpreter
|
||||
from JAPL.objects import ParseError, JAPLError
|
||||
|
||||
|
||||
|
||||
def repl():
|
||||
interpreter = Interpreter()
|
||||
print("[JAPL 0.1.1 - Interactive REPL]")
|
||||
while True:
|
||||
try:
|
||||
source = input(">>> ")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
print()
|
||||
exit()
|
||||
if source:
|
||||
lexer = Lexer(source)
|
||||
try:
|
||||
tokens = lexer.lex()
|
||||
except ParseError as err:
|
||||
print(f"\nAn error occurred, details below\n\nParseError: {err.args[0]}")
|
||||
else:
|
||||
if tokens:
|
||||
try:
|
||||
ast = Parser(tokens).parse()
|
||||
except ParseError as err:
|
||||
token, message = err.args
|
||||
print(f"An error occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
else:
|
||||
if ast:
|
||||
try:
|
||||
result = interpreter.interpret(ast)
|
||||
except JAPLError as error:
|
||||
token, message = error.args
|
||||
print(f"A runtime error occurred at line {token.line} at '{token.lexeme}': {message}")
|
||||
else:
|
||||
if result is not None:
|
||||
print(repr(result))
|
|
@ -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