# Part of the Tapl Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception


from tapl_lang.core import parser, syntax
from tapl_lang.lib import terms
from tapl_lang.pythonlike import grammar
from tapl_lang.pythonlike import rule_names as rn


def parse_expr(text: str, start_rule: str, *, mode: syntax.Term = terms.MODE_SAFE, debug=False) -> syntax.Term:
    return parser.parse_text(
        text,
        grammar=parser.Grammar(grammar.get_grammar().rule_map, start_rule),
        debug=debug,
        config=parser.Config(mode=mode),
    )


def create_loc(start_line: int, start_col: int, end_line: int, end_col: int) -> syntax.Location:
    return syntax.Location(
        start=syntax.Position(line=start_line, column=start_col), end=syntax.Position(line=end_line, column=end_col)
    )


def test_compound_stmt__evaluate_only():
    actual = parse_expr('EVALUATE_ONLY:', rn.COMPOUND_STMT)
    expected = terms.LayerOnly(layer_index=0, term=syntax.TermList(terms=[], is_placeholder=True))
    assert actual == expected


def test_compound_stmt__typecheck_only():
    actual = parse_expr('TYPECHECK_ONLY:', rn.COMPOUND_STMT)
    expected = terms.LayerOnly(layer_index=1, term=syntax.TermList(terms=[], is_placeholder=True))
    assert actual == expected


def test_compound_stmt__function_def():
    actual = parse_expr('def add(x: Int, y: Int) -> Int:', rn.COMPOUND_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedFunctionDef(
        location=create_loc(1, 0, 1, 31),
        name='add',
        parameters=[
            terms.Parameter(
                location=create_loc(1, 8, 1, 14),
                name='x',
                type_=syntax.Layers(
                    layers=[
                        syntax.Empty,
                        terms.TypedName(
                            location=create_loc(1, 11, 1, 14), id='Int', ctx='load', mode=terms.MODE_TYPECHECK
                        ),
                    ]
                ),
                default=syntax.Empty,
                mode=terms.MODE_EVALUATE,
                category=terms.ParamCategory.REGULAR,
            ),
            terms.Parameter(
                location=create_loc(1, 15, 1, 22),
                name='y',
                type_=syntax.Layers(
                    layers=[
                        syntax.Empty,
                        terms.TypedName(
                            location=create_loc(1, 19, 1, 22), id='Int', ctx='load', mode=terms.MODE_TYPECHECK
                        ),
                    ]
                ),
                default=syntax.Empty,
                mode=terms.MODE_EVALUATE,
                category=terms.ParamCategory.REGULAR,
            ),
        ],
        return_type=terms.TypedName(location=create_loc(1, 27, 1, 30), id='Int', ctx='load', mode=terms.MODE_TYPECHECK),
        body=syntax.TermList(terms=[], is_placeholder=True),
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_assignment__annotated():
    actual = parse_expr('x: Int = 42', rn.ASSIGNMENT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedAssign(
        location=create_loc(1, 0, 1, 11),
        target_name=terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='store', mode=terms.MODE_EVALUATE),
        target_type=terms.TypedName(location=create_loc(1, 3, 1, 6), id='Int', ctx='load', mode=terms.MODE_TYPECHECK),
        value=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 9, 1, 11)),
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_assignment__multi_targets():
    actual = parse_expr('a = b = c = 1', rn.ASSIGNMENT, mode=terms.MODE_EVALUATE)
    expected = terms.Assign(
        location=create_loc(1, 0, 1, 13),
        targets=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='store', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='store', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='store', mode=terms.MODE_EVALUATE),
        ],
        value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 12, 1, 13)),
    )
    assert actual == expected


def test_assignment__no_annotation():
    actual = parse_expr('x = 42', rn.ASSIGNMENT, mode=terms.MODE_EVALUATE)
    expected = terms.Assign(
        location=create_loc(1, 0, 1, 6),
        targets=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='store', mode=terms.MODE_EVALUATE),
        ],
        value=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 6)),
    )
    assert actual == expected


def test_simple_stmt__assignment():
    actual = parse_expr('x = 10', rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Assign(
        location=create_loc(1, 0, 1, 6),
        targets=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='store', mode=terms.MODE_EVALUATE),
        ],
        value=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 6)),
    )
    assert actual == expected


