2506 lines
73 KiB
Python
2506 lines
73 KiB
Python
import pytest
|
|
from compiler.parser import parse
|
|
from compiler.tokenizer import tokenize, L, SourceLocation
|
|
import compiler.ast as ast
|
|
from compiler.types import Int_Instance, Bool_Instance, FunType
|
|
|
|
def test_parser_bad_eoi() -> None:
|
|
tokens = tokenize('a + b * 5 - (3 + 7) / 10 4')
|
|
with pytest.raises(Exception):
|
|
parse(tokens)
|
|
|
|
tokens = tokenize('a + b * 5 - (3 + 7) / 10 )')
|
|
with pytest.raises(Exception):
|
|
parse(tokens)
|
|
|
|
tokens = tokenize('a + b * 5 - (3 + 7) / 10 asldkjaskjkajkdjsad')
|
|
with pytest.raises(Exception):
|
|
parse(tokens)
|
|
|
|
# def test_parser_missing_exp() -> None:
|
|
# tokens = tokenize('a + b * 5 - (3 + 7) / 10 >=')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert str(exc_info.value).endswith(': expected "(", an integer literal or an identifier')
|
|
|
|
# def test_parser_invalid_exp() -> None:
|
|
# tokens = tokenize('a + b * 5 - (3 + 7) / ;')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert str(exc_info.value).endswith(': expected "(", an integer literal or an identifier')
|
|
|
|
# def test_parser_missing_closing_parenthesis() -> None:
|
|
# tokens = tokenize('a + b * 5 - (3 + 7) / 10 + (5')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert str(exc_info.value).endswith('"(" not followed by ")"')
|
|
|
|
# def test_parser_empty_token_list() -> None:
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse([])
|
|
# assert str(exc_info.value) == 'len(tokens) == 0'
|
|
|
|
# def test_parser_maths_exp() -> None:
|
|
# tokens = tokenize('a + b * 5 - (3 + 7) / 10')
|
|
# assert parse(tokens) == ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='+',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='*',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# )
|
|
# ),
|
|
# op='-',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Literal(location=L, value=3),
|
|
# op='+',
|
|
# right=ast.Literal(location=L, value=7)
|
|
# ),
|
|
# op='/',
|
|
# right=ast.Literal(location=L, value=10)
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_simple_function_call() -> None:
|
|
# tokens = tokenize('f(x)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_nested_function_call() -> None:
|
|
# tokens = tokenize('f(g(x))')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_function_call_with_multiple_args() -> None:
|
|
# tokens = tokenize('f(x, y, z)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.Identifier(location=L, name='x'),
|
|
# ast.Identifier(location=L, name='y'),
|
|
# ast.Identifier(location=L, name='z')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_function_call_with_nested_expressions() -> None:
|
|
# tokens = tokenize('f(x + y, g(z))')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# ),
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[ast.Identifier(location=L, name='z')]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_function_call_as_part_of_expression() -> None:
|
|
# tokens = tokenize('a * f(x) + 5')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='*',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_nested_parentheses_with_function_call() -> None:
|
|
# tokens = tokenize('(f(x))')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_complex_nested_function_calls() -> None:
|
|
# tokens = tokenize('f(g(h(1), 2), x + y)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='h',
|
|
# args=[ast.Literal(location=L, value=1)]
|
|
# ),
|
|
# ast.Literal(location=L, value=2)
|
|
# ]
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_if_then_else() -> None:
|
|
# tokens = tokenize('if a then b + c else x * y')
|
|
# assert parse(tokens) == ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='a'),
|
|
# then_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# ),
|
|
# else_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# )
|
|
|
|
# tokens = tokenize('if if a then d then b + c else x * y')
|
|
# assert parse(tokens) == ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='a'),
|
|
# then_exp=ast.Identifier(location=L, name='d'),
|
|
# else_exp=None
|
|
# ),
|
|
# then_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# ),
|
|
# else_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_while() -> None:
|
|
# tokens = tokenize('while a do b + c')
|
|
# assert parse(tokens) == ast.While(
|
|
# location=L,
|
|
# while_exp=ast.Identifier(location=L, name='a'),
|
|
# do_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_unary_ops() -> None:
|
|
# tokens = tokenize('not a')
|
|
# assert parse(tokens) == ast.UnaryOp(location=L, op='not', right=ast.Identifier(location=L, name='a'))
|
|
|
|
# tokens = tokenize('-a')
|
|
# assert parse(tokens) == ast.UnaryOp(location=L, op='-', right=ast.Identifier(location=L, name='a'))
|
|
|
|
# tokens = tokenize('b - f(-a)')
|
|
# assert parse(tokens) == ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='-',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.UnaryOp(location=L, op='-', right=ast.Identifier(location=L, name='a'))]
|
|
# )
|
|
# )
|
|
|
|
# tokens = tokenize('not not not a')
|
|
# assert parse(tokens) == ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.Identifier(location=L, name='a')
|
|
# )
|
|
# )
|
|
# )
|
|
|
|
# tokens = tokenize('---a')
|
|
# assert parse(tokens) == ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.Identifier(location=L, name='a')
|
|
# )
|
|
# )
|
|
# )
|
|
|
|
# tokens = tokenize('if if -a then not d then b + c else x * y')
|
|
# assert parse(tokens) == ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.UnaryOp(location=L, op='-', right=ast.Identifier(location=L, name='a')),
|
|
# then_exp=ast.UnaryOp(location=L, op='not', right=ast.Identifier(location=L, name='d')),
|
|
# else_exp=None
|
|
# ),
|
|
# then_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# ),
|
|
# else_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_simple_assignment() -> None:
|
|
# tokens = tokenize('a = b')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='b')
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_chained_assignment() -> None:
|
|
# tokens = tokenize('a = b = c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_assignment_with_arithmetic() -> None:
|
|
# tokens = tokenize('a = b + c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_chained_assignment_with_arithmetic() -> None:
|
|
# tokens = tokenize('a = b = c + d')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='c'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_assignment_with_logic() -> None:
|
|
# tokens = tokenize('a = b and c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='and',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_chained_assignment_with_logic() -> None:
|
|
# tokens = tokenize('a = b = c or d')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='c'),
|
|
# op='or',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_assignment_with_nested_expressions() -> None:
|
|
# tokens = tokenize('a = (b + c) * d')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# ),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_assignment_with_function_call() -> None:
|
|
# tokens = tokenize('a = f(x)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_chained_assignment_with_function_call() -> None:
|
|
# tokens = tokenize('a = b = f(x)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=L, name='x')]
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_chained_assignment_with_mixed_precedence() -> None:
|
|
# tokens = tokenize('a = b + c = d * e')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='d'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='e')
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_blocks() -> None:
|
|
# assert parse(tokenize("""
|
|
# {
|
|
# f(a);
|
|
# x = y;
|
|
# f(x)
|
|
# }
|
|
# """)) == ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='a')]),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# ),
|
|
# ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='x')]),
|
|
# ]
|
|
# )
|
|
|
|
# assert parse(tokenize("""
|
|
# {
|
|
# f(a);
|
|
# x = y;
|
|
# f(x);
|
|
# }
|
|
# """)) == ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='a')]),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# ),
|
|
# ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='x')]),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
|
|
# def test_parser_block_empty() -> None:
|
|
# tokens = tokenize('{}')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(location=L, statements=[])
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_single_statement_no_semicolon() -> None:
|
|
# tokens = tokenize('{ a }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')])
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_single_statement_trailing_semicolon() -> None:
|
|
# tokens = tokenize('{ a; }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_multiple_statements() -> None:
|
|
# tokens = tokenize('{ a; b; c }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Identifier(location=L, name='c')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_multiple_statements_trailing_semicolon() -> None:
|
|
# tokens = tokenize('{ a; b; c; }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Identifier(location=L, name='c'),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_nested() -> None:
|
|
# tokens = tokenize('{ { a; } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_with_if() -> None:
|
|
# tokens = tokenize('{ if a then { b; } else { c } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='a'),
|
|
# then_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# ),
|
|
# else_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[ast.Identifier(location=L, name='c')]
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_with_assignment() -> None:
|
|
# tokens = tokenize('{ a = b; c = d; }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='b')
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='c'),
|
|
# op='=',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# ),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_missing_closing_brace() -> None:
|
|
# tokens = tokenize('{ a')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_block_invalid_empty_statement() -> None:
|
|
# tokens = tokenize('{ ; }')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'expected "(", an integer literal or an identifier' in str(exc_info.value)
|
|
|
|
# def test_parser_block_with_function_call() -> None:
|
|
# tokens = tokenize('{ f(a); }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='a')]),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_with_mixed_expressions() -> None:
|
|
# tokens = tokenize('{ a + b; if c then d; }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='b')
|
|
# ),
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='c'),
|
|
# then_exp=ast.Identifier(location=L, name='d'),
|
|
# else_exp=None
|
|
# ),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_top_level() -> None:
|
|
# tokens = tokenize('var a = 5')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='a',
|
|
# value=ast.Literal(location=L, value=5),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_in_block() -> None:
|
|
# tokens = tokenize('{ var b = 10 }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='b', value=ast.Literal(location=L, value=10), type_f=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_multiple_var_declarations_in_block() -> None:
|
|
# tokens = tokenize('{ var x = 1; var y = 2 }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=1), type_f=None),
|
|
# ast.Var(location=L, name='y', value=ast.Literal(location=L, value=2), type_f=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_expression() -> None:
|
|
# tokens = tokenize('var sum = a + b')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='sum',
|
|
# value=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='b')
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_trailing_semicolon() -> None:
|
|
# tokens = tokenize('{ var x = 5; }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=5), type_f=None),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_nested_blocks() -> None:
|
|
# tokens = tokenize('{ { var x = 1 } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=1), type_f=None)
|
|
# ]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_assignment() -> None:
|
|
# tokens = tokenize('var x = y = 5')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='x',
|
|
# value=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='y'),
|
|
# op='=',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_missing_initializer() -> None:
|
|
# tokens = tokenize('var x')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'expected "="' in str(exc_info.value)
|
|
|
|
# def test_parser_var_declaration_in_invalid_location() -> None:
|
|
# tokens = tokenize('f(var x = 5)')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert '"var" is only allowed directly inside blocks {} and in top-level expressions' in str(exc_info.value)
|
|
|
|
# def test_parser_var_declaration_invalid_identifier() -> None:
|
|
# tokens = tokenize('var 123 = 5')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'expected an identifier' in str(exc_info.value)
|
|
|
|
# def test_parser_var_declaration_with_boolean_literal() -> None:
|
|
# tokens = tokenize('var flag = true')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='flag',
|
|
# value=ast.Literal(location=L, value=True),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_in_if_block() -> None:
|
|
# tokens = tokenize('if x then { var y = 10 } else { var z = 20 }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='x'),
|
|
# then_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='y', value=ast.Literal(location=L, value=10), type_f=None)
|
|
# ]
|
|
# ),
|
|
# else_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='z', value=ast.Literal(location=L, value=20), type_f=None)
|
|
# ]
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_function_call_value() -> None:
|
|
# tokens = tokenize('var result = calculate(5)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='result',
|
|
# value=ast.FunctionCall(
|
|
# location=L,
|
|
# name='calculate',
|
|
# args=[ast.Literal(location=L, value=5)]
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_logical_expression() -> None:
|
|
# tokens = tokenize('var valid = a > 5 and b != 10')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='valid',
|
|
# value=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='>',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# ),
|
|
# op='and',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='!=',
|
|
# right=ast.Literal(location=L, value=10)
|
|
# )
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_with_mixed_block() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# var a = 1;
|
|
# b = a * 2;
|
|
# var c = b + 3
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='a', value=ast.Literal(location=L, value=1), type_f=None),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='*',
|
|
# right=ast.Literal(location=L, value=2)
|
|
# )
|
|
# ),
|
|
# ast.Var(
|
|
# location=L,
|
|
# name='c',
|
|
# value=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Literal(location=L, value=3)
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_in_while_block() -> None:
|
|
# tokens = tokenize('while true do { var counter = 0 }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.While(
|
|
# location=L,
|
|
# while_exp=ast.Literal(location=L, value=True),
|
|
# do_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='counter', value=ast.Literal(location=L, value=0), type_f=None)
|
|
# ]
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# # def test_parser_var_declaration_with_type_annotation() -> None:
|
|
# # tokens = tokenize('var x: Int = 5')
|
|
# # parsed = parse(tokens)
|
|
# # expected = ast.Var(location=L, name='x', value=ast.Literal(location=L, value=5), type_f=None)
|
|
# # assert parsed == expected, f'Expected type annotations to be ignored, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_followed_by_usage() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# var x = 10;
|
|
# x + 5
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=10), type_f=None),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='+',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_var_declaration_in_nested_expression_error() -> None:
|
|
# tokens = tokenize('a + var x = 5')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'allowed directly' in str(exc_info.value), 'Should prevent var in expressions'
|
|
|
|
# def test_parser_var_declaration_missing_value_error() -> None:
|
|
# tokens = tokenize('var x =')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'expected "(", an integer literal or an identifier' in str(exc_info.value)
|
|
|
|
# def test_parser_block_auto_semicolon_after_brace_valid() -> None:
|
|
# tokens = tokenize('{ { a } { b } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')]),
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='b')])
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_consecutive_expressions_in_block_invalid() -> None:
|
|
# tokens = tokenize('{ a b }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_block_after_if_without_semicolon_valid() -> None:
|
|
# tokens = tokenize('{ if true then { a } b }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Literal(location=L, value=True),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')]),
|
|
# else_exp=None
|
|
# ),
|
|
# ast.Identifier(location=L, name='b')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_block_after_if_with_semicolon_valid() -> None:
|
|
# tokens = tokenize('{ if true then { a }; b }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Literal(location=L, value=True),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')]),
|
|
# else_exp=None
|
|
# ),
|
|
# ast.Identifier(location=L, name='b')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_consecutive_after_if_block_invalid() -> None:
|
|
# tokens = tokenize('{ if true then { a } b c }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_mixed_implicit_explicit_semicolons_valid() -> None:
|
|
# tokens = tokenize('{ if true then { a } b; c }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Literal(location=L, value=True),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')]),
|
|
# else_exp=None
|
|
# ),
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Identifier(location=L, name='c')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_if_else_with_implicit_semicolon_valid() -> None:
|
|
# tokens = tokenize('{ if true then { a } else { b } 3 }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Literal(location=L, value=True),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')]),
|
|
# else_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='b')])
|
|
# ),
|
|
# ast.Literal(location=L, value=3)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_nested_blocks_in_assignment_valid() -> None:
|
|
# tokens = tokenize('x = { { f(a) } { b } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[ast.FunctionCall(location=L, name='f', args=[ast.Identifier(location=L, name='a')])]
|
|
# ),
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='b')])
|
|
# ]
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_consecutive_expressions_non_brace_invalid() -> None:
|
|
# tokens = tokenize('{ a { b } }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_var_declarations_missing_semicolon_invalid() -> None:
|
|
# tokens = tokenize('{ var x = 5 var y = 6 }')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'unexpected token "var"' in str(exc_info.value), "Should require semicolon between var declarations"
|
|
|
|
# def test_parser_function_call_followed_by_block_invalid() -> None:
|
|
# tokens = tokenize('{ f() { g() } }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_while_block_followed_by_expression_valid() -> None:
|
|
# tokens = tokenize('{ while cond do { a } b }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.While(
|
|
# location=L,
|
|
# while_exp=ast.Identifier(location=L, name='cond'),
|
|
# do_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='a')])
|
|
# ),
|
|
# ast.Identifier(location=L, name='b')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should allow expression after while block without semicolon"
|
|
|
|
# def test_parser_if_else_and_while_without_semicolon_valid() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# if a then { b } else { c }
|
|
# while d do { e }
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='a'),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='b')]),
|
|
# else_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='c')])
|
|
# ),
|
|
# ast.While(
|
|
# location=L,
|
|
# while_exp=ast.Identifier(location=L, name='d'),
|
|
# do_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='e')])
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should allow consecutive blocks ending with } without semicolons"
|
|
|
|
# def test_parser_var_declaration_followed_by_block_without_semicolon_invalid() -> None:
|
|
# tokens = tokenize('{ var x = 5 { y } }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_nested_blocks_with_expressions_valid() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# { a; { b } }
|
|
# { c }; { d }
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='b')])
|
|
# ]
|
|
# ),
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='c')]),
|
|
# ast.Block(location=L, statements=[ast.Identifier(location=L, name='d')])
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should handle nested blocks and semicolons correctly"
|
|
|
|
# def test_parser_mixed_constructs_with_optional_semicolons_valid() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# if x then { y } else { z }
|
|
# while true do { var i = 0 }
|
|
# { f(a, b, 5, false) }
|
|
# g()
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='x'),
|
|
# then_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='y')]),
|
|
# else_exp=ast.Block(location=L, statements=[ast.Identifier(location=L, name='z')])
|
|
# ),
|
|
# ast.While(
|
|
# location=L,
|
|
# while_exp=ast.Literal(location=L, value=True),
|
|
# do_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='i', value=ast.Literal(location=L, value=0), type_f=None)
|
|
# ]
|
|
# )
|
|
# ),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Literal(location=L, value=5),
|
|
# ast.Literal(location=L, value=False)
|
|
# ]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.FunctionCall(location=L, name='g', args=[])
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should parse mixed constructs with optional semicolons"
|
|
|
|
# def test_parser_deeply_nested_if_else() -> None:
|
|
# tokens = tokenize('''
|
|
# if a then
|
|
# if b then
|
|
# if c then 1
|
|
# else 2
|
|
# else 3
|
|
# else 4
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='a'),
|
|
# then_exp=ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='b'),
|
|
# then_exp=ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='c'),
|
|
# then_exp=ast.Literal(location=L, value=1),
|
|
# else_exp=ast.Literal(location=L, value=2)
|
|
# ),
|
|
# else_exp=ast.Literal(location=L, value=3)
|
|
# ),
|
|
# else_exp=ast.Literal(location=L, value=4)
|
|
# )
|
|
# assert parsed == expected, "Should parse deeply nested if-else structures"
|
|
|
|
# def test_parser_mixed_block_with_all_features() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# var x = f(a, b + c);
|
|
# while x > 0 do {
|
|
# if y then {
|
|
# z = z * 2;
|
|
# print(z)
|
|
# } else {
|
|
# z = z / 2
|
|
# };
|
|
# x = x - 1
|
|
# };
|
|
# x
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(
|
|
# location=L,
|
|
# name='x',
|
|
# value=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# ]
|
|
# ),
|
|
# type_f=None
|
|
# ),
|
|
# ast.While(
|
|
# location=L,
|
|
# while_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='>',
|
|
# right=ast.Literal(location=L, value=0)
|
|
# ),
|
|
# do_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(location=L, name='y'),
|
|
# then_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='z'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='z'),
|
|
# op='*',
|
|
# right=ast.Literal(location=L, value=2)
|
|
# )
|
|
# ),
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='print',
|
|
# args=[ast.Identifier(location=L, name='z')]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# else_exp=ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='z'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='z'),
|
|
# op='/',
|
|
# right=ast.Literal(location=L, value=2)
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='-',
|
|
# right=ast.Literal(location=L, value=1)
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# ),
|
|
# ast.Identifier(location=L, name='x')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should handle complex blocks with mixed features"
|
|
|
|
# def test_parser_function_calls_as_arguments() -> None:
|
|
# tokens = tokenize('f(g(h(1)), x + y(z))')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='h',
|
|
# args=[ast.Literal(location=L, value=1)]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='+',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='y',
|
|
# args=[ast.Identifier(location=L, name='z')]
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should handle nested function calls as arguments"
|
|
|
|
# def test_parser_assignment_in_condition() -> None:
|
|
# tokens = tokenize('if (x = read()) > 0 then print(x) else 0')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.FunctionCall(location=L, name='read', args=[])
|
|
# ),
|
|
# op='>',
|
|
# right=ast.Literal(location=L, value=0)
|
|
# ),
|
|
# then_exp=ast.FunctionCall(location=L, name='print', args=[ast.Identifier(location=L, name='x')]),
|
|
# else_exp=ast.Literal(location=L, value=0)
|
|
# )
|
|
# assert parsed == expected, "Should allow assignments in conditional expressions"
|
|
|
|
# def test_parser_chained_unary_operations() -> None:
|
|
# tokens = tokenize('not -(-x + y)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.Identifier(location=L, name='x')
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Should handle complex unary operator combinations"
|
|
|
|
# def test_parser_mixed_type_block_result() -> None:
|
|
# tokens = tokenize('''
|
|
# {
|
|
# { a; { b; c } };
|
|
# { d() };
|
|
# e
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='b'),
|
|
# ast.Identifier(location=L, name='c')
|
|
# ]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[ast.FunctionCall(location=L, name='d', args=[])]
|
|
# ),
|
|
# ast.Identifier(location=L, name='e')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, "Should handle nested blocks with mixed statement types"
|
|
|
|
# def test_parser_complex_operator_precedence() -> None:
|
|
# tokens = tokenize('a + b * c == d and not e or f % g <= 5')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='+',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# ),
|
|
# op='==',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# ),
|
|
# op='and',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.Identifier(location=L, name='e')
|
|
# )
|
|
# ),
|
|
# op='or',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='f'),
|
|
# op='%',
|
|
# right=ast.Identifier(location=L, name='g')
|
|
# ),
|
|
# op='<=',
|
|
# right=ast.Literal(location=L, value=5)
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Should respect operator precedence hierarchy"
|
|
|
|
# def test_parser_nested_arithmetic_and_comparison() -> None:
|
|
# tokens = tokenize('x * y + z < a - b % c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='z')
|
|
# ),
|
|
# op='<',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='-',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='%',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Should prioritize % then */- then + then <"
|
|
|
|
# def test_parser_logical_ops_with_comparisons() -> None:
|
|
# tokens = tokenize('a == b and c != d or e < f')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='==',
|
|
# right=ast.Identifier(location=L, name='b')
|
|
# ),
|
|
# op='and',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='c'),
|
|
# op='!=',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# )
|
|
# ),
|
|
# op='or',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='e'),
|
|
# op='<',
|
|
# right=ast.Identifier(location=L, name='f')
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Should group comparisons first, then and/or"
|
|
|
|
# def test_parser_right_associative_assignment() -> None:
|
|
# tokens = tokenize('a = b = c + d * e')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='c'),
|
|
# op='+',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='d'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='e')
|
|
# )
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Assignment should be right-associative"
|
|
|
|
# def test_parser_unary_ops_with_arithmetic() -> None:
|
|
# tokens = tokenize('not -x * y')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.Identifier(location=L, name='x')
|
|
# )
|
|
# ),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='y')
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_function_calls_in_expressions() -> None:
|
|
# tokens = tokenize('a + f(b * c) >= g(d, e) and not h()')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='a'),
|
|
# op='+',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='*',
|
|
# right=ast.Identifier(location=L, name='c')
|
|
# )
|
|
# ]
|
|
# )
|
|
# ),
|
|
# op='>=',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[
|
|
# ast.Identifier(location=L, name='d'),
|
|
# ast.Identifier(location=L, name='e')
|
|
# ]
|
|
# )
|
|
# ),
|
|
# op='and',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.FunctionCall(
|
|
# location=L,
|
|
# name='h',
|
|
# args=[]
|
|
# )
|
|
# )
|
|
# )
|
|
# assert parsed == expected, "Should handle function calls within expressions"
|
|
|
|
# def test_parser_locations_literal() -> None:
|
|
# tokens = tokenize('123')
|
|
# parsed = parse(tokens)
|
|
# expected_loc = SourceLocation(file='', line=0, column=0)
|
|
# assert parsed == ast.Literal(location=expected_loc, value=123)
|
|
|
|
# def test_parser_locations_identifier() -> None:
|
|
# tokens = tokenize('x')
|
|
# parsed = parse(tokens)
|
|
# expected_loc = SourceLocation(file='', line=0, column=0)
|
|
# assert parsed == ast.Identifier(location=expected_loc, name='x')
|
|
|
|
# def test_parser_locations_binary_op() -> None:
|
|
# tokens = tokenize('1 + 2')
|
|
# parsed = parse(tokens)
|
|
# op_loc = SourceLocation(file='', line=0, column=2)
|
|
# left_loc = SourceLocation(file='', line=0, column=0)
|
|
# right_loc = SourceLocation(file='', line=0, column=4)
|
|
# assert parsed == ast.BinaryOp(
|
|
# location=op_loc,
|
|
# left=ast.Literal(location=left_loc, value=1),
|
|
# op='+',
|
|
# right=ast.Literal(location=right_loc, value=2)
|
|
# )
|
|
|
|
# def test_parser_locations_function_call() -> None:
|
|
# tokens = tokenize('f(x)')
|
|
# parsed = parse(tokens)
|
|
# f_loc = SourceLocation(file='', line=0, column=0)
|
|
# x_loc = SourceLocation(file='', line=0, column=2)
|
|
# assert parsed == ast.FunctionCall(
|
|
# location=f_loc,
|
|
# name='f',
|
|
# args=[ast.Identifier(location=x_loc, name='x')]
|
|
# )
|
|
|
|
# def test_parser_locations_unary_op() -> None:
|
|
# tokens = tokenize('-x')
|
|
# parsed = parse(tokens)
|
|
# op_loc = SourceLocation(file='', line=0, column=0)
|
|
# x_loc = SourceLocation(file='', line=0, column=1)
|
|
# assert parsed == ast.UnaryOp(
|
|
# location=op_loc,
|
|
# op='-',
|
|
# right=ast.Identifier(location=x_loc, name='x')
|
|
# )
|
|
|
|
# def test_parser_locations_if_statement() -> None:
|
|
# tokens = tokenize('if a then b else c')
|
|
# parsed = parse(tokens)
|
|
# if_loc = SourceLocation(file='', line=0, column=0)
|
|
# a_loc = SourceLocation(file='', line=0, column=3)
|
|
# b_loc = SourceLocation(file='', line=0, column=10)
|
|
# c_loc = SourceLocation(file='', line=0, column=17)
|
|
# assert parsed == ast.If(
|
|
# location=if_loc,
|
|
# cond_exp=ast.Identifier(location=a_loc, name='a'),
|
|
# then_exp=ast.Identifier(location=b_loc, name='b'),
|
|
# else_exp=ast.Identifier(location=c_loc, name='c')
|
|
# )
|
|
|
|
# def test_parser_locations_nested_function_calls() -> None:
|
|
# tokens = tokenize('f(g(h(1)), 2 + x)')
|
|
# parsed = parse(tokens)
|
|
# assert parsed == ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# name='f',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=0, column=2),
|
|
# name='g',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=0, column=4),
|
|
# name='h',
|
|
# args=[ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=6),
|
|
# value=1
|
|
# )]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=12),
|
|
# left=ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=10),
|
|
# value=2
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=14),
|
|
# name='x'
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
|
|
# def test_parser_locations_assignment_chain() -> None:
|
|
# tokens = tokenize('a = b = c + d')
|
|
# parsed = parse(tokens)
|
|
# assert parsed == ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=2),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# name='a'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=6),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=4),
|
|
# name='b'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=10),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=8),
|
|
# name='c'
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=12),
|
|
# name='d'
|
|
# )
|
|
# )
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_locations_multiline_nested_if() -> None:
|
|
# tokens = tokenize('''if
|
|
# x < 10 then
|
|
# if y then 1
|
|
# else 2
|
|
# else 3''')
|
|
# parsed = parse(tokens)
|
|
# assert parsed == ast.If(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# cond_exp=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=1, column=2),
|
|
# name='x'
|
|
# ),
|
|
# op='<',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=1, column=6),
|
|
# value=10
|
|
# )
|
|
# ),
|
|
# then_exp=ast.If(
|
|
# location=SourceLocation(file='', line=2, column=4),
|
|
# cond_exp=ast.Identifier(
|
|
# location=SourceLocation(file='', line=2, column=7),
|
|
# name='y'
|
|
# ),
|
|
# then_exp=ast.Literal(
|
|
# location=SourceLocation(file='', line=2, column=12),
|
|
# value=1
|
|
# ),
|
|
# else_exp=ast.Literal(
|
|
# location=SourceLocation(file='', line=3, column=9),
|
|
# value=2
|
|
# )
|
|
# ),
|
|
# else_exp=ast.Literal(
|
|
# location=SourceLocation(file='', line=4, column=7),
|
|
# value=3
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_locations_complex_assignment_in_condition() -> None:
|
|
# tokens = tokenize('if (x = read()) > 0 then print(x + 1)')
|
|
# parsed = parse(tokens)
|
|
# assert parsed == ast.If(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# cond_exp=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=13),
|
|
# left=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=6),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=4),
|
|
# name='x'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=0, column=9),
|
|
# name='read',
|
|
# args=[]
|
|
# )
|
|
# ),
|
|
# op='>',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=15),
|
|
# value=0
|
|
# )
|
|
# ),
|
|
# then_exp=ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=0, column=22),
|
|
# name='print',
|
|
# args=[
|
|
# ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=30),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=28),
|
|
# name='x'
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=32),
|
|
# value=1
|
|
# )
|
|
# )
|
|
# ]
|
|
# ),
|
|
# else_exp=None
|
|
# )
|
|
|
|
# def test_parser_locations_complex_while_block() -> None:
|
|
# tokens = tokenize('''while x > 0 do {
|
|
# x = x - 1;
|
|
# print(x)
|
|
# }''')
|
|
# parsed = parse(tokens)
|
|
# assert parsed == ast.While(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# while_exp=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=8),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=6),
|
|
# name='x'
|
|
# ),
|
|
# op='>',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=10),
|
|
# value=0
|
|
# )
|
|
# ),
|
|
# do_exp=ast.Block(
|
|
# location=SourceLocation(file='', line=0, column=14),
|
|
# statements=[
|
|
# ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=1, column=8),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# name='x'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=1, column=12),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=1, column=10),
|
|
# name='x'
|
|
# ),
|
|
# op='-',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=1, column=14),
|
|
# value=1
|
|
# )
|
|
# )
|
|
# ),
|
|
# ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=2, column=4),
|
|
# name='print',
|
|
# args=[ast.Identifier(
|
|
# location=SourceLocation(file='', line=2, column=10),
|
|
# name='x'
|
|
# )]
|
|
# )
|
|
# ]
|
|
# )
|
|
# )
|
|
|
|
# def test_parser_top_level_single_expression() -> None:
|
|
# tokens = tokenize('a')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# name='a'
|
|
# )
|
|
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_top_level_two_expressions() -> None:
|
|
# tokens = tokenize('a; b')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# statements=[
|
|
# ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# name='a'
|
|
# ),
|
|
# ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=3),
|
|
# name='b'
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_top_level_trailing_semicolon() -> None:
|
|
# tokens = tokenize('a; b;')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# statements=[
|
|
# ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=0),
|
|
# name='a'
|
|
# ),
|
|
# ast.Identifier(
|
|
# location=SourceLocation(file='', line=0, column=3),
|
|
# name='b'
|
|
# ),
|
|
# ast.Literal(
|
|
# location=SourceLocation(file='', line=0, column=5),
|
|
# value=None
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_top_level_mixed_types() -> None:
|
|
# tokens = tokenize('''
|
|
# var x = 5; # this is a comment that should be ignored
|
|
# x = x + 1;
|
|
# print(x)
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# statements=[
|
|
# ast.Var(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# name='x',
|
|
# value=ast.Literal(
|
|
# location=SourceLocation(file='', line=1, column=10),
|
|
# value=5
|
|
# ),
|
|
# type_f=None
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=2, column=7),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=2, column=4),
|
|
# name='x'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=2, column=11),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=2, column=9),
|
|
# name='x'
|
|
# ),
|
|
# op='+',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=2, column=13),
|
|
# value=1
|
|
# )
|
|
# )
|
|
# ),
|
|
# ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=3, column=4),
|
|
# name='print',
|
|
# args=[ast.Identifier(
|
|
# location=SourceLocation(file='', line=3, column=10),
|
|
# name='x'
|
|
# )]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_top_level_empty_semicolons() -> None:
|
|
# tokens = tokenize(';;')
|
|
# with pytest.raises(Exception) as exc_info:
|
|
# parse(tokens)
|
|
# assert 'expected "(", an integer literal or an identifier' in str(exc_info.value)
|
|
|
|
# def test_parser_top_level_complex_block() -> None:
|
|
# tokens = tokenize('''
|
|
# if true then x = 5; // blah blah blah
|
|
# while x > 0 do {
|
|
# print(x);
|
|
# x = x - 1
|
|
# }
|
|
# ''')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# statements=[
|
|
# ast.If(
|
|
# location=SourceLocation(file='', line=1, column=4),
|
|
# cond_exp=ast.Literal(
|
|
# location=SourceLocation(file='', line=1, column=7),
|
|
# value=True
|
|
# ),
|
|
# then_exp=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=1, column=16),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=1, column=14),
|
|
# name='x'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=1, column=18),
|
|
# value=5
|
|
# )
|
|
# ),
|
|
# else_exp=None
|
|
# ),
|
|
# ast.While(
|
|
# location=SourceLocation(file='', line=2, column=4),
|
|
# while_exp=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=2, column=11),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=2, column=9),
|
|
# name='x'
|
|
# ),
|
|
# op='>',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=2, column=13),
|
|
# value=0
|
|
# )
|
|
# ),
|
|
# do_exp=ast.Block(
|
|
# location=SourceLocation(file='', line=2, column=17),
|
|
# statements=[
|
|
# ast.FunctionCall(
|
|
# location=SourceLocation(file='', line=3, column=6),
|
|
# name='print',
|
|
# args=[ast.Identifier(
|
|
# location=SourceLocation(file='', line=3, column=12),
|
|
# name='x'
|
|
# )]
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=4, column=9),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=4, column=6),
|
|
# name='x'
|
|
# ),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=4, column=13),
|
|
# left=ast.Identifier(
|
|
# location=SourceLocation(file='', line=4, column=11),
|
|
# name='x'
|
|
# ),
|
|
# op='-',
|
|
# right=ast.Literal(
|
|
# location=SourceLocation(file='', line=4, column=15),
|
|
# value=1
|
|
# )
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_complex_block_with_var_and_nested_blocks() -> None:
|
|
# tokens = tokenize('a; b % g; { var c = f((5 + d) * f - e)}; x = 2; {{}};')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(location=L, name='a'),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='b'),
|
|
# op='%',
|
|
# right=ast.Identifier(location=L, name='g')
|
|
# ),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(
|
|
# location=L,
|
|
# name='c',
|
|
# value=ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Literal(location=L, value=5),
|
|
# op='+',
|
|
# right=ast.Identifier(location=L, name='d')
|
|
# ),
|
|
# op = '*',
|
|
# right=ast.Identifier(location=L, name='f')),
|
|
# op='-',
|
|
# right=ast.Identifier(location=L, name='e')
|
|
# )
|
|
# ]
|
|
# ),
|
|
# type_f=None
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(location=L, name='x'),
|
|
# op='=',
|
|
# right=ast.Literal(location=L, value=2)
|
|
# ),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[]
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.Literal(location=L, value=None)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# def test_parser_if_expression_in_binary_op() -> None:
|
|
# tokens = tokenize('1 + if true then 2 else 3')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Literal(location=L, value=1),
|
|
# op='+',
|
|
# right=ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Literal(location=L, value=True),
|
|
# then_exp=ast.Literal(location=L, value=2),
|
|
# else_exp=ast.Literal(location=L, value=3)
|
|
# )
|
|
# )
|
|
# assert parsed == expected, f'Expected {expected}, got {parsed}'
|
|
|
|
# # def test_parser_complex_typed_var_with_locations() -> None:
|
|
# # code = 'var obj: ((a, b, c) => T, apple, banana, coconut) => () => T = 5'
|
|
# # tokens = tokenize(code)
|
|
# # parsed = parse(tokens)
|
|
|
|
# # # Build expected locations based on token positions
|
|
# # expected = ast.Var(
|
|
# # location=SourceLocation(file='', line=0, column=0), # 'var' position
|
|
# # name='obj',
|
|
# # type=ast.Type(
|
|
# # # location=SourceLocation(file='', line=0, column=9), # First '('
|
|
# # params=[
|
|
# # # (a, b, c) => T
|
|
# # ast.Type(
|
|
# # # location=SourceLocation(file='', line=0, column=10), # Inner '('
|
|
# # params=[
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=11), 'a'),
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=14), 'b'),
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=17), 'c')
|
|
# # ],
|
|
# # result=ast.Identifier(SourceLocation(file='', line=0, column=23), 'T')
|
|
# # ),
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=26), 'apple'),
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=33), 'banana'),
|
|
# # ast.Identifier(SourceLocation(file='', line=0, column=41), 'coconut')
|
|
# # ],
|
|
# # result=ast.Type(
|
|
# # # location=SourceLocation(file='', line=0, column=53), # Empty param '('
|
|
# # params=[],
|
|
# # result=ast.Identifier(SourceLocation(file='', line=0, column=59), 'T')
|
|
# # )
|
|
# # ),
|
|
# # value=ast.Literal(SourceLocation(file='', line=0, column=63), 5)
|
|
# # )
|
|
|
|
# # assert parsed == expected, f'''
|
|
# # Expected locations:
|
|
# # {expected}
|
|
|
|
# # Actual locations:
|
|
# # {parsed}
|
|
# # '''
|
|
|
|
# def test_parser_literals_and_identifiers() -> None:
|
|
# tokens = tokenize('42 true false x')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_binary_ops_precedence() -> None:
|
|
# tokens = tokenize('a + b * c - d / e')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'a'),
|
|
# op='+',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'b'),
|
|
# op='*',
|
|
# right=ast.Identifier(L, 'c')
|
|
# )
|
|
# ),
|
|
# op='-',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'd'),
|
|
# op='/',
|
|
# right=ast.Identifier(L, 'e')
|
|
# )
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_parentheses() -> None:
|
|
# tokens = tokenize('(a + b) * c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'a'),
|
|
# op='+',
|
|
# right=ast.Identifier(L, 'b')
|
|
# ),
|
|
# op='*',
|
|
# right=ast.Identifier(L, 'c')
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_function_calls() -> None:
|
|
# tokens = tokenize('f(g(x, y + z), 42)')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.FunctionCall(
|
|
# location=L,
|
|
# name='f',
|
|
# args=[
|
|
# ast.FunctionCall(
|
|
# location=L,
|
|
# name='g',
|
|
# args=[
|
|
# ast.Identifier(L, 'x'),
|
|
# ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'y'),
|
|
# op='+',
|
|
# right=ast.Identifier(L, 'z')
|
|
# )
|
|
# ]
|
|
# ),
|
|
# ast.Literal(L, 42)
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_if_expressions() -> None:
|
|
# tokens = tokenize('if a then b + c else x * y')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.If(
|
|
# location=L,
|
|
# cond_exp=ast.Identifier(L, 'a'),
|
|
# then_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'b'),
|
|
# op='+',
|
|
# right=ast.Identifier(L, 'c')
|
|
# ),
|
|
# else_exp=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'x'),
|
|
# op='*',
|
|
# right=ast.Identifier(L, 'y')
|
|
# )
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_blocks_1() -> None:
|
|
# tokens = tokenize('{ a; { b } }')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Identifier(L, 'a'),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[ast.Identifier(L, 'b')]
|
|
# )
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_var_declarations() -> None:
|
|
# tokens = tokenize('var x = 5')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='x',
|
|
# value=ast.Literal(L, 5),
|
|
# type_f=None
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_comparison_ops() -> None:
|
|
# tokens = tokenize('a < b >= c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'a'),
|
|
# op='<',
|
|
# right=ast.Identifier(L, 'b')
|
|
# ),
|
|
# op='>=',
|
|
# right=ast.Identifier(L, 'c')
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_assignment_right_associative() -> None:
|
|
# tokens = tokenize('a = b = c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'a'),
|
|
# op='=',
|
|
# right=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'b'),
|
|
# op='=',
|
|
# right=ast.Identifier(L, 'c')
|
|
# )
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_locations() -> None:
|
|
# tokens = tokenize('1 + 2')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=SourceLocation(file='', line=0, column=2),
|
|
# left=ast.Literal(SourceLocation(file='', line=0, column=0), 1),
|
|
# op='+',
|
|
# right=ast.Literal(SourceLocation(file='', line=0, column=4), 2)
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_typed_var_declaration() -> None:
|
|
# tokens = tokenize('var f: (Int, Bool) => Int = expr')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Var(
|
|
# location=L,
|
|
# name='f',
|
|
# type_f=FunType(
|
|
# # location=L,
|
|
# params=[
|
|
# Int_Instance,
|
|
# Bool_Instance
|
|
# ],
|
|
# result=Int_Instance
|
|
# ),
|
|
# value=ast.Identifier(L, 'expr')
|
|
# )
|
|
# assert isinstance(parsed, ast.Var)
|
|
# assert parsed.type_f == expected.type_f
|
|
|
|
# def test_parser_optional_semicolons() -> None:
|
|
# tokens = tokenize('{ a { b } }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_error_missing_semicolon() -> None:
|
|
# tokens = tokenize('{ a b }')
|
|
# with pytest.raises(Exception):
|
|
# parse(tokens)
|
|
|
|
# def test_parser_unary_ops_1() -> None:
|
|
# tokens = tokenize('not -x')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.UnaryOp(
|
|
# location=L,
|
|
# op='not',
|
|
# right=ast.UnaryOp(
|
|
# location=L,
|
|
# op='-',
|
|
# right=ast.Identifier(L, 'x')
|
|
# )
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_logical_ops() -> None:
|
|
# tokens = tokenize('a and b or c')
|
|
# parsed = parse(tokens)
|
|
# expected = ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.BinaryOp(
|
|
# location=L,
|
|
# left=ast.Identifier(L, 'a'),
|
|
# op='and',
|
|
# right=ast.Identifier(L, 'b')
|
|
# ),
|
|
# op='or',
|
|
# right=ast.Identifier(L, 'c')
|
|
# )
|
|
# assert parsed == expected
|
|
|
|
# def test_parser_misc() -> None:
|
|
# exp = """var x = 1; { var x = 2; x } x"""
|
|
# tokens = tokenize(exp)
|
|
# parsed = parse(tokens)
|
|
# expected = ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=1), type_f=None),
|
|
# ast.Block(
|
|
# location=L,
|
|
# statements=[
|
|
# ast.Var(location=L, name='x', value=ast.Literal(location=L, value=2), type_f=None),
|
|
# ast.Identifier(location=L, name='x')
|
|
# ]
|
|
# ),
|
|
# ast.Identifier(location=L, name='x')
|
|
# ]
|
|
# )
|
|
# assert parsed == expected
|