diff --git a/lox/Expr.py b/lox/Expr.py index e2c3604..f64f067 100644 --- a/lox/Expr.py +++ b/lox/Expr.py @@ -1,6 +1,7 @@ class Expr: pass + class Binary(Expr): def __init__(self, left, operator, right): self.left = left @@ -10,6 +11,7 @@ def __init__(self, left, operator, right): def accept(self, visitor): return visitor.visit_BinaryExpr(self) + class Grouping(Expr): def __init__(self, expression): self.expression = expression @@ -17,6 +19,7 @@ def __init__(self, expression): def accept(self, visitor): return visitor.visit_GroupingExpr(self) + class Literal(Expr): def __init__(self, value): self.value = value @@ -24,6 +27,7 @@ def __init__(self, value): def accept(self, visitor): return visitor.visit_LiteralExpr(self) + class Unary(Expr): def __init__(self, operator, right): self.operator = operator @@ -32,6 +36,7 @@ def __init__(self, operator, right): def accept(self, visitor): return visitor.visit_UnaryExpr(self) + class Variable(Expr): def __init__(self, name): self.name = name @@ -39,6 +44,7 @@ def __init__(self, name): def accept(self, visitor): return visitor.visit_VariableExpr(self) + class Assign(Expr): def __init__(self, name, value): self.name = name @@ -47,6 +53,7 @@ def __init__(self, name, value): def accept(self, visitor): return visitor.visit_AssignExpr(self) + class Logical(Expr): def __init__(self, left, operator, right): self.left = left @@ -56,6 +63,7 @@ def __init__(self, left, operator, right): def accept(self, visitor): return visitor.visit_LogicalExpr(self) + class Call(Expr): def __init__(self, callee, paren, arguments): self.callee = callee diff --git a/lox/LoxFunction.py b/lox/LoxFunction.py index 89e0b68..d941757 100644 --- a/lox/LoxFunction.py +++ b/lox/LoxFunction.py @@ -2,6 +2,7 @@ from lox.LoxCallable import LoxCallable from lox.loxreturn import Return + class LoxFunction(LoxCallable): def __init__(self, declaration, closure): self.declaration = declaration @@ -10,8 +11,7 @@ def __init__(self, declaration, closure): def __call__(self, interpreter, arguments): self.environment = Environment(self.closure) for i in range(len(self.declaration.params)): - self.environment.define( - self.declaration.params[i].lexeme, arguments[i]) + self.environment.define(self.declaration.params[i].lexeme, arguments[i]) try: interpreter._execute_block(self.declaration.body, self.environment) except Return as ret: diff --git a/lox/Stmt.py b/lox/Stmt.py index 72c8407..742f2f9 100644 --- a/lox/Stmt.py +++ b/lox/Stmt.py @@ -1,20 +1,24 @@ class Stmt: - '''Program -> Decl* ";" + """Program -> Decl* ";" Decl -> VarDcl | Stmt Stmt -> ExprStmt | PrintStmt - ''' + """ + pass + class Expression(Stmt): - ''' + """ ExprStmt -> expression ";" - ''' + """ + def __init__(self, expression): self.expression = expression def accept(self, visitor): return visitor.visit_ExpressionStmt(self) + class Print(Stmt): def __init__(self, expression): self.expression = expression @@ -22,6 +26,7 @@ def __init__(self, expression): def accept(self, visitor): return visitor.visit_PrintStmt(self) + class Var(Stmt): def __init__(self, name, initializer): self.name = name @@ -30,6 +35,7 @@ def __init__(self, name, initializer): def accept(self, visitor): return visitor.visit_VarStmt(self) + class Block(Stmt): def __init__(self, statements): self.statements = statements @@ -37,6 +43,7 @@ def __init__(self, statements): def accept(self, visitor): return visitor.visit_BlockStmt(self) + class If(Stmt): def __init__(self, condition, then_branch, else_branch): self.condition = condition @@ -46,6 +53,7 @@ def __init__(self, condition, then_branch, else_branch): def accept(self, visitor): return visitor.visit_IfStmt(self) + class While(Stmt): def __init__(self, condition, body): self.condition = condition @@ -54,6 +62,7 @@ def __init__(self, condition, body): def accept(self, visitor): return visitor.visit_WhileStmt(self) + class Function(Stmt): def __init__(self, name, params, body): self.name = name @@ -63,6 +72,7 @@ def __init__(self, name, params, body): def accept(self, visitor): return visitor.visit_FunctionStmt(self) + class Return(Stmt): def __init__(self, keyword, value): self.keyword = keyword diff --git a/lox/ast_printer.py b/lox/ast_printer.py index b300b51..21655d5 100644 --- a/lox/ast_printer.py +++ b/lox/ast_printer.py @@ -1,45 +1,41 @@ from lox import Expr -from lox.visitor import Visitor from lox.token import Token from lox.tokentype import TokenType +from lox.visitor import Visitor + class ASTPrinter(Visitor): def pprint_ast(self, expr): return self.visit(expr) def parenthesize(self, name, *args): - out = ['(', name] + out = ["(", name] for expr in args: - out.append(' ') + out.append(" ") out.append(expr.accept(self)) - out.append(')') - return ''.join(out) + out.append(")") + return "".join(out) def visit_BinaryExpr(self, expr): return self.parenthesize(expr.operator.lexeme, expr.left, expr.right) def visit_GroupingExpr(self, expr): - return self.parenthesize('group', expr.expression) + return self.parenthesize("group", expr.expression) def visit_LiteralExpr(self, expr): - if expr.value == 'nil': - return 'nil' + if expr.value == "nil": + return "nil" return str(expr.value) def visit_UnaryExpr(self, expr): return self.parenthesize(expr.operator.lexeme, expr.right) - -if __name__ == '__main__': + +if __name__ == "__main__": expression = Expr.Binary( - Expr.Unary( - Token(TokenType.MINUS, '-', None, 1), - Expr.Literal(123) - ), - Token(TokenType.STAR, '*', None, 1), - Expr.Grouping( - Expr.Literal(45.67) - ) + Expr.Unary(Token(TokenType.MINUS, "-", None, 1), Expr.Literal(123)), + Token(TokenType.STAR, "*", None, 1), + Expr.Grouping(Expr.Literal(45.67)), ) ppast = ASTPrinter().pprint_ast(expression) print(ppast) diff --git a/lox/environment.py b/lox/environment.py index 7b82d2b..235b6ab 100644 --- a/lox/environment.py +++ b/lox/environment.py @@ -1,5 +1,6 @@ from lox.exceptions import RuntimeException + class Environment: def __init__(self, enclosing=None): self.values = {} @@ -11,29 +12,29 @@ def ancestor(self, distance): return self def define(self, name, value): - ''':param name: str + """:param name: str :param value: Expr.Literal - ''' + """ self.values[name] = value def assign(self, name, value): - ''':param name: Token + """:param name: Token :param value: Expr.Literal - ''' + """ if name.lexeme in self.values.keys(): self.define(name.lexeme, value) return if self.enclosing != None: self.enclosing.assign(name, value) return - raise RuntimeException(name, f'Undefined variable {name.lexeme}.') + raise RuntimeException(name, f"Undefined variable {name.lexeme}.") def get(self, name): if name.lexeme in self.values.keys(): return self.values[name.lexeme] if self.enclosing != None: return self.enclosing.get(name) - raise RuntimeException(name, f'Undefined name {name.lexeme}.') + raise RuntimeException(name, f"Undefined name {name.lexeme}.") def getat(self, distance, name): return self.ancestor(distance).values[name.lexeme] diff --git a/lox/exceptions.py b/lox/exceptions.py index cd3ca15..cffe011 100644 --- a/lox/exceptions.py +++ b/lox/exceptions.py @@ -1,6 +1,7 @@ class ParseException(Exception): pass + class RuntimeException(Exception): def __init__(self, token, *args, **kwargs): self.token = token diff --git a/lox/interpreter.py b/lox/interpreter.py index b418f6b..5bf9fb1 100644 --- a/lox/interpreter.py +++ b/lox/interpreter.py @@ -2,35 +2,39 @@ from lox.exceptions import RuntimeException from lox.LoxCallable import LoxCallable from lox.LoxFunction import LoxFunction -from lox.native_functions import Clock from lox.loxreturn import Return +from lox.native_functions import Clock from lox.tokentype import TokenType from lox.visitor import Visitor + def _is_true(obj): - '''Only `nil` and `false` are false. Everything else _evaluates to true. - ''' + """Only `nil` and `false` are false. Everything else _evaluates to true. + """ if obj == None: return False elif isinstance(obj, bool): return obj return True + def _check_num_operand(op, *numbers): for num in numbers: if not isinstance(num, float): - raise RuntimeException(op, 'Operand must be a number.') + raise RuntimeException(op, "Operand must be a number.") + def stringify(obj): if obj == None: - return 'nil' + return "nil" if isinstance(obj, bool): # Lox doesn't capitalize the starting 't' and 'f'. if obj: - return 'true' - return 'false' + return "true" + return "false" return str(obj) + class Interpreter(Visitor): # self.environment points towards the current environment, whereas # `global_env` always points to the outermost environment. @@ -91,7 +95,8 @@ def visit_BinaryExpr(self, expr): return left + right except TypeError: raise RuntimeException( - expr.operator, 'Operands must be two numbers or two strings.') + expr.operator, "Operands must be two numbers or two strings." + ) elif expr.operator.tokentype == TokenType.MINUS: _check_num_operand(expr.operator, left, right) return left - right @@ -138,7 +143,7 @@ def visit_LogicalExpr(self, expr): if expr.operator.tokentype == TokenType.OR: if _is_true(left): return left - else: # AND + else: # AND if not _is_true(left): return left return self._evaluate(expr.right) @@ -150,8 +155,7 @@ def visit_CallExpr(self, expr): arguments.append(self._evaluate(argument)) # callee - what would be the type of callee? if not isinstance(callee, LoxCallable): - raise RuntimeException( - expr.paren, "Can only call functions and classes.") + raise RuntimeException(expr.paren, "Can only call functions and classes.") # The original author, while writing this compiler in Java typecasted # the callee to LoxCallable, even after checking `isinstance(callee, @@ -167,8 +171,9 @@ def visit_CallExpr(self, expr): if len(arguments) != callee.arity: raise RuntimeException( - expr.paren, - f"Expected {function.arity} arguments, but got {len(arguments)}.") + expr.paren, + f"Expected {function.arity} arguments, but got {len(arguments)}.", + ) return callee.__call__(self, arguments) def visit_ExpressionStmt(self, stmt): diff --git a/lox/lox.py b/lox/lox.py index 2795fe7..48b68f6 100755 --- a/lox/lox.py +++ b/lox/lox.py @@ -6,6 +6,7 @@ from lox.scanner import Scanner from lox.tokentype import TokenType + class Lox: def __init__(self): self.had_error = False @@ -15,7 +16,7 @@ def __init__(self): def _report(self, line, where, message): # Should we pipe to `sys.stderr`? - print(f'[Line {line}] Error{where}: {message}') + print(f"[Line {line}] Error{where}: {message}") def error(self, token, message): self.had_error = True @@ -23,13 +24,13 @@ def error(self, token, message): self._report(token.line, " at end", message) else: self._report(token.line, f" at '{token.lexeme}'", message) - + def runtime_error(self, err): - print(f'[Line {err.token.line}] {err}') + print(f"[Line {err.token.line}] {err}") self.had_runtime_error = True def run_file(self, filename): - with open(filename, 'r') as f: + with open(filename, "r") as f: self.run(f.read()) EXIT_CODE = 0 @@ -41,7 +42,7 @@ def run_file(self, filename): def run_prompt(self): while True: - reader = input('> ') + reader = input("> ") self.run(reader) self.had_error = False @@ -56,6 +57,7 @@ def run(self, source): return self.interpreter.interpret(statements) + def main(): l = Lox() if len(sys.argv) > 2: @@ -67,5 +69,5 @@ def main(): l.run_prompt() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/lox/native_functions.py b/lox/native_functions.py index 1d066d9..ad99a04 100644 --- a/lox/native_functions.py +++ b/lox/native_functions.py @@ -1,6 +1,7 @@ +import time + from lox.LoxCallable import LoxCallable -import time class Clock(LoxCallable): @property diff --git a/lox/parser.py b/lox/parser.py index 4616b0f..8c3606d 100644 --- a/lox/parser.py +++ b/lox/parser.py @@ -1,8 +1,8 @@ -from lox import Expr +from lox import Expr, Stmt from lox.exceptions import ParseException -from lox import Stmt from lox.tokentype import TokenType + class Parser: def __init__(self, tokens, lox): self.tokens = tokens @@ -51,8 +51,7 @@ def _finish_call(self, callee): arguments.append(self.expression()) while self._match([TokenType.COMMA]): arguments.append(self.expression()) - paren = self._consume( - TokenType.RIGHT_PAREN, "Expected ')' after arguments.") + paren = self._consume(TokenType.RIGHT_PAREN, "Expected ')' after arguments.") if len(arguments) > 255: self._error(self._peek(), "Cannot have more than 255 arguments.") return Expr.Call(callee, paren, arguments) @@ -68,31 +67,31 @@ def declaration(self): self._synchronize() def function_declaration(self, kind): - '''We reuse this method for both function and method declaration. + """We reuse this method for both function and method declaration. `kind` is used to distinguish between the two. - ''' + """ name = self._consume(TokenType.IDENTIFIER, f"Expect {kind} name.") self._consume(TokenType.LEFT_PAREN, f"Expect '(' after {kind} name.") parameters = [] if not self._check(TokenType.RIGHT_PAREN): - parameters.append(self._consume( - TokenType.IDENTIFIER, "Expect parameter name.")) + parameters.append( + self._consume(TokenType.IDENTIFIER, "Expect parameter name.") + ) while self._match([TokenType.COMMA]): - parameters.append(self._consume( - TokenType.IDENTIFIER, "Expect parameter name.")) + parameters.append( + self._consume(TokenType.IDENTIFIER, "Expect parameter name.") + ) if len(parameters) > 255: self._error(self._peek(), "Cannot have more than 255 parameters.") self._consume(TokenType.RIGHT_PAREN, "Expect ')'after parameters.") - self._consume( - TokenType.LEFT_BRACE, "Expect '{' before" + kind + "body.") + self._consume(TokenType.LEFT_BRACE, "Expect '{' before" + kind + "body.") body = self.block() return Stmt.Function(name, parameters, body) def var_declaration(self): name = self._consume(TokenType.IDENTIFIER, "Expect variable name.") value = self.expression() if self._match([TokenType.EQUAL]) else None - self._consume( - TokenType.SEMICOLON, "Expect ';' after variable declaration.") + self._consume(TokenType.SEMICOLON, "Expect ';' after variable declaration.") return Stmt.Var(name, value) def statement(self): @@ -234,8 +233,14 @@ def equality(self): def comparison(self): expr = self.addition() - while self._match([TokenType.GREATER, TokenType.GREATER_EQUAL, - TokenType.LESS, TokenType.LESS_EQUAL]): + while self._match( + [ + TokenType.GREATER, + TokenType.GREATER_EQUAL, + TokenType.LESS, + TokenType.LESS_EQUAL, + ] + ): operator = self._previous right = self.addition() expr = Expr.Binary(expr, operator, right) @@ -289,18 +294,25 @@ def primary(self): return Expr.Grouping(expr) elif self._match([TokenType.IDENTIFIER]): return Expr.Variable(self._previous) - raise self._error(self._peek(), 'Expect expression.') + raise self._error(self._peek(), "Expect expression.") def _synchronize(self): - '''Skip tokens until a new statement is found in case of an error. - ''' + """Skip tokens until a new statement is found in case of an error. + """ self._advance() while not self._at_end(): if self._previous == TokenType.SEMICOLON: return if self._peek().tokentype in ( - TokenType.CLASS, TokenType.FUN, TokenType.VAR, TokenType.FOR, TokenType.IF, - TokenType.WHILE, TokenType.PRINT, TokenType.RETURN): + TokenType.CLASS, + TokenType.FUN, + TokenType.VAR, + TokenType.FOR, + TokenType.IF, + TokenType.WHILE, + TokenType.PRINT, + TokenType.RETURN, + ): return self._advance() diff --git a/lox/resolver.py b/lox/resolver.py index eb57739..f48722f 100644 --- a/lox/resolver.py +++ b/lox/resolver.py @@ -2,7 +2,8 @@ from lox.visitor import Visitor -FunctionType = Enum('FunctionType', ['NONE', 'FUNCTION']) +FunctionType = Enum("FunctionType", ["NONE", "FUNCTION"]) + class Resolver(Visitor): def __init__(self, lox, interpreter): @@ -54,7 +55,8 @@ def _declare(self, name): scope = self.scopes[-1] if name.lexeme in scope.keys(): self.lox.error( - name, "Variable with this name already declared in this scope.") + name, "Variable with this name already declared in this scope." + ) # We don't need to return/stop here, because `self.lox.had_error` # gets set and no code would be interpreted. scope[name.lexeme] = False @@ -103,11 +105,12 @@ def visit_WhileStmt(self, stmt): self.resolve(stmt.body) def visit_VariableExpr(self, expr): - if ((not self.scope_is_empty) and - self.scopes[-1].get(expr.name.lexeme, None) == False): + if (not self.scope_is_empty) and self.scopes[-1].get( + expr.name.lexeme, None + ) == False: self.lox.error( - expr.name, - "Cannot read local variable in it's own initializer.") + expr.name, "Cannot read local variable in it's own initializer." + ) self.resolve_local(expr, expr.name) def visit_AssignExpr(self, expr): diff --git a/lox/scanner.py b/lox/scanner.py index 19ba764..26f57e4 100644 --- a/lox/scanner.py +++ b/lox/scanner.py @@ -17,9 +17,10 @@ "this": TokenType.THIS, "true": TokenType.TRUE, "var": TokenType.VAR, - "while": TokenType.WHILE + "while": TokenType.WHILE, } + class Scanner: def __init__(self, source, lox): self.source = source @@ -46,7 +47,7 @@ def advance(self): return self.source[self.current - 1] def add_token(self, tokentype, literal=None): - text = self.source[self.start:self.current] + text = self.source[self.start : self.current] self.tokens.append(Token(tokentype, text, literal, self.line)) def _peek(self): @@ -54,12 +55,12 @@ def _peek(self): Look up the current char in c. However, return EOF `\0` if at end. """ if self.at_end: - return '\0' + return "\0" return self.source[self.current] def _peek_next(self): if self.current + 1 > len(self.source): - return '\0' + return "\0" return self.source[self.current + 1] def _is_next(self, expected): @@ -75,16 +76,16 @@ def _handle_number(self): self.advance() # Check for fractional part - if self._peek() == '.' and self._peek_next().isdigit(): - self.advance() # Consume the `.` + if self._peek() == "." and self._peek_next().isdigit(): + self.advance() # Consume the `.` while self._peek().isdigit(): self.advance() - literal = float(self.source[self.start:self.current]) + literal = float(self.source[self.start : self.current]) self.add_token(TokenType.NUMBER, literal) def _handle_slash(self): - if self._is_next('/'): # It is a comment. Ignore until end of line/file. + if self._is_next("/"): # It is a comment. Ignore until end of line/file. while self._peek() != "\n" and not self.at_end: self.advance() else: @@ -93,15 +94,15 @@ def _handle_slash(self): def _handle_identifier(self): while self._peek().isalnum(): self.advance() - - text = self.source[self.start:self.current] + + text = self.source[self.start : self.current] tokentype = KEYWORDS.get(text, TokenType.IDENTIFIER) self.add_token(tokentype) def _handle_string(self): while self._peek() != '"' and not self.at_end: # Allow multi-line strings - if self._peek() == '\n': + if self._peek() == "\n": self.line = self.line + 1 self.advance() if self.at_end: @@ -111,46 +112,50 @@ def _handle_string(self): self.advance() # Consume the ending `"` # Also save the literal value of the string - literal = self.source[self.start + 1:self.current - 1] + literal = self.source[self.start + 1 : self.current - 1] self.add_token(TokenType.STRING, literal) def scan_token(self): c = self.advance() - if c == '(': + if c == "(": self.add_token(TokenType.LEFT_PAREN) - elif c == ')': + elif c == ")": self.add_token(TokenType.RIGHT_PAREN) - elif c == '{': + elif c == "{": self.add_token(TokenType.LEFT_BRACE) - elif c == '}': + elif c == "}": self.add_token(TokenType.RIGHT_BRACE) - elif c == ',': + elif c == ",": self.add_token(TokenType.COMMA) - elif c == '.': + elif c == ".": self.add_token(TokenType.DOT) - elif c == '-': + elif c == "-": self.add_token(TokenType.MINUS) - elif c == '+': + elif c == "+": self.add_token(TokenType.PLUS) - elif c == ';': + elif c == ";": self.add_token(TokenType.SEMICOLON) - elif c == '*': + elif c == "*": self.add_token(TokenType.STAR) elif c == "!": self.add_token( - TokenType.BANG_EQUAL if self._is_next("=") else TokenType.BANG) + TokenType.BANG_EQUAL if self._is_next("=") else TokenType.BANG + ) elif c == "=": self.add_token( - TokenType.EQUAL_EQUAL if self._is_next("=") else TokenType.EQUAL) + TokenType.EQUAL_EQUAL if self._is_next("=") else TokenType.EQUAL + ) elif c == "<": self.add_token( - TokenType.LESS_EQUAL if self._is_next("=") else TokenType.LESS) + TokenType.LESS_EQUAL if self._is_next("=") else TokenType.LESS + ) elif c == ">": self.add_token( - TokenType.GREATER_EQUAL if self._is_next("=") else TokenType.GREATER) + TokenType.GREATER_EQUAL if self._is_next("=") else TokenType.GREATER + ) elif c == "/": self._handle_slash() - elif c in (" ", "\r", "\t"): # Empty spaces + elif c in (" ", "\r", "\t"): # Empty spaces pass elif c == "\n": self.line = self.line + 1 diff --git a/lox/token.py b/lox/token.py index 50032dc..731fb3d 100644 --- a/lox/token.py +++ b/lox/token.py @@ -4,6 +4,6 @@ def __init__(self, tokentype, lexeme, literal, line): self.lexeme = lexeme self.literal = literal self.line = line - + def __str__(self): return f"{self.tokentype} {self.lexeme} {self.literal}" diff --git a/lox/tokentype.py b/lox/tokentype.py index 85a0730..1649379 100644 --- a/lox/tokentype.py +++ b/lox/tokentype.py @@ -1,24 +1,49 @@ from enum import Enum types = [ - # Single-character tokens. - 'LEFT_PAREN', 'RIGHT_PAREN', 'LEFT_BRACE', 'RIGHT_BRACE', - 'COMMA', 'DOT', 'MINUS', 'PLUS', 'SEMICOLON', 'SLASH', 'STAR', - - # One or two character tokens. - 'BANG', 'BANG_EQUAL', - 'EQUAL', 'EQUAL_EQUAL', - 'GREATER', 'GREATER_EQUAL', - 'LESS', 'LESS_EQUAL', - - # Literals. - 'IDENTIFIER', 'STRING', 'NUMBER', - - # Keywords. - 'AND', 'CLASS', 'ELSE', 'FALSE', 'FUN', 'FOR', 'IF', 'NIL', 'OR', - 'PRINT', 'RETURN', 'SUPER', 'THIS', 'TRUE', 'VAR', 'WHILE', - - 'EOF' + # Single-character tokens. + "LEFT_PAREN", + "RIGHT_PAREN", + "LEFT_BRACE", + "RIGHT_BRACE", + "COMMA", + "DOT", + "MINUS", + "PLUS", + "SEMICOLON", + "SLASH", + "STAR", + # One or two character tokens. + "BANG", + "BANG_EQUAL", + "EQUAL", + "EQUAL_EQUAL", + "GREATER", + "GREATER_EQUAL", + "LESS", + "LESS_EQUAL", + # Literals. + "IDENTIFIER", + "STRING", + "NUMBER", + # Keywords. + "AND", + "CLASS", + "ELSE", + "FALSE", + "FUN", + "FOR", + "IF", + "NIL", + "OR", + "PRINT", + "RETURN", + "SUPER", + "THIS", + "TRUE", + "VAR", + "WHILE", + "EOF", ] -TokenType = Enum('TokenType', types) +TokenType = Enum("TokenType", types) diff --git a/setup.py b/setup.py index 57115f9..6e8ebb2 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,13 @@ from setuptools import setup -setup(name='plox', - version='0.1', - description='Python implementation of the Lox programming language', - keywords='compiler-design', - author='Rahul Jha', - author_email='rj722@protonmail.com', - url='https://github.com/RJ722/plox', - packages=['lox'], - entry_points={ - 'console_scripts': ['plox = lox.lox:main'], - }, +setup( + name="plox", + version="0.1", + description="Python implementation of the Lox programming language", + keywords="compiler-design", + author="Rahul Jha", + author_email="rj722@protonmail.com", + url="https://github.com/RJ722/plox", + packages=["lox"], + entry_points={"console_scripts": ["plox = lox.lox:main"],}, ) diff --git a/tests/__init__.py b/tests/__init__.py index eac48d3..93b0c47 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,7 +1,8 @@ +import pytest + import lox from lox import lox -import pytest @pytest.fixture def l(): diff --git a/tests/test_scanner.py b/tests/test_scanner.py index 21d5130..fa813cd 100644 --- a/tests/test_scanner.py +++ b/tests/test_scanner.py @@ -3,67 +3,84 @@ from . import l + def get_tokens(code, l): return Scanner(code, l).scan_tokens() + def check_tokens(result, expected): - ''' result: [Token*] + """ result: [Token*] expected: [TokenType*] - ''' + """ assert len(result) == len(expected) for i in range(len(result)): assert result[i].tokentype == expected[i] + def _check(tfn, l): code, expected = tfn(l) expected.append(TokenType.EOF) tokens = get_tokens(code, l) check_tokens(tokens, expected) + def raises_no_error(tfn): def inner(l): _check(tfn, l) assert l.had_error == False + return inner + def raises_error(tfn): def inner(l): _check(tfn, l) assert l.had_error == True + return inner + @raises_no_error def test_empty(l): - code = '' + code = "" expected = [] return code, expected + @raises_no_error def test_print(l): - code = 'print 1 + 2;' + code = "print 1 + 2;" expected = [ - TokenType.PRINT, TokenType.NUMBER, TokenType.PLUS, TokenType.NUMBER, - TokenType.SEMICOLON] + TokenType.PRINT, + TokenType.NUMBER, + TokenType.PLUS, + TokenType.NUMBER, + TokenType.SEMICOLON, + ] return code, expected + @raises_no_error def test_print_print(l): code = 'print "print a + b";' expected = [TokenType.PRINT, TokenType.STRING, TokenType.SEMICOLON] return code, expected + @raises_error def test_underscore(l): - code = '_' + code = "_" expected = [] return code, expected + @raises_error def test_single_quotes(l): code = "''" expected = [] return code, expected + @raises_error def test_unexpected_in_middle(l): code = """\ @@ -74,11 +91,16 @@ def test_unexpected_in_middle(l): () """ expected = [ - TokenType.CLASS, TokenType.FUN, TokenType.LEFT_BRACE, - TokenType.RIGHT_BRACE, TokenType.LEFT_PAREN, TokenType.RIGHT_PAREN + TokenType.CLASS, + TokenType.FUN, + TokenType.LEFT_BRACE, + TokenType.RIGHT_BRACE, + TokenType.LEFT_PAREN, + TokenType.RIGHT_PAREN, ] return code, expected + @raises_no_error def test_keywords(l): code = """\ @@ -100,12 +122,26 @@ def test_keywords(l): while """ expected = [ - TokenType.AND, TokenType.CLASS, TokenType.ELSE, TokenType.FALSE, - TokenType.FUN, TokenType.FOR, TokenType.IF, TokenType.NIL, TokenType.OR, - TokenType.PRINT, TokenType.RETURN, TokenType.SUPER, TokenType.THIS, - TokenType.TRUE, TokenType.VAR, TokenType.WHILE] + TokenType.AND, + TokenType.CLASS, + TokenType.ELSE, + TokenType.FALSE, + TokenType.FUN, + TokenType.FOR, + TokenType.IF, + TokenType.NIL, + TokenType.OR, + TokenType.PRINT, + TokenType.RETURN, + TokenType.SUPER, + TokenType.THIS, + TokenType.TRUE, + TokenType.VAR, + TokenType.WHILE, + ] return code, expected + @raises_no_error def test_symbols(l): code = """\ @@ -125,14 +161,29 @@ def test_symbols(l): <= """ expected = [ - TokenType.LEFT_PAREN, TokenType.RIGHT_PAREN, TokenType.LEFT_BRACE, - TokenType.RIGHT_BRACE, TokenType.COMMA, TokenType.DOT, TokenType.MINUS, - TokenType.PLUS, TokenType.SEMICOLON, TokenType.SLASH, TokenType.STAR, - TokenType.BANG, TokenType.BANG_EQUAL, TokenType.EQUAL_EQUAL, - TokenType.EQUAL, TokenType.GREATER, TokenType.GREATER_EQUAL, - TokenType.LESS, TokenType.LESS_EQUAL] + TokenType.LEFT_PAREN, + TokenType.RIGHT_PAREN, + TokenType.LEFT_BRACE, + TokenType.RIGHT_BRACE, + TokenType.COMMA, + TokenType.DOT, + TokenType.MINUS, + TokenType.PLUS, + TokenType.SEMICOLON, + TokenType.SLASH, + TokenType.STAR, + TokenType.BANG, + TokenType.BANG_EQUAL, + TokenType.EQUAL_EQUAL, + TokenType.EQUAL, + TokenType.GREATER, + TokenType.GREATER_EQUAL, + TokenType.LESS, + TokenType.LESS_EQUAL, + ] return code, expected + @raises_no_error def test_tricky_double_symbols(l): code = """\ @@ -144,13 +195,24 @@ def test_tricky_double_symbols(l): <=> """ expected = [ - TokenType.BANG, TokenType.BANG, TokenType.BANG_EQUAL, - TokenType.EQUAL_EQUAL, TokenType.EQUAL_EQUAL, TokenType.EQUAL_EQUAL, - TokenType.EQUAL_EQUAL, TokenType.EQUAL, TokenType.LESS, - TokenType.LESS_EQUAL, TokenType.LESS, TokenType.GREATER_EQUAL, - TokenType.LESS_EQUAL, TokenType.GREATER] + TokenType.BANG, + TokenType.BANG, + TokenType.BANG_EQUAL, + TokenType.EQUAL_EQUAL, + TokenType.EQUAL_EQUAL, + TokenType.EQUAL_EQUAL, + TokenType.EQUAL_EQUAL, + TokenType.EQUAL, + TokenType.LESS, + TokenType.LESS_EQUAL, + TokenType.LESS, + TokenType.GREATER_EQUAL, + TokenType.LESS_EQUAL, + TokenType.GREATER, + ] return code, expected + @raises_no_error def test_multiline_strings(l): code = """\ @@ -169,15 +231,22 @@ def test_multiline_strings(l): expected = [TokenType.STRING, TokenType.STRING] return code, expected + @raises_no_error def test_ignore_comments(l): code = "// This is a comment" expected = [] return code, expected + @raises_no_error def test_ignore_inline_comments(l): code = "var a = 1; // Ignore me!" - expected = [TokenType.VAR, TokenType.IDENTIFIER, TokenType.EQUAL, - TokenType.NUMBER, TokenType.SEMICOLON] + expected = [ + TokenType.VAR, + TokenType.IDENTIFIER, + TokenType.EQUAL, + TokenType.NUMBER, + TokenType.SEMICOLON, + ] return code, expected diff --git a/tests/test_unused_code.py b/tests/test_unused_code.py index fd199f2..ded3486 100644 --- a/tests/test_unused_code.py +++ b/tests/test_unused_code.py @@ -1,16 +1,19 @@ import pytest from vulture import Vulture + @pytest.fixture def v(): return Vulture(verbose=True) + def test_unused_code(v): - v.scavenge(['lox']) + v.scavenge(["lox"]) v.report() assert v.get_unused_code() == [] + def test_unused_test(v): - v.scavenge(['tests']) + v.scavenge(["tests"]) v.report() assert v.get_unused_code() == []