def test_simple_stmt__star_expressions():
    actual = parse_expr('a, b, c', rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Expr(
        location=create_loc(1, 0, 1, 7),
        value=terms.Tuple(
            location=create_loc(1, 0, 1, 7),
            elements=[
                terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
                terms.TypedName(location=create_loc(1, 3, 1, 4), id='b', ctx='load', mode=terms.MODE_EVALUATE),
                terms.TypedName(location=create_loc(1, 6, 1, 7), id='c', ctx='load', mode=terms.MODE_EVALUATE),
            ],
            ctx='load',
        ),
    )
    assert actual == expected


def test_simple_stmt__return():
    actual = parse_expr('return x + 1', rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedReturn(
        location=create_loc(1, 0, 1, 12),
        value=terms.BinOp(
            location=create_loc(1, 6, 1, 12),
            left=terms.TypedName(location=create_loc(1, 7, 1, 8), id='x', ctx='load', mode=terms.MODE_EVALUATE),
            op='+',
            right=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 11, 1, 12)),
        ),
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_simple_stmt__import_name():
    actual = parse_expr('import math, sys as system', rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedImport(
        location=create_loc(1, 0, 1, 26),
        names=[
            terms.Alias(name='math', asname=None),
            terms.Alias(name='sys', asname='system'),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_simple_stmt__raise():
    actual = parse_expr("raise ValueError('Invalid value')", rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Raise(
        location=create_loc(1, 0, 1, 33),
        exception=terms.Call(
            location=create_loc(1, 5, 1, 33),
            func=terms.TypedName(
                location=create_loc(1, 6, 1, 16), id='ValueError', ctx='load', mode=terms.MODE_EVALUATE
            ),
            args=[
                terms.StringLiteral(value='Invalid value', mode=terms.MODE_EVALUATE, location=create_loc(1, 17, 1, 32)),
            ],
            keywords=[],
        ),
        cause=syntax.Empty,
    )
    assert actual == expected


def test_simple_stmt__pass():
    actual = parse_expr('pass', rn.SIMPLE_STMT)
    expected = terms.Pass(location=create_loc(1, 0, 1, 4))
    assert actual == expected


def test_simple_stmt__del():
    actual = parse_expr('del my_var', rn.SIMPLE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Delete(
        location=create_loc(1, 0, 1, 10),
        targets=[
            terms.TypedName(location=create_loc(1, 4, 1, 10), id='my_var', ctx='delete', mode=terms.MODE_EVALUATE)
        ],
    )
    assert actual == expected


def test_import_name__single():
    actual = parse_expr('import module_name', rn.IMPORT_NAME, mode=terms.MODE_EVALUATE)
    expected = terms.TypedImport(
        location=create_loc(1, 0, 1, 18),
        names=[terms.Alias(name='module_name', asname=None)],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_import_name__multiple():
    actual = parse_expr('import mod1, mod2 as m2, mod3', rn.IMPORT_NAME, mode=terms.MODE_EVALUATE)
    expected = terms.TypedImport(
        location=create_loc(1, 0, 1, 29),
        names=[
            terms.Alias(name='mod1', asname=None),
            terms.Alias(name='mod2', asname='m2'),
            terms.Alias(name='mod3', asname=None),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_import_from__path():
    actual = parse_expr('import a.b.c', rn.IMPORT_NAME, mode=terms.MODE_EVALUATE)
    expected = terms.TypedImport(
        location=create_loc(1, 0, 1, 12),
        names=[terms.Alias(name='a.b.c', asname=None)],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_import_from__path_multiple():
    actual = parse_expr('import a.b.c as k, d.e', rn.IMPORT_NAME, mode=terms.MODE_EVALUATE)
    expected = terms.TypedImport(
        location=create_loc(1, 0, 1, 22),
        names=[
            terms.Alias(name='a.b.c', asname='k'),
            terms.Alias(name='d.e', asname=None),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_import_from__simple():
    actual = parse_expr('from os import path', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 19),
        module='os',
        names=[terms.Alias(name='path', asname=None)],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__multiple_names():
    actual = parse_expr('from os import path, getcwd as cwd', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 34),
        module='os',
        names=[
            terms.Alias(name='path', asname=None),
            terms.Alias(name='getcwd', asname='cwd'),
        ],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__dotted_module():
    actual = parse_expr('from os.path import join', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 24),
        module='os.path',
        names=[terms.Alias(name='join', asname=None)],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__relative_single_dot():
    actual = parse_expr('from . import utils', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 19),
        module=None,
        names=[terms.Alias(name='utils', asname=None)],
        level=1,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__relative_double_dot():
    actual = parse_expr('from .. import helpers', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 22),
        module=None,
        names=[terms.Alias(name='helpers', asname=None)],
        level=2,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__relative_ellipsis():
    actual = parse_expr('from ... import base', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 20),
        module=None,
        names=[terms.Alias(name='base', asname=None)],
        level=3,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__relative_with_module():
    actual = parse_expr('from .models import User', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 24),
        module='models',
        names=[terms.Alias(name='User', asname=None)],
        level=1,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__parenthesized():
    actual = parse_expr('from os import (path, getcwd)', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 29),
        module='os',
        names=[
            terms.Alias(name='path', asname=None),
            terms.Alias(name='getcwd', asname=None),
        ],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__parenthesized_trailing_comma():
    actual = parse_expr('from os import (path, getcwd,)', rn.IMPORT_FROM)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 30),
        module='os',
        names=[
            terms.Alias(name='path', asname=None),
            terms.Alias(name='getcwd', asname=None),
        ],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_import_from__via_import_stmt():
    actual = parse_expr('from sys import argv', rn.IMPORT_STMT)
    expected = terms.TypedImportFrom(
        location=create_loc(1, 0, 1, 20),
        module='sys',
        names=[terms.Alias(name='argv', asname=None)],
        level=0,
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_raise__expression():
    actual = parse_expr('raise 42', rn.RAISE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Raise(
        location=create_loc(1, 0, 1, 8),
        exception=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 8)),
        cause=syntax.Empty,
    )
    assert actual == expected


def test_raise__no_expression():
    actual = parse_expr('raise', rn.RAISE_STMT)
    expected = terms.Raise(
        location=create_loc(1, 0, 1, 5),
        exception=syntax.Empty,
        cause=syntax.Empty,
    )
    assert actual == expected


def test_del_stmt__single_target():
    actual = parse_expr('del x', rn.DEL_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Delete(
        location=create_loc(1, 0, 1, 5),
        targets=[terms.TypedName(location=create_loc(1, 4, 1, 5), id='x', ctx='delete', mode=terms.MODE_EVALUATE)],
    )
    assert actual == expected


def test_del_stmt__multi_targets():
    actual = parse_expr('del a, b, c', rn.DEL_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Delete(
        location=create_loc(1, 0, 1, 11),
        targets=[
            terms.TypedName(location=create_loc(1, 4, 1, 5), id='a', ctx='delete', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 7, 1, 8), id='b', ctx='delete', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 10, 1, 11), id='c', ctx='delete', mode=terms.MODE_EVALUATE),
        ],
    )
    assert actual == expected


def test_del_stmt__attribute():
    actual = parse_expr('del obj.attr', rn.DEL_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Delete(
        location=create_loc(1, 0, 1, 12),
        targets=[
            terms.Attribute(
                location=create_loc(1, 3, 1, 12),
                value=terms.TypedName(location=create_loc(1, 4, 1, 7), id='obj', ctx='load', mode=terms.MODE_EVALUATE),
                attr='attr',
                ctx='delete',
            )
        ],
    )
    assert actual == expected


def test_del_stmt__subscript():
    actual = parse_expr('del d[1]', rn.DEL_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.Delete(
        location=create_loc(1, 0, 1, 8),
        targets=[
            terms.Subscript(
                location=create_loc(1, 3, 1, 8),
                value=terms.TypedName(location=create_loc(1, 4, 1, 5), id='d', ctx='load', mode=terms.MODE_EVALUATE),
                slice=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 7)),
                ctx='delete',
            )
        ],
    )
    assert actual == expected


def test_star_targets__single():
    actual = parse_expr('variable', rn.STAR_TARGETS, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 8), id='variable', ctx='store', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_star_targets__multi():
    actual = parse_expr('var1, var2, var3', rn.STAR_TARGETS, mode=terms.MODE_EVALUATE)
    expected = terms.Tuple(
        location=create_loc(1, 0, 1, 16),
        elements=[
            terms.TypedName(location=create_loc(1, 0, 1, 4), id='var1', ctx='store', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 6, 1, 10), id='var2', ctx='store', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 12, 1, 16), id='var3', ctx='store', mode=terms.MODE_EVALUATE),
        ],
        ctx='store',
    )
    assert actual == expected


def tetst_star_target__targt_with_star_atom():
    actual = parse_expr('variable', rn.STAR_TARGET, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 8), id='variable', ctx='store', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_t_lookahead():
    punct = ['(', '[', '.']
    for p in punct:
        actual = parse_expr(p, rn.T_LOOKAHEAD)
        expected = grammar.TokenPunct(location=create_loc(1, 0, 1, 1), value=p)
        assert actual == expected


def test_t_primary__atom_failed():
    actual = parse_expr('variable', rn.T_PRIMARY, mode=terms.MODE_EVALUATE)
    expected = parser.ParseFailed
    assert actual == expected


def test_target_with_star_atom__name():
    actual = parse_expr('my_var', rn.TARGET_WITH_STAR_ATOM, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 6), id='my_var', ctx='store', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_target_with_star_atom__attribute():
    actual = parse_expr('obj.attr', rn.TARGET_WITH_STAR_ATOM, mode=terms.MODE_EVALUATE)
    expected = terms.Attribute(
        location=create_loc(1, 0, 1, 8),
        value=terms.TypedName(location=create_loc(1, 0, 1, 3), id='obj', ctx='load', mode=terms.MODE_EVALUATE),
        attr='attr',
        ctx='store',
    )
    assert actual == expected


def test_target_with_star_atom__attribute_nested():
    actual = parse_expr('obj.sub.attr', rn.TARGET_WITH_STAR_ATOM, mode=terms.MODE_EVALUATE)
    expected = terms.Attribute(
        location=create_loc(1, 0, 1, 12),
        value=terms.Attribute(
            location=create_loc(1, 0, 1, 7),
            value=terms.TypedName(location=create_loc(1, 0, 1, 3), id='obj', ctx='load', mode=terms.MODE_EVALUATE),
            attr='sub',
            ctx='load',
        ),
        attr='attr',
        ctx='store',
    )
    assert actual == expected


def test_target_with_star_atom__subscript():
    actual = parse_expr('d[:-1]', rn.TARGET_WITH_STAR_ATOM, mode=terms.MODE_EVALUATE, debug=True)
    expected = terms.Subscript(
        location=create_loc(1, 0, 1, 6),
        value=terms.TypedName(location=create_loc(1, 0, 1, 1), id='d', ctx='load', mode=terms.MODE_EVALUATE),
        slice=terms.Slice(
            location=create_loc(1, 2, 1, 5),
            lower=syntax.Empty,
            upper=terms.UnaryOp(
                location=create_loc(1, 3, 1, 5),
                op='-',
                operand=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 5)),
            ),
            step=syntax.Empty,
        ),
        ctx='load',
    )
    assert actual == expected


def test_target_with_star_atom__subscript_nested():
    actual = parse_expr('matrix[0][1:5]', rn.TARGET_WITH_STAR_ATOM, mode=terms.MODE_EVALUATE)
    expected = terms.Subscript(
        location=create_loc(1, 0, 1, 14),
        value=terms.Subscript(
            location=create_loc(1, 0, 1, 9),
            value=terms.TypedName(location=create_loc(1, 0, 1, 6), id='matrix', ctx='load', mode=terms.MODE_EVALUATE),
            slice=terms.IntegerLiteral(value=0, mode=terms.MODE_EVALUATE, location=create_loc(1, 7, 1, 8)),
            ctx='load',
        ),
        slice=terms.Slice(
            location=create_loc(1, 10, 1, 13),
            lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 10, 1, 11)),
            upper=terms.IntegerLiteral(value=5, mode=terms.MODE_EVALUATE, location=create_loc(1, 12, 1, 13)),
            step=syntax.Empty,
        ),
        ctx='load',
    )
    assert actual == expected


def test_slices__single():
    actual = parse_expr('a:b', rn.SLICES, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 3),
        lower=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
        upper=terms.TypedName(location=create_loc(1, 2, 1, 3), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        step=syntax.Empty,
    )
    assert actual == expected


def test_slices__single_as_tuple():
    actual = parse_expr('a,', rn.SLICES, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)]
    )
    assert actual == expected


def test_slices__multi():
    actual = parse_expr('x:y, 1:10, ::2', rn.SLICES, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            terms.Slice(
                location=create_loc(1, 0, 1, 3),
                lower=terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='load', mode=terms.MODE_EVALUATE),
                upper=terms.TypedName(location=create_loc(1, 2, 1, 3), id='y', ctx='load', mode=terms.MODE_EVALUATE),
                step=syntax.Empty,
            ),
            terms.Slice(
                location=create_loc(1, 4, 1, 9),
                lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
                upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 7, 1, 9)),
                step=syntax.Empty,
            ),
            terms.Slice(
                location=create_loc(1, 10, 1, 14),
                lower=syntax.Empty,
                upper=syntax.Empty,
                step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 13, 1, 14)),
            ),
        ],
    )
    assert actual == expected


