Significant code refactoring, improved readability and standard compliance by adding more (and more specific) type hints. Revised visitor pattern

This commit is contained in:
nocturn9x 2020-07-30 15:38:33 +02:00
parent ff346d9559
commit 71a73d010b
13 changed files with 256 additions and 203 deletions

4
.gitignore vendored
View File

@ -127,3 +127,7 @@ dmypy.json
# Pyre type checker
.pyre/
# PyCharm
.idea/

View File

@ -1,11 +1,12 @@
from .meta.expression import Expression, Variable, Call
from .meta.tokentype import TokenType
from .meta.exceptions import JAPLError, BreakException, ReturnException
from .meta.statement import Statement
from .meta.environment import Environment
from .types.callable import Callable
import operator
from typing import List
from .types.callable import Callable
from .meta.environment import Environment
from .meta.tokentype import TokenType
from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify
from .meta.exceptions import JAPLError, BreakException, ReturnException
from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call
from .meta.statement import Statement, Print, StatementExpr, If, While, Del, Break, Return, Var, Block, Function
class Interpreter(object):
@ -14,19 +15,10 @@ class Interpreter(object):
programming language
"""
OPS = {TokenType.MINUS: operator.sub,
TokenType.PLUS: operator.add,
TokenType.SLASH: operator.truediv,
TokenType.STAR: operator.mul,
TokenType.DEQ: operator.eq,
TokenType.GT: operator.gt,
TokenType.GE: operator.ge,
TokenType.LT: operator.lt,
TokenType.LE: operator.le,
TokenType.EQ: operator.eq,
TokenType.NE: operator.ne,
TokenType.MOD: operator.mod,
TokenType.POW: operator.pow}
OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv,
TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt,
TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq,
TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow}
def __init__(self):
"""Object constructor"""
@ -41,7 +33,7 @@ class Interpreter(object):
self.looping = False
self.in_function = False
def number_operand(self, operator, operand):
def number_operand(self, op, operand):
"""
An helper method to check if the operand
to a unary operator is a number
@ -49,40 +41,41 @@ class Interpreter(object):
if isinstance(operand, (int, float)):
return
raise JAPLError(operator, f"Unsupported unary operator '{operator.lexeme}' for object of type '{type(operand).__name__}'")
raise JAPLError(op,
f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'")
def compatible_operands(self, operator, left, right):
def compatible_operands(self, op, left, right):
"""
Helper method to check types when doing binary
operations
"""
if operator.kind == TokenType.SLASH and right == 0:
raise JAPLError(operator, "Cannot divide by 0")
if op.kind == TokenType.SLASH and right == 0:
raise JAPLError(op, "Cannot divide by 0")
elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))):
if operator.kind not in (TokenType.DEQ, TokenType.NE):
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
if op.kind not in (TokenType.DEQ, TokenType.NE):
raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
return
elif isinstance(left, (int, float)) and isinstance(right, (int, float)):
return
elif operator.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE):
if isinstance(left, str) and isinstance(right, str):
return
elif isinstance(left, str) and isinstance(right, int):
return
elif isinstance(left, int) and isinstance(right, str):
return
raise JAPLError(operator, f"Unsupported binary operator '{operator.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'")
def visit_literal(self, visitor: Expression.Visitor):
def visit_literal(self, expr: Literal):
"""
Visits a Literal node in the Abstract Syntax Tree,
returning its value to the visitor
"""
return visitor.value
return expr.value
def visit_logical(self, expr):
def visit_logical(self, expr: Logical):
"""Visits a logical node"""
left = self.eval(expr.left)
@ -98,13 +91,13 @@ class Interpreter(object):
Evaluates an expression by calling its accept()
method and passing self to it. This mechanism is known
as the 'Visitor Pattern': the expression object will
later call the interprerer's appropiate method to
later call the interpreter's appropriate method to
evaluate itself
"""
return expr.accept(self)
def visit_grouping(self, grouping: Expression):
def visit_grouping(self, grouping: Grouping):
"""
Visits a Grouping node in the Abstract Syntax Tree,
recursively evaluating its subexpressions
@ -112,7 +105,7 @@ class Interpreter(object):
return self.eval(grouping.expr)
def visit_unary(self, expr: Expression):
def visit_unary(self, expr: Unary):
"""
Visits a Unary node in the Abstract Syntax Teee,
returning the negation of the given object, if
@ -125,7 +118,7 @@ class Interpreter(object):
return not right
return -right
def visit_binary(self, expr: Expression):
def visit_binary(self, expr: Binary):
"""
Visits a Binary node in the Abstract Syntax Tree,
recursively evaulating both operands first and then
@ -137,31 +130,37 @@ class Interpreter(object):
self.compatible_operands(expr.operator, left, right)
return self.OPS[expr.operator.kind](left, right)
def visit_print(self, stmt: Statement):
def visit_print(self, stmt: Print):
"""
Visits the print statement node in the AST and
evaluates its expression before printing it to
stdout
Visits the print statement node in the AST and
evaluates its expression before printing it to
stdout
"""
val = self.eval(stmt.expression)
print(val)
def visit_statement_expr(self, stmt: Statement):
"""Visits an expression statement and evaluates it"""
def visit_statement_expr(self, stmt: StatementExpr):
"""
Visits an expression statement and evaluates it
"""
self.eval(stmt.expression)
def visit_if(self, statement):
"""Visits an If node and evaluates it"""
def visit_if(self, statement: If):
"""
Visits an If node and evaluates it
"""
if self.eval(statement.condition):
self.exec(statement.then_branch)
elif statement.else_branch:
self.exec(statement.else_branch)
def visit_while(self, statement):
"""Visits a while node and executes it"""
def visit_while(self, statement: While):
"""
Visits a while node and executes it
"""
self.looping = True
while self.eval(statement.condition):
@ -171,16 +170,20 @@ class Interpreter(object):
break
self.looping = False
def visit_var_stmt(self, stmt: Statement):
"""Vitits a var statement"""
def visit_var_stmt(self, stmt: Var):
"""
Visits a var statement
"""
val = None
if stmt.init:
val = self.eval(stmt.init)
self.environment.define(stmt.name.lexeme, val)
def lookup(self, name, expr):
"""Performs name lookups in the closest scope"""
def lookup(self, name, expr: Expression):
"""
Performs name lookups in the closest scope
"""
distance = self.locals.get(expr)
if distance is not None:
@ -189,40 +192,52 @@ class Interpreter(object):
return self.globals.get(name)
def visit_var_expr(self, expr: Variable):
"""Visits a var expression"""
"""
Visits a var expression
"""
return self.lookup(expr.name, expr)
def visit_del(self, stmt: Statement):
"""Visits a del expression"""
def visit_del(self, stmt: Del):
"""
Visits a del expression
"""
return self.environment.delete(stmt.name)
def visit_assign(self, visitor):
"""Visits an assignment expression"""
def visit_assign(self, stmt: Assignment):
"""
Visits an assignment expression
"""
right = self.eval(visitor.value)
distance = self.locals.get(visitor)
right = self.eval(stmt.value)
distance = self.locals.get(stmt)
if distance is not None:
self.environment.assign_at(distance, visitor.name, right)
self.environment.assign_at(distance, stmt.name, right)
else:
self.globals.assign(visitor.name, right)
self.globals.assign(stmt.name, right)
return right
def visit_block(self, visitor):
"""Visits a new scope block"""
def visit_block(self, stmt: Block):
"""
Visits a new scope block
"""
return self.execute_block(visitor.statements, Environment(self.environment))
return self.execute_block(stmt.statements, Environment(self.environment))
def visit_break(self, visitor):
"""Visits a break statement"""
def visit_break(self, stmt: Break):
"""
Visits a break statement
"""
if self.looping:
raise BreakException()
raise JAPLError(visitor.token, "'break' outside loop")
raise JAPLError(stmt.token, "'break' outside loop")
def visit_call_expr(self, expr: Expression):
"""Visits a call expression"""
def visit_call_expr(self, expr: Call):
"""
Visits a call expression
"""
callee = self.eval(expr.callee)
if not isinstance(callee, Callable):
@ -235,8 +250,10 @@ class Interpreter(object):
raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}")
return function.call(self, arguments)
def execute_block(self, statements, scope: Environment):
"""Executes a block of statements"""
def execute_block(self, statements: List[Statement], scope: Environment):
"""
Executes a block of statements
"""
prev = self.environment
try:
@ -246,8 +263,10 @@ class Interpreter(object):
finally:
self.environment = prev
def visit_return(self, statement):
"""Visits a return statement"""
def visit_return(self, statement: Return):
"""
Visits a return statement
"""
if self.in_function:
value = None
@ -257,18 +276,22 @@ class Interpreter(object):
else:
raise JAPLError(statement.keyword, "'return' outside function")
def visit_function(self, statement):
"""Visits a function"""
def visit_function(self, statement: Function):
"""
Visits a function
"""
function = JAPLFunction(statement, self.environment)
self.environment.define(statement.name.lexeme, function)
def exec(self, statement: Statement):
"""Executes a statement"""
"""
Executes a statement
"""
statement.accept(self)
def interpret(self, statements):
def interpret(self, statements: List[Statement]):
"""
Executes a JAPL program
"""
@ -276,7 +299,11 @@ class Interpreter(object):
for statement in statements:
self.exec(statement)
def resolve(self, expr, depth):
"""Stores the result of the name resolution"""
def resolve(self, expr: Expression, depth: int):
"""
Stores the result of the name resolution: this
info will be used later to know exactly in which
environment to look up a given variable
"""
self.locals[expr] = depth # How many environments to skip!
self.locals[expr] = depth # How many environments to skip!

