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