def test_slices__multi_trailing_comma():
    actual = parse_expr('x:y, ::2,', rn.SLICES, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            terms.Slice(
                location=create_loc(1, 0, 1, 3),
                lower=terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='load', mode=terms.MODE_EVALUATE),
                upper=terms.TypedName(location=create_loc(1, 2, 1, 3), id='y', ctx='load', mode=terms.MODE_EVALUATE),
                step=syntax.Empty,
            ),
            terms.Slice(
                location=create_loc(1, 4, 1, 8),
                lower=syntax.Empty,
                upper=syntax.Empty,
                step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 7, 1, 8)),
            ),
        ],
    )
    assert actual == expected


def test_slice__named_expression():
    actual = parse_expr('x := 5', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.NamedExpr(
        location=create_loc(1, 0, 1, 6),
        target=terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='store', mode=terms.MODE_EVALUATE),
        value=terms.IntegerLiteral(value=5, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
    )
    assert actual == expected


def test_slice__single_expression():
    actual = parse_expr('y + 2', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 5),
        left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='y', ctx='load', mode=terms.MODE_EVALUATE),
        op='+',
        right=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 5)),
    )
    assert actual == expected


def test_slice__range_single():
    actual = parse_expr('1:10', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 4),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 2, 1, 4)),
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_single_no_upper():
    actual = parse_expr('1:', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 2),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=syntax.Empty,
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_single_no_lower():
    actual = parse_expr(':10', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 3),
        lower=syntax.Empty,
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 3)),
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_full():
    actual = parse_expr('1:10:2', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 6),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 2, 1, 4)),
        step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
    )
    assert actual == expected


