Files
2026-06-24 17:24:04 +02:00

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