def test_parser_invalid_line(): """ To validate the parser detects the presence of an missing numeral in expression """ parser = Parser(Lexer(Scanner("1 + 2 +\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.INCOMPLETE_LINE assert err.value.filename == "<stdin>" assert err.value.lineno == 0 assert err.value.offset == 6 assert err.value.line == "1 + 2 +" parser = Parser(Lexer(Scanner("1 + 2 *\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.INCOMPLETE_LINE assert err.value.filename == "<stdin>" assert err.value.lineno == 0 assert err.value.offset == 6 assert err.value.line == "1 + 2 *"
def test_analyzer_wrong_assign(clean_namespace): """ To validate that the analyzer generates exceptions when the assign operator is not properly written. """ analyzer = Analyzer(Parser(Lexer(Scanner('7 + 4 -> b + 1\n', '__test__')))) assert str(analyzer.table) == "__test__, root" with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.GARBAGE_CHARACTERS assert err.value.filename == "__test__" assert err.value.lineno == 0 assert err.value.offset == 6 assert err.value.line == "7 + 4 -> b + 1" analyzer = Analyzer(Parser(Lexer(Scanner('7 + 4 <- 1 + 2\n', '__test__')))) assert str(analyzer.table) == "__test__, root" with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.LEFT_MEMBER_IS_EXPRESSION assert err.value.filename == "__test__" assert err.value.lineno == 0 assert err.value.offset == 2 assert err.value.line == "7 + 4 <- 1 + 2" analyzer = Analyzer(Parser(Lexer(Scanner('7 + 4 -> \n', '__test__')))) assert str(analyzer.table) == "__test__, root" with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.INCOMPLETE_LINE assert err.value.filename == "__test__" assert err.value.lineno == 0 assert err.value.offset == 8 assert err.value.line == "7 + 4 -> " analyzer = Analyzer(Parser(Lexer(Scanner('7 + 4 -> 6\n', '__test__')))) assert str(analyzer.table) == "__test__, root" with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.NAME_EXPECTED assert err.value.filename == "__test__" assert err.value.lineno == 0 assert err.value.offset == 9 assert err.value.line == "7 + 4 -> 6"
def test_parser_nested_let_assign(): """ To validate the parser solves nested let. """ parser = Parser(Lexer(Scanner("let:\n let:\n a <- 1 + 2\n b <- a * 3\n\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(Let(MutableAssign(Name(a), Add(Num(1), Num(2)))), MutableAssign(Name(b), Mul(Name(a), Num(3))))" parser = Parser(Lexer(Scanner("let:\n let:\n a <- 1 + 2\n\n b <- a * 3\n\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(Let(MutableAssign(Name(a), Add(Num(1), Num(2)))), MutableAssign(Name(b), Mul(Name(a), Num(3))))"
def test_parser_let_be(): """ To validate the parser iterates properly over a variable being assigned the result of an expression. """ parser = Parser(Lexer(Scanner("let b:\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(Class(Name(b), None))" parser = Parser(Lexer(Scanner("let bit be attribute:\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(Class(Name(bit), Type(Name(attribute))))"
def test_analyzer_immutable_assign(clean_namespace): """ To validate that the analyzer is able to produce a symbol table. We test store and load context, that is, a value is stored with the result of an expression, and then we use the alias to store another value into another variable. """ analyzer = Analyzer(Parser(Lexer(Scanner('7 + 4 -> b\n', '__test__')))) assert str(analyzer.table) == "__test__, root" analyzer() assert analyzer.table[('b', '__test__')].type.value == 11 assert analyzer.table[('b', '__test__')].type.mutable == Field.IMMUTABLE assert analyzer.table[('b', '__test__')].type.type == Field.UNKNOWN analyzer.parser.lexer.scanner += '12 -> b\n' with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.REASSIGN_IMMUTABLE assert err.value.filename == "__test__" assert err.value.lineno == 1 assert err.value.offset == 3 assert err.value.line == "12 -> b"
def test_parser_let_assign(): """ To validate the parser iterates properly over a variable being assigned the result of an expression. """ parser = Parser(Lexer(Scanner("let a <- 1 + 2\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(MutableAssign(Name(a), Add(Num(1), Num(2))))" parser = Parser(Lexer(Scanner("let b * 2 -> a\n"))) assign = parser() assert assign.name == NodeType.Let assert str(assign) == "Let(ImmutableAssign(Name(a), Mul(Name(b), Num(2))))"
def test_parser_docstring(): """ To validate that the docstring of a class, or a function is not captured in this version of the tool, and considered as multiline comment. """ parser = Parser(Lexer(Scanner('"""\nHello you\n"""\n'))) test = parser() assert test.name == NodeType.Noop
def test_interpreter(): """ Basic set of visiting nodes """ interpreter = Interpreter() cmd = next(Parser(Lexer(Scanner("1 + 2\n")))) assert interpreter.visit(cmd) == 3 cmd = next(Parser(Lexer(Scanner("1 * 2\n")))) assert interpreter.visit(cmd) == 2 cmd = next(Parser(Lexer(Scanner("1 - 2\n")))) assert interpreter.visit(cmd) == -1 cmd = next(Parser(Lexer(Scanner("\n")))) assert interpreter.visit(cmd) is None cmd = next(Parser(Lexer(Scanner("a\n")))) assert interpreter.visit(cmd) == 'a'
def test_parser_wrong_expression(): """ To validate the parser complains under various situations: 1. Expression has too much trailing characters. 2. Expression has an unexpected let keyword. """ parser = Parser(Lexer(Scanner("1 + 2 + 3 "))) with pytest.raises(LythSyntaxError) as err: parser() assert err.value.msg is LythError.GARBAGE_CHARACTERS parser = Parser(Lexer(Scanner("let 1 + 2 + 3\n"))) with pytest.raises(LythSyntaxError) as err: parser() assert err.value.msg is LythError.LET_ON_EXPRESSION
def test_parser_precedence(): """ To validate the parser returns the right AST node. """ parser = Parser(Lexer(Scanner("1 + 2 * 3 - 1\n"))) expr = next(parser) assert expr.name == NodeType.Sub assert str(expr) == "Sub(Add(Num(1), Mul(Num(2), Num(3))), Num(1))" expr = next(parser) assert expr.name == NodeType.Noop assert str(expr) == "Noop()"
def test_parser_multiplication(): """ To validate the parser returns the right AST node. """ parser = Parser(Lexer(Scanner("1 * 2 * 3\n"))) expr = next(parser) assert expr.name == NodeType.Mul assert str(expr) == "Mul(Mul(Num(1), Num(2)), Num(3))" expr = next(parser) assert expr.name == NodeType.Noop assert str(expr) == "Noop()"
def test_parser_expression(): """ To validate the parser iterates properly over an expression. The last node on a properly formatted string should be No Op. """ parser = Parser(Lexer(Scanner("1 + 2 + 3 \n"))) for i in parser: pass assert i.name == NodeType.Noop assert str(i) == "Noop()"
def test_parser_parenthesis(): """ Parentheses offer the highest precedence and change AST node ordering. """ parser = Parser(Lexer(Scanner("1 + (a - 3) * 5\n"))) expr = next(parser) assert expr.name == NodeType.Add assert str(expr) == "Add(Num(1), Mul(Sub(Name(a), Num(3)), Num(5)))" expr = next(parser) assert expr.name == NodeType.Noop assert str(expr) == "Noop()"
def test_parser_substraction(): """ To validate the parser returns the right AST node. """ parser = Parser(Lexer(Scanner("1 + 2 - 3\n"))) expr = next(parser) assert expr.name == NodeType.Sub assert str(expr) == "Sub(Add(Num(1), Num(2)), Num(3))" expr = next(parser) assert expr.name == NodeType.Noop assert str(expr) == "Noop()"
def main(argv=sys.argv): """ The main entry point of the application. """ settings = fetch(argv[1:]) error = 0 # interpreter = Interpreter() count = 0 while count <= settings.cycle: try: source = input('>>> ') if source.rstrip() and source.rstrip()[-1] == ':': while True: line = input('... ') source += '\n' + line if not line or len(line) - len(line.lstrip()) == 0: break scanner = Scanner(source + "\n") parser = Parser(Lexer(scanner)) analyzer = Analyzer(parser) cmd = next(parser) # print(interpreter.visit(cmd)) ret = analyzer.visit(cmd) if ret: print(ret) except LythSyntaxError as e: print(e) except KeyboardInterrupt: print("Keyboard interrupt") break except Exception: exc_type, exc_value, exc_tb = sys.exc_info() traceback.print_exception(exc_type, exc_value, exc_tb) error = 1 break if settings.cycle: count += 1 print("Goodbye.") return error
def test_parser_invalid_expression(): """ To validate the parser detects the expression it evaluates is invalid. """ parser = Parser(Lexer(Scanner("1 + 2 + /\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.LITERAL_EXPECTED assert err.value.filename == "<stdin>" assert err.value.lineno == 0 assert err.value.offset == 8 assert err.value.line == "1 + 2 + /"
def test_parser_invalid_let(): """ To validate the parser checks let block statement integrity """ parser = Parser(Lexer(Scanner("let: a <- 1 + 2\n b <- a * 3\n\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.GARBAGE_CHARACTERS assert err.value.filename == "<stdin>" assert err.value.lineno == 0 assert err.value.offset == 5 assert err.value.line == "let: a <- 1 + 2" parser = Parser(Lexer(Scanner("let:\na <- 1 + 2\n b <- a * 3\n\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.INCONSISTENT_INDENT assert err.value.filename == "<stdin>" assert err.value.lineno == 1 assert err.value.offset == 0 assert err.value.line == "a <- 1 + 2" parser = Parser(Lexer(Scanner("let:\n a <- 1 + 2\n b <- a * 3\n\n"))) with pytest.raises(LythSyntaxError) as err: next(parser) assert err.value.msg is LythError.INCONSISTENT_INDENT assert err.value.filename == "<stdin>" assert err.value.lineno == 2 assert err.value.offset == 0 assert err.value.line == " b <- a * 3"
def test_analyzer_let(clean_namespace): """ To validate that for now the analyzer is not doing anything upon let keyword """ analyzer = Analyzer(Parser(Lexer(Scanner('let a <- 1 + 2\n', '__test__')))) assert str(analyzer.table) == "__test__, root" analyzer() assert analyzer.table[('a', '__test__')].type.value == 3 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('a', '__test__')].type.type == Field.UNKNOWN assert analyzer.table.left is None assert str(analyzer.table.right) == "a, __test__"
def test_analyzer_iterator(clean_namespace): """ To validate the ability to analyze multiple lines on the fly """ analyzer = Analyzer(Parser(Lexer(Scanner('a <- 1 + 2\na * 5 -> b\n', '__test__')))) assert str(analyzer.table) == "__test__, root" assert analyzer.table.left is None assert analyzer.table.right is None for _ in analyzer: print(f"TEST: left node of root is {analyzer.table.left}") print(f"TEST: right node of root is {analyzer.table.right}") assert analyzer.table[('a', '__test__')].type.value == 3 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('b', '__test__')].type.value == 15 assert analyzer.table[('b', '__test__')].type.mutable == Field.IMMUTABLE
def test_analyzer_unknown_ast_node(clean_namespace): """ To validate we get a dedicated error message when the AST node is not valid """ parser = Parser(Lexer(Scanner('a <- 1 + 2\na * 5 -> b\n', '__test__'))) node = next(parser) from enum import Enum class Dummy(Enum): DUMMY = "dummy" node.name = Dummy.DUMMY analyzer = Analyzer(None, "__dummy__.py") with pytest.raises(TypeError): analyzer.visit(node)
def test_interpreter_whole_file(): """ The interpreter should survive a basic set of lyth commands. Next, we'll see if bytecode can be properly produced... ;) """ path = Path(__file__).resolve().parent / 'resources/stm32f4_gpiob.lyth' f = open(path) try: interpreter = Interpreter() parser = Parser(Lexer(Scanner(f.read(), filename="stm32f4_gpiob.lyth"))) while True: cmd = next(parser) assert interpreter.visit(cmd) is None except Exception: raise finally: f.close()
def test_analyzer_mutable_assign(clean_namespace): """ To validate that the analyzer is able to produce a symbol table. We test store and load context, that is, a value is stored with the result of an expression, and then we use the alias to store another value into another variable. """ analyzer = Analyzer(Parser(Lexer(Scanner('a <- 1 + 2\n', '__test__')))) assert str(analyzer.table) == "__test__, root" analyzer() assert analyzer.table[('a', '__test__')].type.value == 3 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('a', '__test__')].type.type == Field.UNKNOWN assert analyzer.table.left is None assert str(analyzer.table.right) == "a, __test__" analyzer.parser.lexer.scanner += '__a <- a + 2\n' assert str(analyzer.table) == "__test__, root" analyzer() assert analyzer.table[('a', '__test__')].type.value == 3 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('a', '__test__')].type.type == Field.UNKNOWN assert analyzer.table[('__a', '__test__')].type.value == 5 assert analyzer.table[('__a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('__a', '__test__')].type.type == Field.UNKNOWN assert str(analyzer.table.left) == "__a, __test__" assert str(analyzer.table.right) == "a, __test__" analyzer.parser.lexer.scanner += 'a <- 5\n' analyzer() assert analyzer.table[('a', '__test__')].type.value == 5 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('a', '__test__')].type.type == Field.UNKNOWN assert analyzer.table[('__a', '__test__')].type.value == 5 assert analyzer.table[('__a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('__a', '__test__')].type.type == Field.UNKNOWN analyzer.parser.lexer.scanner += 'c <- b + 2\n' with pytest.raises(LythSyntaxError) as err: analyzer() assert err.value.msg is LythError.VARIABLE_REFERENCED_BEFORE_ASSIGNMENT assert err.value.filename == "__test__" assert err.value.lineno == 3 assert err.value.offset == 5 assert err.value.line == "c <- b + 2" analyzer = Analyzer(Parser(Lexer(Scanner('a <- 1 - 2\n', '__test__')))) analyzer() assert analyzer.table[('a', '__test__')].type.value == -1 assert analyzer.table[('a', '__test__')].type.mutable == Field.MUTABLE assert analyzer.table[('a', '__test__')].type.type == Field.UNKNOWN analyzer = Analyzer(Parser(Lexer(Scanner('4 / 2 -> d\n', '__test__')))) analyzer() assert analyzer.table[('d', '__test__')].type.value == 2 assert analyzer.table[('d', '__test__')].type.mutable == Field.IMMUTABLE assert analyzer.table[('d', '__test__')].type.type == Field.UNKNOWN