def test_slice__range_no_lower():
    actual = parse_expr(':10:2', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 5),
        lower=syntax.Empty,
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 3)),
        step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 5)),
    )
    assert actual == expected


def test_slice__range_no_upper():
    actual = parse_expr('1::2', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 4),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=syntax.Empty,
        step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 3, 1, 4)),
    )
    assert actual == expected


def test_slice__range_no_step():
    actual = parse_expr('1:10:', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 5),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 2, 1, 4)),
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_no_lower_upper_step():
    actual = parse_expr('::', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 2),
        lower=syntax.Empty,
        upper=syntax.Empty,
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_no_upper_step():
    actual = parse_expr('1::', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 3),
        lower=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 1)),
        upper=syntax.Empty,
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_no_lower_step():
    actual = parse_expr(':10:', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 4),
        lower=syntax.Empty,
        upper=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 3)),
        step=syntax.Empty,
    )
    assert actual == expected


def test_slice__range_no_lower_upper():
    actual = parse_expr('::2', rn.SLICE, mode=terms.MODE_EVALUATE)
    expected = terms.Slice(
        location=create_loc(1, 0, 1, 3),
        lower=syntax.Empty,
        upper=syntax.Empty,
        step=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 2, 1, 3)),
    )
    assert actual == expected


def test_atom__name():
    actual = parse_expr('variable_name', rn.ATOM, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(
        location=create_loc(1, 0, 1, 13), id='variable_name', ctx='load', mode=terms.MODE_EVALUATE
    )
    assert actual == expected


def test_atom__true():
    actual = parse_expr('True', rn.ATOM)
    expected = terms.BooleanLiteral(value=True, mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 4))
    assert actual == expected


def test_atom__lifted_true():
    actual = parse_expr('^True', rn.ATOM)
    expected = terms.BooleanLiteral(value=True, mode=terms.MODE_LIFT, location=create_loc(1, 1, 1, 5))
    assert actual == expected


def test_atom__false():
    actual = parse_expr('False', rn.ATOM)
    expected = terms.BooleanLiteral(value=False, mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 5))
    assert actual == expected


def test_atom__none():
    actual = parse_expr('None', rn.ATOM)
    expected = terms.NoneLiteral(mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 4))
    assert actual == expected


def test_atom__string():
    actual = parse_expr("'hello, world!'", rn.ATOM)
    expected = terms.StringLiteral(value='hello, world!', mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 15))
    assert actual == expected


def test_atom__number_integer():
    actual = parse_expr('42', rn.ATOM)
    expected = terms.IntegerLiteral(value=42, mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 2))
    assert actual == expected


def test_atom__number_integer_lifted():
    actual = parse_expr('^42', rn.ATOM)
    expected = terms.IntegerLiteral(value=42, mode=terms.MODE_LIFT, location=create_loc(1, 1, 1, 3))
    assert actual == expected


def test_atom__number_float():
    actual = parse_expr('3.14', rn.ATOM)
    expected = terms.FloatLiteral(value=3.14, mode=terms.MODE_SAFE, location=create_loc(1, 0, 1, 4))
    assert actual == expected


def test_group__expression():
    actual = parse_expr('(x + 1)', rn.GROUP, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 1, 1, 6),
        left=terms.TypedName(location=create_loc(1, 1, 1, 2), id='x', ctx='load', mode=terms.MODE_EVALUATE),
        op='+',
        right=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
    )
    assert actual == expected


def test_group__named_expression():
    actual = parse_expr('(y := 10)', rn.GROUP, mode=terms.MODE_EVALUATE)
    expected = terms.NamedExpr(
        location=create_loc(1, 1, 1, 8),
        target=terms.TypedName(location=create_loc(1, 1, 1, 2), id='y', ctx='store', mode=terms.MODE_EVALUATE),
        value=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 8)),
    )
    assert actual == expected


def test_tuple__empty():
    actual = parse_expr('()', rn.ATOM)
    expected = terms.Tuple(
        location=create_loc(1, 0, 1, 2),
        elements=[],
        ctx='load',
    )
    assert actual == expected


def test_tuple__single():
    actual = parse_expr('(42,)', rn.ATOM)
    expected = terms.Tuple(
        location=create_loc(1, 0, 1, 5),
        elements=[
            terms.IntegerLiteral(value=42, mode=terms.MODE_SAFE, location=create_loc(1, 1, 1, 3)),
        ],
        ctx='load',
    )
    assert actual == expected


def test_tuple__multi():
    actual = parse_expr("(1, 'two', None)", rn.ATOM)
    expected = terms.Tuple(
        location=create_loc(1, 0, 1, 16),
        elements=[
            terms.IntegerLiteral(value=1, mode=terms.MODE_SAFE, location=create_loc(1, 1, 1, 2)),
            terms.StringLiteral(value='two', mode=terms.MODE_SAFE, location=create_loc(1, 4, 1, 9)),
            terms.NoneLiteral(mode=terms.MODE_SAFE, location=create_loc(1, 11, 1, 15)),
        ],
        ctx='load',
    )
    assert actual == expected


