diff --git a/.gitignore b/.gitignore index b6e4761..4ec727d 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,7 @@ dmypy.json # Pyre type checker .pyre/ + +# PyCharm + +.idea/ \ No newline at end of file diff --git a/JAPL/interpreter.py b/JAPL/interpreter.py index 99d64ea..38da48a 100644 --- a/JAPL/interpreter.py +++ b/JAPL/interpreter.py @@ -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! diff --git a/JAPL/lexer.py b/JAPL/lexer.py index d9e1410..027829f 100644 --- a/JAPL/lexer.py +++ b/JAPL/lexer.py @@ -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() diff --git a/JAPL/meta/exceptions.py b/JAPL/meta/exceptions.py index 5914a86..de9f18f 100644 --- a/JAPL/meta/exceptions.py +++ b/JAPL/meta/exceptions.py @@ -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: diff --git a/JAPL/meta/expression.py b/JAPL/meta/expression.py index a8969e3..2c0260f 100644 --- a/JAPL/meta/expression.py +++ b/JAPL/meta/expression.py @@ -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] = () diff --git a/JAPL/meta/statement.py b/JAPL/meta/statement.py index e4f35c1..62711d7 100644 --- a/JAPL/meta/statement.py +++ b/JAPL/meta/statement.py @@ -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 diff --git a/JAPL/objects.py b/JAPL/objects.py deleted file mode 100644 index 7af2638..0000000 --- a/JAPL/objects.py +++ /dev/null @@ -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 diff --git a/JAPL/parser.py b/JAPL/parser.py index fe66489..7c6f1cb 100644 --- a/JAPL/parser.py +++ b/JAPL/parser.py @@ -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""" diff --git a/JAPL/resolver.py b/JAPL/resolver.py index f265f72..f6bb5e6 100644 --- a/JAPL/resolver.py +++ b/JAPL/resolver.py @@ -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 diff --git a/JAPL/types/callable.py b/JAPL/types/callable.py index 7e7bc1f..46bada0 100644 --- a/JAPL/types/callable.py +++ b/JAPL/types/callable.py @@ -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 diff --git a/JAPL/types/native.py b/JAPL/types/native.py index 2ae0b23..09a9fdf 100644 --- a/JAPL/types/native.py +++ b/JAPL/types/native.py @@ -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"" - diff --git a/JAPL/wrapper.py b/JAPL/wrapper.py index c76d73b..80ba830 100644 --- a/JAPL/wrapper.py +++ b/JAPL/wrapper.py @@ -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)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e6ab583 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +singledispatchmethod \ No newline at end of file