View File

@ -1,5 +1,7 @@
from .objects import Token, TokenType, ParseError
from typing import List, Union
from .meta.tokenobject import Token
from .meta.tokentype import TokenType
from .meta.exceptions import ParseError
from typing import List
class Lexer(object):
@ -10,7 +12,6 @@ class Lexer(object):
are caught here as well.
"""
TOKENS = {"(": TokenType.LP, ")": TokenType.RP,
"{": TokenType.LB, "}": TokenType.RB,
".": TokenType.DOT, ",": TokenType.COMMA,
@ -30,7 +31,6 @@ class Lexer(object):
"this": TokenType.THIS, "super": TokenType.SUPER,
"del": TokenType.DEL, "break": TokenType.BREAK}
def __init__(self, source: str):
"""Object constructor"""
@ -182,7 +182,6 @@ class Lexer(object):
and returns a list of tokens
"""
while not self.done():
self.start = self.current
self.scan_token()

View File

@ -1,17 +1,19 @@
from .tokentype import TokenType
class JAPLError(BaseException):
"""JAPL's exceptions base class"""
def __repr__(self):
return self.args[1]
class ParseError(JAPLError):
"""An error occurred while parsing"""
def __repr__(self):
if len(self.args) > 1:
token, message = self.args
message, token = self.args
if token.kind == TokenType.EOF:
return f"Unexpected error while parsing at line {token.line}, at end: {message}"
else:

View File

@ -4,14 +4,15 @@ from .tokenobject import Token
from typing import List
class Expression(ABC):
class Expression(object):
"""
An object representing a JAPL expression.
This is an abstract base class and is not
meant to be instantiated directly, inherit
from it instead!
This class is not meant to be instantiated directly,
inherit from it instead!
"""
def accept(self, visitor):
raise NotImplementedError
class Visitor(ABC):
"""
@ -20,23 +21,24 @@ class Expression(ABC):
"""
@abstractmethod
def accept(self, visitor):
raise NotImplementedError
def visit_literal(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_binary(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_grouping(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_unary(self, visitor):
raise NotImplementedError
@dataclass
class Binary(Expression, Expression.Visitor):
class Binary(Expression):
left: Expression
operator: Token
right: Expression
@ -46,7 +48,7 @@ class Binary(Expression, Expression.Visitor):
@dataclass
class Unary(Expression, Expression.Visitor):
class Unary(Expression):
operator: Token
right: Expression
@ -55,7 +57,7 @@ class Unary(Expression, Expression.Visitor):
@dataclass
class Literal(Expression, Expression.Visitor):
class Literal(Expression):
value: object
def accept(self, visitor):
@ -63,7 +65,7 @@ class Literal(Expression, Expression.Visitor):
@dataclass
class Grouping(Expression, Expression.Visitor):
class Grouping(Expression):
expr: Expression
def accept(self, visitor):
@ -71,29 +73,29 @@ class Grouping(Expression, Expression.Visitor):
@dataclass
class Variable(Expression, Expression.Visitor):
class Variable(Expression):
name: Token
def accept(self, visitor):
return visitor.visit_var_expr(self)
def __hash__(self):
return hash(self.name.lexeme)
return super().__hash__()
@dataclass
class Assignment(Expression, Expression.Visitor):
class Assignment(Expression):
name: Token
value: Expression
def accept(self, visitor):
return visitor.visit_assign(self)
def __hash__(self):
return hash(self.name.lexeme)
return super().__hash__()
@dataclass
class Logical(Expression, Expression.Visitor):
class Logical(Expression):
left: Expression
operator: Token
right: Expression
@ -103,7 +105,7 @@ class Logical(Expression, Expression.Visitor):
@dataclass
class Call(Expression, Expression.Visitor):
class Call(Expression):
callee: Expression
paren: Token
arguments: List[Expression] = ()

View File

@ -2,25 +2,63 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass
from .expression import Expression
from .tokenobject import Token
from typing import List
from typing import List, Any
class Statement(ABC):
class Statement(object):
"""
An Abstract Base Class representing
JAPL's statements
A Base Class representing JAPL statements
"""
def accept(self, visitor):
raise NotImplementedError
class Visitor(ABC):
"""Wrapper to implement the Visitor Pattern"""
@abstractmethod
def accept(self, visitor):
def visit_print(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_statement_expr(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_var_stmt(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_del(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_block(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_if(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_while(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_break(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_function(self, visitor):
raise NotImplementedError
@abstractmethod
def visit_return(self, visitor):
raise NotImplementedError
@dataclass
class Print(Statement, Statement.Visitor):
class Print(Statement):
"""
The print statement
"""
@ -32,7 +70,7 @@ class Print(Statement, Statement.Visitor):
@dataclass
class StatementExpr(Statement, Statement.Visitor):
class StatementExpr(Statement):
"""
An expression statement
"""
@ -44,7 +82,7 @@ class StatementExpr(Statement, Statement.Visitor):
@dataclass
class Var(Statement, Statement.Visitor):
class Var(Statement):
"""
A var statement
"""
@ -57,19 +95,19 @@ class Var(Statement, Statement.Visitor):
@dataclass
class Del(Statement, Statement.Visitor):
class Del(Statement):
"""
A del statement
"""
name: Token
name: Any
def accept(self, visitor):
visitor.visit_del(self)
@dataclass
class Block(Statement, Statement.Visitor):
class Block(Statement):
"""A block statement"""
statements: List[Statement]
@ -77,8 +115,9 @@ class Block(Statement, Statement.Visitor):
def accept(self, visitor):
visitor.visit_block(self)
@dataclass
class If(Statement, Statement.Visitor):
class If(Statement):
"""An if statement"""
condition: Expression
@ -90,7 +129,7 @@ class If(Statement, Statement.Visitor):
@dataclass
class While(Statement, Statement.Visitor):
class While(Statement):
"""A while statement"""
condition: Expression
@ -100,8 +139,9 @@ class While(Statement, Statement.Visitor):
print("porcodio")
visitor.visit_while(self)
@dataclass
class Break(Statement, Statement.Visitor):
class Break(Statement):
"""A break statement"""
token: Token
@ -109,8 +149,9 @@ class Break(Statement, Statement.Visitor):
def accept(self, visitor):
visitor.visit_break(self)
@dataclass
class Function(Statement, Statement.Visitor):
class Function(Statement):
"""A function statement"""
name: Token
@ -122,7 +163,7 @@ class Function(Statement, Statement.Visitor):
@dataclass
class Return(Statement, Statement.Visitor, BaseException):
class Return(Statement, BaseException):
"""A return statement"""
keyword: Token

View File

@ -1,5 +0,0 @@
from .meta.statement import Statement, Print
from .meta.expression import Expression, Unary, Binary, Grouping, Literal
from .meta.exceptions import ParseError, JAPLError
from .meta.tokentype import TokenType
from .meta.tokenobject import Token

View File

@ -1,9 +1,10 @@
from .objects import Expression, Token, TokenType, Binary, Unary, \
Literal, Grouping, ParseError, JAPLError
from .meta.statement import Print, StatementExpr, Var, Del, Block, \
If, While, Break, Function, Return
from .meta.expression import Variable, Assignment, Logical, Call
from typing import List
from .meta.exceptions import ParseError
from .meta.tokentype import TokenType
from .meta.tokenobject import Token
from typing import List, Union
from .meta.expression import Variable, Assignment, Logical, Call, Expression, Binary, Unary, Literal, Grouping, Expression
from .meta.statement import Print, StatementExpr, Var, Del, Block, If, While, Break, Function, Return, Statement
class Parser(object):
@ -26,7 +27,7 @@ class Parser(object):
return True
return False
def throw(self, message: str, token: Token) -> ParseError:
def throw(self, token: Token, message: str) -> ParseError:
"""Returns ParseError with the given message"""
return ParseError(message, token)
@ -41,10 +42,10 @@ class Parser(object):
break
else:
token_type = self.peek().kind
if token_type in (TokenType.IF, TokenType.CLASS,
TokenType.VAR, TokenType.FOR,
TokenType.WHILE, TokenType.PRINT,
TokenType.RETURN, TokenType.FUN):
if token_type in (
TokenType.IF, TokenType.CLASS, TokenType.VAR, TokenType.FOR, TokenType.WHILE, TokenType.PRINT,
TokenType.RETURN, TokenType.FUN
):
return
self.step()
@ -63,11 +64,13 @@ class Parser(object):
return self.tokens[self.current - 1]
def done(self):
"""Returns True if we reached EOF"""
"""
Returns True if we reached EOF
"""
return self.peek().kind == TokenType.EOF
return self.peek().kind == TokenType.EOF
def match(self, *types: list):
def match(self, *types: Union[TokenType, List[TokenType]]):
"""
Checks if the current token matches
any of the given token type(s)
@ -81,16 +84,15 @@ class Parser(object):
def consume(self, token_type, message: str):
"""
Consumes a token, raises an error
with the given message if the current token
differs from the expected one
Consumes a token, raises an error
with the given message if the current token
differs from the expected one
"""
if self.check(token_type):
return self.step()
raise self.throw(self.peek(), message)
def primary(self):
"""Parses unary expressions (literals)"""
@ -184,9 +186,7 @@ class Parser(object):
"""
expr: Expression = self.addition()
while self.match(TokenType.GT, TokenType.GE,
TokenType.LT, TokenType.LE,
TokenType.NE):
while self.match(TokenType.GT, TokenType.GE, TokenType.LT, TokenType.LE, TokenType.NE):
operator: Token = self.previous()
right: Expression = self.addition()
expr = Binary(expr, operator, right)
@ -371,14 +371,14 @@ class Parser(object):
return self.expression_statement()
def var_declaration(self):
"""Parses a var declaration"""
"""Parses a var declaration"""
name = self.consume(TokenType.ID, "Expecting a variable name")
init = None
if self.match(TokenType.EQ):
init = self.expression()
self.consume(TokenType.SEMICOLON, "Missing semicolon after declaration")
return Var(name, init)
name = self.consume(TokenType.ID, "Expecting a variable name")
init = None
if self.match(TokenType.EQ):
init = self.expression()
self.consume(TokenType.SEMICOLON, "Missing semicolon after declaration")
return Var(name, init)
def function(self, kind: str):
"""Parses a function declaration"""

View File

@ -1,7 +1,10 @@
from .meta.exceptions import JAPLError
from .meta.expression import Expression
from .meta.statement import Statement
from functools import singledispatchmethod
try:
from functools import singledispatchmethod
except ImportError:
from singledispatchmethod import singledispatchmethod
from typing import List, Union
from collections import deque

View File

@ -1,29 +1,11 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
class CallableBase(ABC):
"""Abstract base class for callables"""
def __init__(self, arity):
"""Object constructor"""
self.arity: int = arity
@abstractmethod
def call(self, interpreter, arguments):
"""Calls the callable"""
raise NotImplementedError
class Callable(CallableBase):
class Callable(object):
"""A generic callable"""
def call(self):
...
def call(self, interpreter, arguments):
raise NotImplementedError
def __init__(self, arity):
"""Object constructor"""
self.arity: int = arity
self.arity = arity

View File

@ -7,7 +7,7 @@ from ..meta.exceptions import ReturnException
class Clock(Callable):
"""JAPL's wrapper around time.time"""
def __init__(self, *args):
def __init__(self, *_):
"""Object constructor"""
self.arity = 0
@ -22,7 +22,7 @@ class Clock(Callable):
class Type(Callable):
"""JAPL's wrapper around type"""
def __init__(self, *args):
def __init__(self, *_):
"""Object constructor"""
self.arity = 1
@ -38,11 +38,11 @@ class JAPLFunction(Callable):
"""A generic wrapper for user-defined functions"""
def __init__(self, declaration, closure):
"""Object constructor"""
"""Object constructor"""
self.declaration = declaration
self.arity = len(self.declaration.params)
self.closure = closure
self.declaration = declaration
self.arity = len(self.declaration.params)
self.closure = closure
def call(self, interpreter, arguments):
scope = Environment(self.closure)
@ -63,7 +63,7 @@ class JAPLFunction(Callable):
class Truthy(Callable):
"""JAPL's wrapper around bool"""
def __init__(self, *args):
def __init__(self, *_):
"""Object constructor"""
self.arity = 1
@ -78,7 +78,7 @@ class Truthy(Callable):
class Stringify(Callable):
"""JAPL's wrapper around str()"""
def __init__(self, *args):
def __init__(self, *_):
"""Object constructor"""
self.arity = 1
@ -88,4 +88,3 @@ class Stringify(Callable):
def __repr__(self):
return f"<built-in function stringify>"

View File

@ -1,7 +1,6 @@
from JAPL.lexer import Lexer
from JAPL.meta.exceptions import ParseError, JAPLError
from JAPL.resolver import Resolver
from sys import stderr
from JAPL.parser import Parser
from JAPL.interpreter import Interpreter
@ -9,7 +8,6 @@ from JAPL.interpreter import Interpreter
class JAPL(object):
"""Wrapper around JAPL's interpreter, lexer and parser"""
interpreter = Interpreter()
resolver = Resolver(interpreter)
@ -34,7 +32,7 @@ class JAPL(object):
print(f"Error' '{file}', permission denied")
except JAPLError as err:
if len(err.args) == 2:
token, message = err.args
message, token = err.args
print(f"An exception occurred at line {token.line}, file '{file}' at '{token.lexeme}': {message}")
else:
print(f"An exception occurred, details below\n\n{err}")
@ -49,27 +47,27 @@ class JAPL(object):
source = input(">>> ")
except (EOFError, KeyboardInterrupt):
print()
exit()
if not source:
continue
lexer = Lexer(source)
try:
tokens = lexer.lex()
except ParseError as err:
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
else:
if not source:
continue
lexer = Lexer(source)
try:
ast = Parser(tokens).parse()
tokens = lexer.lex()
except ParseError as err:
token, message = err.args
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
print(f"\nAn exception occurred, details below\n\nParseError: {err.args[0]}")
else:
try:
self.resolver.resolve(ast)
result = self.interpreter.interpret(ast)
except JAPLError as error:
token, message = error.args
print(f"A runtime exception occurred at line {token.line} at '{token.lexeme}': {message}")
ast = Parser(tokens).parse()
except ParseError as err:
message, token = err.args
print(f"An exception occurred at line {token.line} at '{token.lexeme}': {message}")
else:
if result is not None:
print(repr(result))
try:
self.resolver.resolve(ast)
result = self.interpreter.interpret(ast)
except JAPLError as error:
token, message = error.args
print(f"A runtime exception occurred at line {token.line} at '{token.lexeme}': {message}")
else:
if result is not None:
print(repr(result))

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
singledispatchmethod