def test_list__empty():
    # FIXME: convert to MODE_TYPECHECK with unknow type, or No AUTO type inference.
    actual = parse_expr('[]', rn.LIST, mode=terms.MODE_EVALUATE)
    expected = terms.TypedList(
        location=create_loc(1, 0, 1, 2),
        elements=[],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_list__single():
    actual = parse_expr('[item]', rn.LIST, mode=terms.MODE_EVALUATE)
    expected = terms.TypedList(
        location=create_loc(1, 0, 1, 6),
        elements=[
            terms.TypedName(location=create_loc(1, 1, 1, 5), id='item', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_list__multi():
    actual = parse_expr('[a, b, c]', rn.LIST, mode=terms.MODE_EVALUATE)
    expected = terms.TypedList(
        location=create_loc(1, 0, 1, 9),
        elements=[
            terms.TypedName(location=create_loc(1, 1, 1, 2), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 7, 1, 8), id='c', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_set():
    actual = parse_expr('{x, y, z}', rn.SET, mode=terms.MODE_EVALUATE)
    expected = terms.TypedSet(
        location=create_loc(1, 0, 1, 9),
        elements=[
            terms.TypedName(location=create_loc(1, 1, 1, 2), id='x', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 4, 1, 5), id='y', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 7, 1, 8), id='z', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_kvpair():
    actual = parse_expr("'a': 1", rn.KVPAIR, mode=terms.MODE_EVALUATE)
    expected = grammar.KeyValuePair(
        key=terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
        value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
    )
    assert actual == expected


def test_double_starred_kvpair():
    actual = parse_expr("'a': 1", rn.DOUBLE_STARRED_KVPAIR, mode=terms.MODE_EVALUATE)
    expected = grammar.KeyValuePair(
        key=terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
        value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
    )
    assert actual == expected


def test_double_starred_kvpair__single():
    actual = parse_expr("'a': 1", rn.DOUBLE_STARRED_KVPAIRS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            grammar.KeyValuePair(
                key=terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
                value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
            ),
        ],
    )
    assert actual == expected


def test_double_starred_kvpairs__multi():
    actual = parse_expr("'a': 1, 'b': 2", rn.DOUBLE_STARRED_KVPAIRS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            grammar.KeyValuePair(
                key=terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
                value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
            ),
            grammar.KeyValuePair(
                key=terms.StringLiteral(value='b', mode=terms.MODE_EVALUATE, location=create_loc(1, 8, 1, 11)),
                value=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 13, 1, 14)),
            ),
        ],
    )
    assert actual == expected


def test_double_starred_kvpairs__trailing_comma():
    actual = parse_expr("'a': 1, 'b': 2,", rn.DOUBLE_STARRED_KVPAIRS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            grammar.KeyValuePair(
                key=terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
                value=terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 6)),
            ),
            grammar.KeyValuePair(
                key=terms.StringLiteral(value='b', mode=terms.MODE_EVALUATE, location=create_loc(1, 8, 1, 11)),
                value=terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 13, 1, 14)),
            ),
        ],
    )
    assert actual == expected


def test_dict__empty():
    actual = parse_expr('{}', rn.DICT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedDict(
        location=create_loc(1, 0, 1, 2),
        keys=[],
        values=[],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_dict__single():
    actual = parse_expr("{'a': 1}", rn.DICT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedDict(
        location=create_loc(1, 0, 1, 8),
        keys=[terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 4))],
        values=[terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 7))],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_dict__traling_comma():
    actual = parse_expr("{'a': 1,}", rn.DICT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedDict(
        location=create_loc(1, 0, 1, 9),
        keys=[terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 4))],
        values=[terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 7))],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_dict__multi():
    actual = parse_expr("{'a': 1, 'b': 2}", rn.DICT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedDict(
        location=create_loc(1, 0, 1, 16),
        keys=[
            terms.StringLiteral(value='a', mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 4)),
            terms.StringLiteral(value='b', mode=terms.MODE_EVALUATE, location=create_loc(1, 9, 1, 12)),
        ],
        values=[
            terms.IntegerLiteral(value=1, mode=terms.MODE_EVALUATE, location=create_loc(1, 6, 1, 7)),
            terms.IntegerLiteral(value=2, mode=terms.MODE_EVALUATE, location=create_loc(1, 14, 1, 15)),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_expression__disjunction():
    actual = parse_expr('a or b', rn.EXPRESSION, mode=terms.MODE_SAFE)
    expected = terms.TypedBoolOp(
        location=create_loc(1, 0, 1, 6),
        operator='or',
        values=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_SAFE),
            terms.TypedName(location=create_loc(1, 5, 1, 6), id='b', ctx='load', mode=terms.MODE_SAFE),
        ],
        mode=terms.MODE_SAFE,
    )
    assert actual == expected


def test_disjunction__single():
    actual = parse_expr('a', rn.DISJUNCTION, mode=terms.MODE_SAFE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_SAFE)
    assert actual == expected


