Пример #1
0
    def test_assignment(self, mocker):
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()
        on_interpret_error_mock = mocker.MagicMock()

        lines = [
            "var a = 0;",
            "var c = a;",
            "var b;",
            "a = 3 + 6;",
            "b = 3 / 6;",
            "a = a + b;",
            "print(a);",
            "a;",
        ]

        expression = "\n".join(lines)

        scanner = Scanner(expression, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        result = Interpreter().interpret(statements,
                                         on_error=on_interpret_error_mock)

        assert result == 9.5
Пример #2
0
    def run(self, source: str):
        logger.debug("Running line", source=source)

        scanner = Scanner(source, on_error=self.error)
        tokens = scanner.scan_tokens()

        for token in tokens:
            logger.debug("Running token", token=token)

        parser = Parser(tokens, on_token_error=self.token_error)
        statements = parser.parse()

        if self.had_error:
            logger.debug("Error after parsing")
            return

        resolver = Resolver(interpreter=self.interpreter,
                            on_error=self.token_error)
        resolver.resolve(statements)
        # Stop if there was a resolution error.
        if self.had_error:
            logger.debug("Error after resolving")
            return

        self.interpreter.interpret(statements, on_error=self.runtime_error)
Пример #3
0
    def test_parser_synchronize(self, mocker):
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        lines = [
            "var a = 4;",
            "print (4+3;",
            "a = 5;",
            "",
            "var a = (3/4",
            "print (a);",
        ]
        source = "\n".join(lines)
        scanner = Scanner(source, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        # We validate that the error is called, the real values are tested in
        # test_missing_closing_bracket
        assert not on_scanner_error_mock.called
        assert on_parser_error_mock.called

        # Assert that the third statement is sane and that the parser continues after
        # errors
        assert isinstance(statements[1], Expression)
        assert statements[1].expression.name.line == 3
        assert statements[1].expression.name.lexeme == "a"
Пример #4
0
    def test_nested_binary_expr(self, create_token_factory, mocker):
        """ Test nested binary expressions, 4 * 6 / 2 """
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        test_string = "4 * 6 / 2;"
        scanner = Scanner(test_string, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()
        expr: Expression = statements[0].expression

        assert isinstance(expr, Binary)
        assert isinstance(expr.left, Binary)
        assert isinstance(expr.right, Literal)

        assert expr.operator.token_type == TokenType.SLASH
        assert expr.right.value == 2.0
        # Left will be 4 * 6
        assert expr.left.operator.token_type == TokenType.STAR
        assert expr.left.left.value == 4
        assert expr.left.right.value == 6

        result = Interpreter().visit_binary_expr(expr)

        assert result == 12
Пример #5
0
    def test_empty_list(self):
        # Test an empty list. This will raise an IndexError, since it's not supported,
        # and the calling party should fix this.

        parser = Parser([], on_token_error=None)

        with pytest.raises(IndexError):
            parser.parse()
Пример #6
0
    def test_interpret(self, mocker, expression, result):
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        scanner = Scanner(expression, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        result = Interpreter().interpret(statements)

        assert result == result
Пример #7
0
    def test_parser(self, mocker):
        # A 'simple' string:
        # (4 + 4) * (1 / 4) != -3.0 == "FOO" == true != false != nil;
        on_error_mock = mocker.MagicMock()

        tokens = [
            Token(token_type=TokenType.LEFT_PAREN, lexeme="(", literal=None, line=1),
            Token(token_type=TokenType.NUMBER, lexeme="4", literal=4.0, line=1),
            Token(token_type=TokenType.PLUS, lexeme="+", literal=None, line=1),
            Token(token_type=TokenType.NUMBER, lexeme="4", literal=4.0, line=1),
            Token(token_type=TokenType.RIGHT_PAREN, lexeme=")", literal=None, line=1),
            Token(token_type=TokenType.STAR, lexeme="*", literal=None, line=1),
            Token(token_type=TokenType.LEFT_PAREN, lexeme="(", literal=None, line=1),
            Token(token_type=TokenType.NUMBER, lexeme="1", literal=1.0, line=1),
            Token(token_type=TokenType.SLASH, lexeme="/", literal=None, line=1),
            Token(token_type=TokenType.NUMBER, lexeme="4", literal=4.0, line=1),
            Token(token_type=TokenType.RIGHT_PAREN, lexeme=")", literal=None, line=1),
            Token(token_type=TokenType.BANG_EQUAL, lexeme="!=", literal=None, line=1),
            Token(token_type=TokenType.MINUS, lexeme="-", literal=None, line=1),
            Token(token_type=TokenType.NUMBER, lexeme="3.0", literal=3.0, line=1),
            Token(token_type=TokenType.EQUAL_EQUAL, lexeme="==", literal=None, line=1),
            Token(token_type=TokenType.STRING, lexeme='"FOO"', literal="FOO", line=1),
            Token(token_type=TokenType.EQUAL_EQUAL, lexeme="==", literal=None, line=1),
            Token(token_type=TokenType.TRUE, lexeme="true", literal=None, line=1),
            Token(token_type=TokenType.BANG_EQUAL, lexeme="!=", literal=None, line=1),
            Token(token_type=TokenType.FALSE, lexeme="false", literal=None, line=1),
            Token(token_type=TokenType.BANG_EQUAL, lexeme="!=", literal=None, line=1),
            Token(token_type=TokenType.NIL, lexeme="nil", literal=None, line=1),
            Token(token_type=TokenType.SEMICOLON, lexeme=";", literal=None, line=1),
            Token(token_type=TokenType.EOF, lexeme="", literal=None, line=1),
        ]

        parser = Parser(tokens, on_token_error=on_error_mock)
        statements = parser.parse()
        expr = statements[0].expression

        # There musn't be an error
        assert not on_error_mock.called

        # The first statement will be Binary
        assert isinstance(expr, Binary)
        # We can expand the whole tree by hand, or just simply convert it to
        # a string and validate that

        ast = AstPrinter().print(expr)
        expected = (
            "(!= (!= (== (== (!= (* (group (+ 4.0 4.0)) (group (/ 1.0 4.0))) "
            "(- 3.0)) FOO) True) False) nil)"
        )

        assert ast == expected
Пример #8
0
    def test_only_eof_list(self, mocker):
        # When a new line is entered, there aren't any statements, and the parser will
        # return an empty list. No errors are raised

        on_error_mock = mocker.MagicMock()
        test_token = Token(token_type=TokenType.EOF, lexeme="", literal=None, line=1)

        parser = Parser([test_token], on_token_error=on_error_mock)
        statements = parser.parse()

        assert statements == []

        # No errors
        assert not on_error_mock.called
Пример #9
0
    def test_interpret_error(self, mocker):
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()
        on_interpret_error_mock = mocker.MagicMock()

        expression = '0 + "Foo";'

        scanner = Scanner(expression, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        Interpreter().interpret(statements, on_error=on_interpret_error_mock)

        # There will be an error
        assert on_interpret_error_mock.called
        assert "Operands must be two numbers or two strings" in str(
            on_interpret_error_mock.call_args)
Пример #10
0
    def test_for_parser(self, mocker):
        """ Test that we parse the for loop correctly """

        source = "for(var a = 0; ; a = a + 1) {}"

        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        scanner = Scanner(source, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        assert statements
        # Because the condition is missing, it must alway be true:
        assert statements[0].statements[1].condition.value is True

        assert not on_scanner_error_mock.called
        assert not on_parser_error_mock.called
Пример #11
0
    def test_missing_closing_bracket(self, mocker):
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        source = "(23+34"

        scanner = Scanner(source, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        # There will be an error. Scanner must be fine, but the error will be in the
        # parser. There will be no statements generated
        assert statements == []
        assert not on_scanner_error_mock.called
        assert on_parser_error_mock.called

        on_parser_error_mock.assert_called_once_with(
            tokens[-1], "Expect ')' after expression."
        )
Пример #12
0
    def test_invalid_assignment(self, mocker):
        """
        An identifiers must start with a letter [a-z], numbers are invalid and
        must raise an error
        """
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        expression = "var 0123foobar = 34;"

        scanner = Scanner(expression, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        assert statements == []
        assert not on_scanner_error_mock.called
        assert on_parser_error_mock.called

        on_parser_error_mock.assert_called_once_with(tokens[1], "Expect variable name.")
Пример #13
0
    def test_for_invalid_for_loop_parser(self, mocker):
        """ Test that we parse the for loop correctly """

        source = "for (var a = 0; a <= 5; a = a + 1) {}"
        left_missing = source.replace("(", "")
        right_missing = source.replace(")", "")

        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()

        scanner = Scanner(source, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        statements = parser.parse()

        assert statements
        assert not on_scanner_error_mock.called
        assert not on_parser_error_mock.called

        # Test a invalid statement, missing (
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()
        scanner = Scanner(left_missing, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        parser.parse()

        assert not on_scanner_error_mock.called
        assert on_parser_error_mock.called
        assert "Expect '(' after 'for'" in str(on_parser_error_mock.call_args_list[0])

        # Test a invalid statement, missing )
        on_scanner_error_mock = mocker.MagicMock()
        on_parser_error_mock = mocker.MagicMock()
        scanner = Scanner(right_missing, on_error=on_scanner_error_mock)
        tokens = scanner.scan_tokens()
        parser = Parser(tokens, on_token_error=on_parser_error_mock)
        parser.parse()

        assert not on_scanner_error_mock.called
        assert on_parser_error_mock.called
        assert "Expect ')' after for clauses" in str(
            on_parser_error_mock.call_args_list[0]
        )