def test_disjunction__or_chain():
    actual = parse_expr('a or b or c', rn.DISJUNCTION, mode=terms.MODE_EVALUATE)
    expected = terms.TypedBoolOp(
        location=create_loc(1, 0, 1, 11),
        operator='or',
        values=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 5, 1, 6), id='b', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 10, 1, 11), id='c', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_conjunction__and_chain():
    actual = parse_expr('x and y and z', rn.CONJUNCTION, mode=terms.MODE_EVALUATE)
    expected = terms.TypedBoolOp(
        location=create_loc(1, 0, 1, 13),
        operator='and',
        values=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 6, 1, 7), id='y', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 12, 1, 13), id='z', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_conjunction__single():
    actual = parse_expr('x', rn.CONJUNCTION, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_inversion__not():
    actual = parse_expr('not flag', rn.INVERSION, mode=terms.MODE_EVALUATE)
    expected = terms.BoolNot(
        location=create_loc(1, 0, 1, 8),
        operand=terms.TypedName(location=create_loc(1, 4, 1, 8), id='flag', ctx='load', mode=terms.MODE_EVALUATE),
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_inversion__single():
    actual = parse_expr('flag', rn.INVERSION, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 4), id='flag', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_comparison__single():
    actual = parse_expr('a', rn.COMPARISON, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_comparison__chain():
    actual = parse_expr('a < b <= c', rn.COMPARISON, mode=terms.MODE_EVALUATE)
    expected = terms.Compare(
        location=create_loc(1, 0, 1, 10),
        left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
        operators=['<', '<='],
        comparators=[
            terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 9, 1, 10), id='c', ctx='load', mode=terms.MODE_EVALUATE),
        ],
    )
    assert actual == expected


def test_comparison__operators():
    operators = [
        '==',
        '!=',
        '<=',
        '<',
        '>=',
        '>',
        'not in',
        'in',
        'is not',
        'is',
    ]
    for op in operators:
        op_length = len(op)
        expr = f'a {op} b'
        actual = parse_expr(expr, rn.COMPARISON, mode=terms.MODE_EVALUATE)
        expected = terms.Compare(
            location=create_loc(1, 0, 1, 4 + op_length),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            operators=[op],
            comparators=[
                terms.TypedName(
                    location=create_loc(1, 3 + op_length, 1, 4 + op_length),
                    id='b',
                    ctx='load',
                    mode=terms.MODE_EVALUATE,
                ),
            ],
        )
        assert actual == expected, f'Failed for operator: {expr}'


def test_bitwise_or__chain():
    actual = parse_expr('a | b | c', rn.BITWISE_OR, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 9),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 5),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='|',
            right=terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='|',
        right=terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_bitwise_or__single():
    actual = parse_expr('a', rn.BITWISE_OR, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_bitwise_xor__whitespace_required():
    actual = parse_expr('a ^b', rn.BITWISE_XOR, mode=terms.MODE_EVALUATE)
    assert isinstance(actual, syntax.ErrorTerm)


def test_bitwise_xor__chain():
    actual = parse_expr('a ^ b ^ c', rn.BITWISE_XOR, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 9),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 5),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='^',
            right=terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='^',
        right=terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_bitwise_xor__single():
    actual = parse_expr('a', rn.BITWISE_XOR, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_bitwise_and__chain():
    actual = parse_expr('a & b & c', rn.BITWISE_AND, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 9),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 5),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='&',
            right=terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='&',
        right=terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_bitwise_and__single():
    actual = parse_expr('a', rn.BITWISE_AND, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_bitwise_shift__chain():
    actual = parse_expr('a << b >> c', rn.SHIFT_EXPR, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 11),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 6),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='<<',
            right=terms.TypedName(location=create_loc(1, 5, 1, 6), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='>>',
        right=terms.TypedName(location=create_loc(1, 10, 1, 11), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_bitwise_shift__single():
    actual = parse_expr('a', rn.SHIFT_EXPR, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_sum__invalid_arithmetic():
    actual = parse_expr('a + not b', rn.SUM, mode=terms.MODE_EVALUATE)
    expected = syntax.ErrorTerm(
        message="'not' after an operator must be parenthesized", location=create_loc(1, 0, 1, 9)
    )
    assert actual == expected


def test_sum__chain():
    actual = parse_expr('a + b - c', rn.SUM, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 9),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 5),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='+',
            right=terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='-',
        right=terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_sum__single():
    actual = parse_expr('a', rn.SUM, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_term__chain():
    actual = parse_expr('a * b / c', rn.TERM, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 9),
        left=terms.BinOp(
            location=create_loc(1, 0, 1, 5),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op='*',
            right=terms.TypedName(location=create_loc(1, 4, 1, 5), id='b', ctx='load', mode=terms.MODE_EVALUATE),
        ),
        op='/',
        right=terms.TypedName(location=create_loc(1, 8, 1, 9), id='c', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_term__operators():
    operators = [
        '*',
        '/',
        '//',
        '%',
        '@',
    ]
    for op in operators:
        expr = f'a {op} b'
        actual = parse_expr(expr, rn.TERM, mode=terms.MODE_EVALUATE)
        expected = terms.BinOp(
            location=create_loc(1, 0, 1, 4 + len(op)),
            left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            op=op,
            right=terms.TypedName(
                location=create_loc(1, 3 + len(op), 1, 4 + len(op)),
                id='b',
                ctx='load',
                mode=terms.MODE_EVALUATE,
            ),
        )
        assert actual == expected, f'Failed for operator: {expr}'


def test_term__single():
    actual = parse_expr('a', rn.TERM, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_factor__invalid_factor():
    actual = parse_expr('+ not b', rn.FACTOR, mode=terms.MODE_EVALUATE)
    expected = syntax.ErrorTerm(
        message="'not' after an operator must be parenthesized", location=create_loc(1, 0, 1, 7)
    )
    assert actual == expected


def test_factor__operators():
    operators = [
        '+',
        '-',
        '~',
    ]
    for op in operators:
        expr = f'{op}b'
        actual = parse_expr(expr, rn.FACTOR, mode=terms.MODE_EVALUATE)
        expected = terms.UnaryOp(
            location=create_loc(1, 0, 1, 2 + len(op) - 1),
            op=op,
            operand=terms.TypedName(
                location=create_loc(1, 1 + len(op) - 1, 1, 2 + len(op) - 1),
                id='b',
                ctx='load',
                mode=terms.MODE_EVALUATE,
            ),
        )
        assert actual == expected, f'Failed for operator: {expr}'


def test_power__binary():
    actual = parse_expr('a ** b', rn.POWER, mode=terms.MODE_EVALUATE)
    expected = terms.BinOp(
        location=create_loc(1, 0, 1, 6),
        left=terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
        op='**',
        right=terms.TypedName(location=create_loc(1, 5, 1, 6), id='b', ctx='load', mode=terms.MODE_EVALUATE),
    )
    assert actual == expected


def test_power__single():
    actual = parse_expr('a', rn.POWER, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_primary__attribute():
    actual = parse_expr('obj.attr', rn.PRIMARY, mode=terms.MODE_EVALUATE)
    expected = terms.Attribute(
        location=create_loc(1, 0, 1, 8),
        value=terms.TypedName(location=create_loc(1, 0, 1, 3), id='obj', ctx='load', mode=terms.MODE_EVALUATE),
        attr='attr',
        ctx='load',
    )
    assert actual == expected


def test_primary__call():
    actual = parse_expr('func(arg1, arg2)', rn.PRIMARY, mode=terms.MODE_EVALUATE)
    expected = terms.Call(
        location=create_loc(1, 0, 1, 16),
        func=terms.TypedName(location=create_loc(1, 0, 1, 4), id='func', ctx='load', mode=terms.MODE_EVALUATE),
        args=[
            terms.TypedName(location=create_loc(1, 5, 1, 9), id='arg1', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 11, 1, 15), id='arg2', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        keywords=[],
    )
    assert actual == expected


def test_primary__slice():
    actual = parse_expr('arr[10]', rn.PRIMARY, mode=terms.MODE_EVALUATE)
    expected = terms.Subscript(
        location=create_loc(1, 0, 1, 7),
        value=terms.TypedName(location=create_loc(1, 0, 1, 3), id='arr', ctx='load', mode=terms.MODE_EVALUATE),
        slice=terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 4, 1, 6)),
        ctx='load',
    )
    assert actual == expected


def test_primary__atom():
    actual = parse_expr('variable', rn.PRIMARY, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 8), id='variable', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_star_expressions__single():
    actual = parse_expr('a', rn.STAR_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)
    assert actual == expected


def test_star_expressions__multiple():
    actual = parse_expr('a, b, c', rn.STAR_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = terms.Tuple(
        location=create_loc(1, 0, 1, 7),
        elements=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 3, 1, 4), id='b', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 6, 1, 7), id='c', ctx='load', mode=terms.MODE_EVALUATE),
        ],
        ctx='load',
    )
    assert actual == expected


def test_star_named_epxressions__empty():
    actual = parse_expr(' ', rn.STAR_NAMED_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = syntax.ErrorTerm(message='chunk[line:1] Not all text consumed: indices 0:0/1:0.')
    assert actual == expected


def test_star_named_expressions__single():
    actual = parse_expr('a', rn.STAR_NAMED_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)],
    )
    assert actual == expected


def test_star_named_expressions__trailing_comma():
    actual = parse_expr('a,', rn.STAR_NAMED_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE)],
    )
    assert actual == expected


def test_star_named_expressions__multiple():
    actual = parse_expr('a, b, c', rn.STAR_NAMED_EXPRESSIONS, mode=terms.MODE_EVALUATE)
    expected = syntax.TermList(
        terms=[
            terms.TypedName(location=create_loc(1, 0, 1, 1), id='a', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 3, 1, 4), id='b', ctx='load', mode=terms.MODE_EVALUATE),
            terms.TypedName(location=create_loc(1, 6, 1, 7), id='c', ctx='load', mode=terms.MODE_EVALUATE),
        ],
    )
    assert actual == expected


def test_assignment_expression__simple():
    actual = parse_expr('x := 42', rn.ASSIGNMENT_EXPRESSION, mode=terms.MODE_EVALUATE)
    expected = terms.NamedExpr(
        location=create_loc(1, 0, 1, 7),
        target=terms.TypedName(location=create_loc(1, 0, 1, 1), id='x', ctx='store', mode=terms.MODE_EVALUATE),
        value=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 5, 1, 7)),
    )
    assert actual == expected


def test_assignment_expression__expect_expression():
    actual = parse_expr('y := ', rn.ASSIGNMENT_EXPRESSION, mode=terms.MODE_EVALUATE)
    expected = syntax.ErrorTerm(message='Expected expression after ":="', location=create_loc(1, 0, 1, 4))
    assert actual == expected


def test_if_stmt__simple():
    actual = parse_expr('if x > 0:', rn.IF_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedIf(
        location=create_loc(1, 0, 1, 9),
        test=terms.Compare(
            location=create_loc(1, 2, 1, 8),
            left=terms.TypedName(location=create_loc(1, 3, 1, 4), id='x', ctx='load', mode=terms.MODE_EVALUATE),
            operators=['>'],
            comparators=[terms.IntegerLiteral(value=0, mode=terms.MODE_EVALUATE, location=create_loc(1, 7, 1, 8))],
        ),
        body=syntax.TermList(terms=[], is_placeholder=True),
        elifs=[],
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_if_stmt__named():
    actual = parse_expr('if (a := 42):', rn.IF_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedIf(
        location=create_loc(1, 0, 1, 13),
        test=terms.NamedExpr(
            location=create_loc(1, 4, 1, 11),
            target=terms.TypedName(location=create_loc(1, 4, 1, 5), id='a', ctx='store', mode=terms.MODE_EVALUATE),
            value=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 9, 1, 11)),
        ),
        body=syntax.TermList(terms=[], is_placeholder=True),
        elifs=[],
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_elif_stmt__simple():
    actual = parse_expr('elif y < 10:', rn.ELIF_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.ElifSibling(
        location=create_loc(1, 0, 1, 12),
        test=terms.Compare(
            location=create_loc(1, 4, 1, 11),
            left=terms.TypedName(location=create_loc(1, 5, 1, 6), id='y', ctx='load', mode=terms.MODE_EVALUATE),
            operators=['<'],
            comparators=[terms.IntegerLiteral(value=10, mode=terms.MODE_EVALUATE, location=create_loc(1, 9, 1, 11))],
        ),
        body=syntax.TermList(terms=[], is_placeholder=True),
    )
    assert actual == expected


def test_else_stmt__simple():
    actual = parse_expr('else:', rn.ELSE_BLOCK, mode=terms.MODE_EVALUATE)
    expected = terms.ElseSibling(
        location=create_loc(1, 0, 1, 5),
        body=syntax.TermList(terms=[], is_placeholder=True),
    )
    assert actual == expected


def test_for_stmt__simple():
    actual = parse_expr('for item in collection:', rn.FOR_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedFor(
        location=create_loc(1, 0, 1, 23),
        target=terms.TypedName(location=create_loc(1, 4, 1, 8), id='item', ctx='store', mode=terms.MODE_EVALUATE),
        iter=terms.TypedName(location=create_loc(1, 12, 1, 22), id='collection', ctx='load', mode=terms.MODE_EVALUATE),
        body=syntax.TermList(terms=[], is_placeholder=True),
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_for_stmt__tuple_target():
    actual = parse_expr('for a, b in pairs:', rn.FOR_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedFor(
        location=create_loc(1, 0, 1, 18),
        target=terms.Tuple(
            location=create_loc(1, 3, 1, 8),
            elements=[
                terms.TypedName(location=create_loc(1, 4, 1, 5), id='a', ctx='store', mode=terms.MODE_EVALUATE),
                terms.TypedName(location=create_loc(1, 7, 1, 8), id='b', ctx='store', mode=terms.MODE_EVALUATE),
            ],
            ctx='store',
        ),
        iter=terms.TypedName(location=create_loc(1, 12, 1, 17), id='pairs', ctx='load', mode=terms.MODE_EVALUATE),
        body=syntax.TermList(terms=[], is_placeholder=True),
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_while_stmt__simple():
    actual = parse_expr('while condition:', rn.WHILE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedWhile(
        location=create_loc(1, 0, 1, 16),
        test=terms.TypedName(location=create_loc(1, 6, 1, 15), id='condition', ctx='load', mode=terms.MODE_EVALUATE),
        body=syntax.TermList(terms=[], is_placeholder=True),
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_while_stmt__named():
    actual = parse_expr('while x := 42:', rn.WHILE_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.TypedWhile(
        location=create_loc(1, 0, 1, 14),
        test=terms.NamedExpr(
            location=create_loc(1, 5, 1, 13),
            target=terms.TypedName(location=create_loc(1, 6, 1, 7), id='x', ctx='store', mode=terms.MODE_EVALUATE),
            value=terms.IntegerLiteral(value=42, mode=terms.MODE_EVALUATE, location=create_loc(1, 11, 1, 13)),
        ),
        body=syntax.TermList(terms=[], is_placeholder=True),
        orelse=syntax.Empty,
        mode=terms.MODE_EVALUATE,
    )
    assert actual == expected


def test_with_stmt__no_as():
    actual = parse_expr('with resource:', rn.WITH_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.With(
        location=create_loc(1, 0, 1, 14),
        items=[
            terms.WithItem(
                context_expr=terms.TypedName(
                    location=create_loc(1, 5, 1, 13), id='resource', ctx='load', mode=terms.MODE_EVALUATE
                ),
                optional_vars=syntax.Empty,
            ),
        ],
        body=syntax.TermList(terms=[], is_placeholder=True),
    )
    assert actual == expected


def test_with_stmt__simple():
    actual = parse_expr('with resource as res:', rn.WITH_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.With(
        location=create_loc(1, 0, 1, 21),
        items=[
            terms.WithItem(
                context_expr=terms.TypedName(
                    location=create_loc(1, 5, 1, 13), id='resource', ctx='load', mode=terms.MODE_EVALUATE
                ),
                optional_vars=terms.TypedName(
                    location=create_loc(1, 17, 1, 20), id='res', ctx='store', mode=terms.MODE_EVALUATE
                ),
            ),
        ],
        body=syntax.TermList(terms=[], is_placeholder=True),
    )
    assert actual == expected


def test_with_stmt__multiple_items():
    actual = parse_expr('with res1, res2 as r2:', rn.WITH_STMT, mode=terms.MODE_EVALUATE)
    expected = terms.With(
        location=create_loc(1, 0, 1, 22),
        items=[
            terms.WithItem(
                context_expr=terms.TypedName(
                    location=create_loc(1, 5, 1, 9), id='res1', ctx='load', mode=terms.MODE_EVALUATE
                ),
                optional_vars=syntax.Empty,
            ),
            terms.WithItem(
                context_expr=terms.TypedName(
                    location=create_loc(1, 11, 1, 15), id='res2', ctx='load', mode=terms.MODE_EVALUATE
                ),
                optional_vars=terms.TypedName(
                    location=create_loc(1, 19, 1, 21), id='r2', ctx='store', mode=terms.MODE_EVALUATE
                ),
            ),
        ],
        body=syntax.TermList(terms=[], is_placeholder=True),
    )
    assert actual == expected


def test_expression__double_layer():
    actual = parse_expr('<3:A>', rn.EXPRESSION)
    expected = syntax.Layers(
        layers=[
            terms.IntegerLiteral(value=3, mode=terms.MODE_EVALUATE, location=create_loc(1, 1, 1, 2)),
            terms.TypedName(id='A', ctx='load', mode=terms.MODE_TYPECHECK, location=create_loc(1, 3, 1, 4)),
        ],
    )
    assert actual == expected


def test_expression__name_bang():
    actual = parse_expr('Dog!', rn.EXPRESSION, mode=terms.MODE_EVALUATE)
    expected = terms.Attribute(
        value=terms.TypedName(id='Dog', ctx='load', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 3)),
        attr='result__sa',
        ctx='load',
        location=create_loc(1, 0, 1, 4),
    )
    assert actual == expected


def test_expression__bang():
    actual = parse_expr('Matrix(3,5)!', rn.EXPRESSION, mode=terms.MODE_EVALUATE)
    expected = terms.Attribute(
        value=terms.Call(
            func=terms.TypedName(id='Matrix', ctx='load', mode=terms.MODE_EVALUATE, location=create_loc(1, 0, 1, 6)),
            args=[
                terms.IntegerLiteral(value=3, mode=terms.MODE_EVALUATE, location=create_loc(1, 7, 1, 8)),
                terms.IntegerLiteral(value=5, mode=terms.MODE_EVALUATE, location=create_loc(1, 9, 1, 10)),
            ],
            keywords=[],
            location=create_loc(1, 0, 1, 11),
        ),
        attr='result__sa',
        ctx='load',
        location=create_loc(1, 0, 1, 12),
    )
    assert actual == expected
