#------------------------------------------------------------------------------
# setlx2py: test_parser.py
#
# Unit tests for the Parser class in setlx_parser.py
# Beware: Only the syntax is tested here!
#         Some of the code does not make semantically sense at all!
#
# Copyright (C) 2013, Jan-Christoph Klie
# License: Apache v2
#------------------------------------------------------------------------------

from nose.tools import eq_, with_setup, nottest

from setlx2py.setlx_lexer import Lexer
from setlx2py.setlx_parser import Parser
from setlx2py.setlx_ast import *

##
## Test housekeeping
##

parser = Parser(yacc_optimize=False)

##
## Parse helper
##

def parse_statements(text):    
    root = parser.parse(text) # FileAST
    return root # List of statements

def parse_single_statement(text):
    return parse_statements(text).stmts[0] # first statement after FileAST

##
## Custom asserts
##

def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(),
        ('UnaryOp', operator, ('Constant', typ, val)))

def assert_binop_from_node(node, operator, left, right):    
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_( node.right.value, right)
    
def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)

def assert_assignment(text, operator, left, right):
    """ Asserts that a given assignment has the
    expected operator, left- and ride hand side.

    Args:
        text: The string containing an assignment
        operator: The expected assignment operator 
        left: The expected left hand side variable
        right: The expected right hand side variable
    Raises:
        AssertionError: if result != expected
    """
    try:
        node = parse_single_statement(text)
        eq_(node.op, operator)
        eq_(node.target.name, left)
        eq_( node.right.value, right)
    except AssertionError as e:
        node.show()
        raise e

def assert_assignment_explicit(text, operator, left, right):
    node = parse_single_statement(text)
    eq_(node.op, operator)
    for var, l in zip(node.left.assignments, left):
        eq_(var.name, l)
    eq_(node.right.value, right)

#    _______       _
#   |__   __|     | |
#      | | ___ ___| |_ ___
#      | |/ _ / __| __/ __|
#      | |  __\__ | |_\__ \
#      |_|\___|___/\__|___/
                                       
def test_should_be_creatable():
    assert parser is not None
    
##
## Atomic Values
##    

def test_atomic_value_int():
    node = parse_single_statement('42;')
    eq_(node.to_tuples(), ('Constant', 'int', 42))

def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))

def test_atomic_value_true():
    node = parse_single_statement('true;')
    eq_(node.to_tuples(), ('Constant', 'bool', True))

def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))

def test_atomic_value_interpolation():
    node = parse_single_statement('"n := $n$";')
    eq_(node.to_tuples(),
        ('Interpolation',
         ('Constant', 'literal', 'n := $n$'),
         ('ExprList', )))
    
##
## Collections
##

# List

def test_list_minimal():
    node = parse_single_statement('[];')

def test_list_single_element():
    node = parse_single_statement('[foo];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo')))

def test_list_two_elements():
    node = parse_single_statement('[foo,bar];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

def test_list_three_elements():
    node = parse_single_statement('[foo,bar,baz];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo'),
         ('Identifier', 'bar'),
         ('Identifier', 'baz')))

def test_list_head_tail():
    node = parse_single_statement('[h|t];')
    eq_(node.to_tuples(),
        ('Pattern',
         ('Identifier', 'h'),
         ('Identifier', 't')))

def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(),
        ('Pattern',
         ('ExprList',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))    

# Set

def test_set_minimal():
    node = parse_single_statement('{};')

def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo')))

def test_set_two_elements():
    node = parse_single_statement('{foo,bar};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

def test_set_three_elements():
    node = parse_single_statement('{foo,bar,baz};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo'),
         ('Identifier', 'bar'),
         ('Identifier', 'baz')))

def test_set_head_tail():
    node = parse_single_statement('{h|t};')
    eq_(node.to_tuples(),
        ('Pattern',
         ('Identifier', 'h'),
         ('Identifier', 't')))

def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(),
        ('Pattern',
         ('ExprList',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))    
    
##
## Ranges
##

# List

def test_atomic_value_list_ab():
    node = parse_single_statement('[1 .. 10];')
    eq_(node.to_tuples(),
        ('Range', 'list',
         ('Constant', 'int', 1),
         ('Constant', 'int', 10)))

def test_atomic_value_list_abc():
    node = parse_single_statement('[a,b..c];')
    eq_(node.to_tuples(),
        ('Range', 'list',
         ('Identifier', 'a'),
         ('Identifier', 'b'),
         ('Identifier', 'c')))    
    
# Set

def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(),
        ('Range', 'set',
         ('Identifier', 'a'),
         ('Identifier', 'b')))

def test_atomic_value_set_abc():
    node = parse_single_statement('{a,b..c};')
    eq_(node.to_tuples(),
        ('Range', 'set',
         ('Identifier', 'a'),
         ('Identifier', 'b'),
         ('Identifier', 'c')))

##
## Comprehension
##

# Set

def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Identifier', 'x'))))

def test_set_comprehension_two_iterators():
    node = parse_single_statement('{a * b: a in {1..3}, b in {1..3}};')
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('BinaryOp', '*',
          ('Identifier', 'a'),
          ('Identifier', 'b')),
         ('IteratorChain', '',
          ('Iterator',
           ('Identifier', 'a'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))),
          ('Iterator',
           ('Identifier', 'b'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))))))

def test_set_comprehension_prime():
    s = """{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };"""
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Range', 'set',
           ('Constant', 'int', 2),
           ('Constant', 'int', 100))),
         ('BinaryOp', '==',
          ('Comprehension', 'set',
           ('Identifier', 'x'),
           ('Iterator',
            ('Identifier','x'),
            ('Range', 'set',
             ('Constant', 'int', 1),
             ('Identifier', 'p'))),
           ('BinaryOp', '==',
            ('BinaryOp', '%',
             ('Identifier', 'p'),
             ('Identifier', 'x')),
            ('Constant', 'int', 0))),
          ('Set',
           ('Constant', 'int', 1),
           ('Identifier', 'p')))))

# List

def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Identifier', 'x'))))

def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('BinaryOp', '*',
          ('Identifier', 'a'),
          ('Identifier', 'b')),
         ('IteratorChain', '',
          ('Iterator',
           ('Identifier', 'a'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))),
          ('Iterator',
           ('Identifier', 'b'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))))))

def test_list_comprehension_prime():
    s = """[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];"""
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Range', 'set',
           ('Constant', 'int', 2),
           ('Constant', 'int', 100))),
         ('BinaryOp', '==',
          ('Comprehension', 'list',
           ('Identifier', 'x'),
           ('Iterator',
            ('Identifier','x'),
            ('Range', 'set',
             ('Constant', 'int', 1),
             ('Identifier', 'p'))),
           ('BinaryOp', '==',
            ('BinaryOp', '%',
             ('Identifier', 'p'),
             ('Identifier', 'x')),
            ('Constant', 'int', 0))),
          ('Set',
           ('Constant', 'int', 1),
           ('Identifier', 'p')))))
    
##
## Variables
##

def test_variables():
    node = parse_single_statement('foo;')
    eq_(node.to_tuples(),
        ('Identifier', 'foo'))

##    
## Binary Operations
##

def test_binop_boolean():
    assert_binop('true <==> true;', '<==>', True, True)
    assert_binop('true <!=> true;', '<!=>', True, True)    
    assert_binop('true => true;',     '=>', True, True)    
    assert_binop('true || false;',    '||', True, False)
    assert_binop('true && false;',    '&&', True, False)
    assert_binop('"foo" == \'bar\';', '==', "foo", "bar")
    assert_binop('true != false;',    '!=', True, False)
    assert_binop('1 <  2;',            '<', 1, 2)
    assert_binop('true <= false;',    '<=', True, False)
    assert_binop('true >  false;',     '>', True, False)
    assert_binop('true >= false;',    '>=', True, False)    

def test_binop_contain():
    assert_binop('true    in false;',    'in', True, False)
    assert_binop('true notin false;', 'notin', True, False)

def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)

def test_binop_product():
    assert_binop('42 *  43;',  '*', 42, 43)
    assert_binop('42 /  43;',  '/', 42, 43)
    assert_binop('42 \  43;', '\\', 42, 43)
    assert_binop('42 %  43;',  '%', 42, 43)
    assert_binop('42 >< 43;', '><', 42, 43)
    assert_binop('42 ** 43;', '**', 42, 43)

def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)

def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(),
        ('BinaryOp', '+',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

##    
## Precedence
##

def test_three_operands_precedence_and_or():
    node = parse_single_statement('true && false || true;')
    eq_(node.to_tuples(),
        ('BinaryOp', '||',
         ('BinaryOp', '&&',
          ('Constant', 'bool', True),
          ('Constant', 'bool', False)),
         ('Constant', 'bool', True)))

def test_three_operands_precedence_minus_divide():
    node = parse_single_statement('8 - 4 / 2.0;')
    eq_(node.to_tuples(),
        ('BinaryOp', '-',
         ('Constant', 'int', 8),
         ('BinaryOp', '/',
          ('Constant', 'int', 4 ),
          ('Constant', 'double', 2.0 ))))

def test_precendce_arithmetic():
    node = parse_single_statement('a + b  * c / ( d - e );')
    eq_(node.to_tuples(),
        ('BinaryOp', '+',
         ('Identifier', 'a'),
         ('BinaryOp', '/',
          ('BinaryOp', '*',
           ('Identifier', 'b'),
           ('Identifier', 'c')),
          ('BinaryOp', '-',
           ('Identifier', 'd'),
           ('Identifier', 'e')))))    

##
## Unary operations
##
    
def test_unop():
    assert_unop('+/ 42;',  '+/', 'int', 42)
    assert_unop('*/ 42;',  '*/', 'int', 42)
    assert_unop('-  42;',   '-', 'int', 42)
    assert_unop('#  42;',   '#', 'int', 42)
    assert_unop('@  42;',   '@', 'int', 42)
    assert_unop('!true;', 'not', 'bool', True)
    assert_unop('1337!;', 'fac', 'int', 1337)

####    
##
## Simple Statements
##
####
    
def test_more_than_one_statement_simple():
    stmts = parse_statements('1 + 2; 3 * 4;')
    eq_(stmts.to_tuples(),
        ('FileAST',
         ('BinaryOp', '+',
          ('Constant', 'int', 1),
          ('Constant', 'int', 2)),
         ('BinaryOp', '*',
          ('Constant', 'int', 3),
          ('Constant', 'int', 4))))

##
## Attributeref
##

def test_attributeref_minimal():
    node = parse_single_statement('x.y;')
    eq_(node.to_tuples(),
        ('AttributeRef',
         ('Identifier', 'x'),
         ('Identifier', 'y')))

def test_attributeref_chained():
    node = parse_single_statement('x.y.z;')
    eq_(node.to_tuples(),
        ('AttributeRef',
         ('AttributeRef',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))

##
## Subscription
##

def test_subscription_minimal():
    node = parse_single_statement('x[y];')
    eq_(node.to_tuples(),
        ('Subscription',
         ('Identifier', 'x'),
         ('Identifier', 'y')))

def test_subscription_chained():
    node = parse_single_statement('x[y][0];')
    eq_(node.to_tuples(),
        ('Subscription',
         ('Subscription',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Constant', 'int', 0)))    
        
##
## Call
##

def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList', )))

def test_call_one_argument():
    node = parse_single_statement('foo(bar);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList',
          ('Identifier', 'bar'))))

def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList',
          ('Identifier', 'bar'),
          ('Identifier', 'baz'))))

def test_call_two_expressions():
    node = parse_single_statement('substring(size-1,size+3);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'substring'),
         ('ArgumentList',
          ('BinaryOp', '-', ('Identifier', 'size'), ('Constant', 'int', 1)),
          ('BinaryOp', '+', ('Identifier', 'size'), ('Constant', 'int', 3)))))

def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(),
        ('Call',
         ('Call',
          ('Identifier', 'getCallback'),
          ('ArgumentList',
           ('Constant', 'string', 'onExit'))),
         ('ArgumentList', )))

##
## Slicing
##

def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'b')))

def test_slicing_right():
    node = parse_single_statement('list[a..];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'a')))
    
def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'a'),
         ('Identifier', 'b'))) 
##
## Lambda
##

def test_lambda_minimal_zero_params():
    node = parse_single_statement('[ ] |-> true;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ),
         ('Constant', 'bool', True)))

def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'foo')),
         ('Constant', 'literal', 'bar')))

def test_lambda_minimal_one_param_brackets():
    node = parse_single_statement('x |-> x ** x;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'x')),
         ('BinaryOp', '**',
          ('Identifier', 'x'),
          ('Identifier', 'x'))))

def test_lambda_two_params():
    node = parse_single_statement('[x,y] |-> x+y;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'x'), ('Identifier', 'y')),
         ('BinaryOp', '+',
          ('Identifier', 'x'),
          ('Identifier', 'y'))))
    
##
## Assignment
##

def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'foo'),
         ('Constant', 'int', 42)))    
    
def test_assignment_underscore():
    node = parse_single_statement('_ := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', '_'),
         ('Constant', 'bool', True)))    

def test_assignment_explicit_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'foo'),
         ('Constant', 'int', 42)))

def test_assignment_explicit_two():
    node = parse_single_statement('[foo, bar] := "xy";')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('List',
          ('Identifier', 'foo'),
          ('Identifier', 'bar')),
         ('Constant', 'string', 'xy')))
    
def test_assignment_attributeref():
    assignment = parse_single_statement('foo.bar := true;')
    eq_(assignment.to_tuples(),
        ('Assignment', ':=',
        ('AttributeRef',
          ('Identifier', 'foo'),
          ('Identifier', 'bar')),
         ('Constant', 'bool', True)))

def test_assignment_member_access_chained():
    assignment = parse_single_statement('foo.bar.baz := 42;')
    eq_(assignment.to_tuples(),
        ('Assignment', ':=',
         ('AttributeRef',
          ('AttributeRef',
           ('Identifier', 'foo'),
           ('Identifier', 'bar')),
          ('Identifier', 'baz')),
         ('Constant', 'int', 42)))

def test_assignment_subscription():
    node = parse_single_statement('foo[0] := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Identifier', 'foo'),
          ('Constant', 'int', 0)),
         ('Constant', 'bool', True)))

def test_assignment_subscription_chained():
    node = parse_single_statement('foo[0][1] := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Subscription',
           ('Identifier', 'foo'),
           ('Constant', 'int', 0)),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))

def test_assignment_mixed_attributeref_subscription():
    node = parse_single_statement('foo[x+1].bar.baz[42][y**2] := z;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Subscription',
           ('AttributeRef',
            ('AttributeRef',
             ('Subscription',
              ('Identifier', 'foo'),
              ('BinaryOp', '+', ('Identifier', 'x'), ('Constant', 'int', 1))),
             ('Identifier', 'bar')),
            ('Identifier', 'baz')),
           ('Constant', 'int', 42)),
          ('BinaryOp', '**', ('Identifier', 'y'), ('Constant', 'int', 2))),
         ('Identifier', 'z')))

##
## Augmented Assignment
##

def test_augmented_assignment():
    assert_assignment('foo += 42;', '+=',  'foo', 42)
    assert_assignment('foo -= 1;',  '-=',  'foo', 1)
    assert_assignment('foo *= 2;',  '*=',  'foo', 2)
    assert_assignment('foo /= 2;',  '/=',  'foo', 2)
    assert_assignment('foo \\= 3;', '\\=', 'foo', 3)
    assert_assignment('foo %= 10;', '%=',  'foo', 10)

##    
## Assert
##

def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(),
        ('Assert',
         ('Identifier', 'isOverflown'),
         ('Constant', 'bool', False)))

##
## Jump statements
##

def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(),
        ('Backtrack',))

def test_break_statement():
    node = parse_single_statement('break;')
    eq_(node.to_tuples(),
        ('Break',))

def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(),
        ('Continue',))

def test_break_statement():
    node = parse_single_statement('exit;')
    eq_(node.to_tuples(),
        ('Exit',))

def test_return_statement_void():
    node = parse_single_statement('return;')
    eq_(node.to_tuples(),
        ('Return',))

def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(),
        ('Return',
         ('Constant', 'int', 42)))

def test_return_statement_binop():
    node = parse_single_statement('return 42 ** 2;')
    eq_(node.to_tuples(),
        ('Return',
         ('BinaryOp', '**',
          ('Constant', 'int', 42),
          ('Constant', 'int', 2))))

##    
## Terms
##

def test_term_no_arguments():
    node = parse_single_statement('F();')
    eq_(node.to_tuples(),
        ('Term', 'F', ('ArgumentList',)))

def test_term_single_arg():
    node = parse_single_statement('F(true);')
    eq_(node.to_tuples(),
        ('Term', 'F',
         (('ArgumentList'),
          ('Constant', 'bool', True))))

def test_term_multi_arg():
    node = parse_single_statement('F(true, false);')
    eq_(node.to_tuples(),
        ('Term', 'F',
         ('ArgumentList', 
          ('Constant', 'bool', True),
          ('Constant', 'bool', False))))

##
## Quantifier
##

def test_quantifier_all():
    node =  parse_single_statement('forall (x in 1 | true);')
    eq_(node.to_tuples(),
        ('Quantor', 'all',
         ('Iterator',
          ('Identifier', 'x'),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))
    

def test_quantifier_exists():
    node =  parse_single_statement('exists (x in 1 | true);')
    eq_(node.to_tuples(),
        ('Quantor', 'any',
         ('Iterator',
          ('Identifier', 'x'),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))

def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(),
        ('Quantor', 'all',
         ('Iterator',
          ('Identifier', 'n'),
          ('Range', 'list',
           ('Constant', 'int', 1),
           ('Constant', 'int', 10))),
         ('BinaryOp', '<=',
          ('BinaryOp', '**',
           ('Identifier', 'n'),
           ('Constant', 'int', 2)),
          ('BinaryOp', '**',
           ('Constant', 'int', 2),
           ('Identifier', 'n')))))
    
####
##
## Compound Statements
##
####
    
##
## If statements
##

def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(),
        ('If',
         ('Constant', 'bool', True),
         ('Block', )))

def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '>=',
          ('Identifier', 'x'),
          ('Constant', 'int', 5)),
         ('Block',
          ('Return', ('Constant', 'bool', True)))))

def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '&&',
          ('Identifier', 'isRaining'),
          ('Identifier', 'isCold')),
         ('Block',
          ('Assignment', ':=',
           ('Identifier', 'prediction'),
           ('Constant', 'string', 'Let it snow!')),
          ('Return', ('Identifier', 'prediction')))))

# If-Else

def test_if_else_minimal():
    node = parse_single_statement("if(foo) {} else {}")
    eq_(node.to_tuples(),
        ('If',
         ('Identifier', 'foo'),
         ('Block',),
         ('Block',)))
    

def test_if_single_else():
    s = """
    if(i % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '==',
          ('BinaryOp', '%',
           ('Identifier', 'i'),
           ('Constant', 'int', 2)),
          ('Constant', 'int', 0)),
         ('Block', ('Return', ('Constant', 'string', 'Even'))),
         ('Block', ('Return', ('Constant', 'string', 'Odd')))))

def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('Identifier', 'isGreen'),
         ('Block',
          ('Return', ('Constant', 'string', 'Green'))),
         ('If',
          ('Identifier', 'isRed'),
          ('Block',
           ('Return', ('Constant', 'string', 'Red'))),
          ('Block',
           ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), 
         ('If', ('BinaryOp', '==',
                 ('Identifier', 'grade'),
                 ('Constant', 'string', 'A')),
          ('Block', ('Return', ('Constant', 'string', 'Excellent'))),
          ('If', ('BinaryOp', '==',
                  ('Identifier', 'grade'),
                  ('Constant', 'string', 'B')),
           ('Block', ('Return', ('Constant', 'string', 'Good'))),
           ('If', ('BinaryOp', '==',
                   ('Identifier', 'grade'),
                   ('Constant', 'string', 'C')),
            ('Block', ('Return', ('Constant', 'string', 'Satisfactory'))),
            ('If', ('BinaryOp', '==',
                    ('Identifier', 'grade'),
                    ('Constant', 'string', 'D')),
             ('Block', ('Return', ('Constant', 'string', 'Pass'))),
             ('If', ('BinaryOp', '==',
                     ('Identifier', 'grade'),
                     ('Constant', 'string', 'F')),
              ('Block', ('Return', ('Constant', 'string', 'Fail'))),
              ('Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


def test_if_nested_else():
    s = """
    if(num1 == num2) {
        return "Equal";
    } else {
        if(num1 > num2) { 
            return "Num1 greater";
        } else {
            return "Num2 greater";
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '==', ('Identifier', 'num1'), ('Identifier', 'num2')), 
         ('Block', ('Return', ('Constant', 'string', 'Equal'))),  
         ('Block',
          ('If',
           ('BinaryOp', '>', ('Identifier', 'num1'), ('Identifier', 'num2')),
           ('Block', ('Return', ('Constant', 'string', 'Num1 greater'))),
           ('Block', ('Return', ('Constant', 'string', 'Num2 greater')))))))

##
## Switch/Case
##

def test_switch_case_minimal():
    node = parse_single_statement('switch {}')
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList', )))

def test_switch_case_simple_no_default():
    s = """
    // Check whether an integer n is even
    switch {
        case n % 2 == 0 : return 'Even';
        case n % 2 == 1 : return 'Odd';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList',
          ('Case',
           ('BinaryOp', '==',
            ('BinaryOp', '%', ('Identifier', 'n'), ('Constant', 'int', 2)),
            ('Constant', 'int', 0)),
           ('Block', ('Return', ('Constant', 'literal', 'Even')))),
          ('Case',
           ('BinaryOp', '==',
            ('BinaryOp', '%', ('Identifier', 'n'), ('Constant', 'int', 2)),
            ('Constant', 'int', 1)),
           ('Block', ('Return', ('Constant', 'literal', 'Odd')))))))
    
def test_switch_case_with_default():
    s = """
    switch {
        case grade == 'A' : return 'Excellent';
        case grade == 'B' : return 'Good';
        case grade == 'C' : return 'Satisfactory';
        case grade == 'D' : return 'Pass';
        case grade == 'F' : return 'Fail';
        default           : return 'Invalid input';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList',
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'A')),
          ('Block', ('Return', ('Constant', 'literal', 'Excellent')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'B')),
          ('Block', ('Return', ('Constant', 'literal', 'Good')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'C')),
          ('Block', ('Return', ('Constant', 'literal', 'Satisfactory')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'D')),
          ('Block', ('Return', ('Constant', 'literal', 'Pass')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'F')),
          ('Block', ('Return', ('Constant', 'literal', 'Fail'))))),
          ('Default', ('Block', ('Return', ('Constant', 'literal', 'Invalid input'))))))

##
## Match
##


def test_match_minimal():
    s = """
    match(s) {
        case [] : return s;
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List', ),
           ('Block',
            ('Return', ('Identifier', 's')))))))

def test_match_one_pattern():
    s = """
    match(s) {
        case [h|t] : return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Identifier', 'h')))))))

def test_match_one_pattern_default():
    s = """
    match(s) {
        case [h|t] : return 'Not empty!';
        default    : return 'Empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!'))))),
         ('Default',
          ('Block',
           ('Return', ('Constant', 'literal', 'Empty!'))))))

def test_match_two_cases():
    s = """
    match (s) {
      case [] : return "Empty";
      case [h|t]: return "Not empty";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List',),
           ('Block', ('Return', ('Constant', 'string', 'Empty')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'string', 'Not empty')))))))

def test_match_two_cases_default():
    s = """
    match (s) {
      case [h] : return h;
      case [h|t]: return t;
      default : return "Error!";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List', ('Identifier', 'h')),
           ('Block', ('Return', ('Identifier', 'h')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Identifier', 't'))))),
          ('Default',
           ('Block',
            ('Return', ('Constant', 'string', 'Error!'))))))

def test_match_pattern_condition():
    s = """
    match(s) {
        case [h|t] | h != null: return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('BinaryOp', '!=',
            ('Identifier', 'h'),
            ('Identifier', 'null')),
           ('Block',
            ('Return', ('Identifier', 'h')))))))
    
# Regex

def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

def test_match_one_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_match_regex_condition():
    s = """
    match(s) {
        regex '\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', '\w+'),
           ('As', ('Identifier', 'x')),
           ('BinaryOp', '>=',
            ('UnaryOp', '#', ('Identifier', 'x')),
            ('Constant', 'int', 42)),
           ('Block',
            ('Return', ('Identifier', 'x')))))))

# Case + Regex

def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))),
          ('Case',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!')))))))

def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!')))))))
    
##
## Scan
##

def test_scan_one_regex():
    s = """
    scan(s) {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

def test_scan_one_regex_using():
    s = """
    scan(x) using y {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 'x'),
         ('As', ('Identifier', 'y')),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

    
def test_match_one_regex_as():
    s = """
    scan(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_scan_two_regex():
    s = """
    scan(s) {
        regex 'foo' : return "bar";
        regex 'bar' : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))),
          ('Regex',
           ('Constant', 'literal', 'bar'),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_scan_two_regex_using_default():
    s = """
    scan(c) using chr {
        regex '[0-9]' : return "Number";
        regex '[a-zA-Z]' : return "Character";
        default: return "Neither number nor character";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 'c'),
         ('As', ('Identifier','chr')),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', '[0-9]'),
           ('Block',
            ('Return', ('Constant', 'string', 'Number')))),
          ('Regex',
           ('Constant', 'literal', '[a-zA-Z]'),
           ('Block',
            ('Return', ('Constant', 'string', 'Character'))))),
          ('Default',
           ('Block',
            ('Return', ('Constant', 'string', 'Neither number nor character'))))))
    
##
## Try/Catch
##    
    
##
## While-Loop
##

def test_while_minimal():
    node = parse_single_statement("while(!empty) {}")
    eq_(node.to_tuples(),
        ('While',
         ('UnaryOp', 'not', ('Identifier', 'empty')),
         ('Block',)))

def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(),
        ('While',
         ('BinaryOp', '<', ('Identifier', 'i'), ('Identifier', 'n')),
         ('Block',
          ('Assignment', '*=', ('Identifier', 'x'), ('Constant', 'int', 2)),
          ('Assignment', '-=', ('Identifier', 'i'), ('Constant', 'int', 1)))))
##
## Do-While-Loop
##

def test_do_while_loop_minimal():
    node = parse_single_statement("do {} while(!empty); ")
    eq_(node.to_tuples(),
        ('DoWhile',
         ('UnaryOp', 'not', ('Identifier', 'empty')),
         ('Block',)))

def test_do_while_loop_bigger_body():
    node = parse_single_statement('do { x *= 2; i -= 1;} while(i < n);')
    eq_(node.to_tuples(),
        ('DoWhile',
         ('BinaryOp', '<', ('Identifier', 'i'), ('Identifier', 'n')),
         ('Block',
          ('Assignment', '*=', ('Identifier', 'x'), ('Constant', 'int', 2)),
          ('Assignment', '-=', ('Identifier', 'i'), ('Constant', 'int', 1)))))

##
## For-Loop
##

def test_for_loop_minimal():
    node = parse_single_statement("for(x in primes) {}")
    eq_(node.to_tuples(),
        ('For',
         ('Iterator',
          ('Identifier', 'x'),
          ('Identifier', 'primes')),
         ('Block', )))

def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('For',
         ('Iterator',
          ('Identifier', 'c'),
          ('Identifier', 'characters')),
         ('Block',
          ('If',
           ('BinaryOp', 'in',
            ('Identifier', 'c'),
            ('Identifier', 'vowels')),
           ('Block', ('Return', ('Identifier', 'c')))))))

def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('For',
         ('IteratorChain', '',
          ('Iterator', ('Identifier', 'i'), ('Identifier', 'uids')),
          ('Iterator', ('Identifier', 's'), ('Identifier', 'street')),
          ('Iterator', ('Identifier', 'n'), ('Identifier', 'street_number'))),
         ('Block',
          ('Assignment', ':=',
           ('Subscription',
            ('Identifier', 'address'),
            ('Identifier', 'i')),
           ('Interpolation',
            ('Constant', 'literal', '$street$ $street_number$'), ('ExprList',))))))
        

##
## Procedures
##

def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), ('Block',)))

def test_procedure_square():
    node = parse_single_statement('procedure(x) { return x ** x; };')
    eq_(node.to_tuples(),
        ('Procedure', '', 'vanilla',
         ('ParamList', ('Param', 'x')),
         ('Block', ('Return',
           ('BinaryOp', '**',
            ('Identifier', 'x'),
            ('Identifier', 'x'))))))

def test_procedure_max():
    s = """
    max := procedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'max'),
         ('Procedure', '', 'vanilla',
          ('ParamList', ('Param', 'a'), ('Param', 'b')),
          ('Block',
           ('If',
            ('BinaryOp', '>', ('Identifier', 'a'), ('Identifier', 'b')),
            ('Block', ('Return', ('Identifier', 'a'))),
            ('Block', ('Return', ('Identifier', 'b'))))))))

def test_procedure_fac():
    s = """
    fac := procedure(n) {
        i := 1;
        while(n >= 1) {
            i *= n;
            n -= 1;
        }
        return i;
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'fac'),
         ('Procedure', '', 'vanilla',
          ('ParamList', ('Param', 'n')),
          ('Block',
           ('Assignment', ':=', ('Identifier', 'i'), ('Constant', 'int', 1)),
           ('While',
            ('BinaryOp', '>=', ('Identifier', 'n'), ('Constant', 'int', 1)),
            ('Block',
             ('Assignment', '*=', ('Identifier', 'i'), ('Identifier', 'n')),
             ('Assignment', '-=', ('Identifier', 'n'), ('Constant', 'int', 1)))),
            ('Return', ('Identifier', 'i'))))))

##
## Cached procedures
##

def test_cached_procedure_minimal():
    node = parse_single_statement('cachedProcedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList',), ('Block',)))

def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(),
        ('Procedure', '', 'cached',
         ('ParamList', ('Param', 'x')),
         ('Block', ('Return',
           ('BinaryOp', '**',
            ('Identifier', 'x'),
            ('Identifier', 'x'))))))

def test_cached_procedure_max():
    s = """
    max := cachedProcedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'max'),
         ('Procedure', '', 'cached',
          ('ParamList', ('Param', 'a'), ('Param', 'b')),
          ('Block',
           ('If',
            ('BinaryOp', '>', ('Identifier', 'a'), ('Identifier', 'b')),
            ('Block', ('Return', ('Identifier', 'a'))),
            ('Block', ('Return', ('Identifier', 'b'))))))))

def test_cached_procedure_fac():
    s = """
    fac := cachedProcedure(n) {
        i := 1;
        while(n >= 1) {
            i *= n;
            n -= 1;
        }
        return i;
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'fac'),
         ('Procedure', '', 'cached',
          ('ParamList', ('Param', 'n')),
          ('Block',
           ('Assignment', ':=', ('Identifier', 'i'), ('Constant', 'int', 1)),
           ('While',
            ('BinaryOp', '>=', ('Identifier', 'n'), ('Constant', 'int', 1)),
            ('Block',
             ('Assignment', '*=', ('Identifier', 'i'), ('Identifier', 'n')),
             ('Assignment', '-=', ('Identifier', 'n'), ('Constant', 'int', 1)))),
            ('Return', ('Identifier', 'i'))))))

##
##
##

def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList', ),
         ('Block', ),
         ('Block', )))

def test_class_with_parameters():
    node = parse_single_statement('class point(x,y) {}')
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList',
          ('Param', 'x'),
          ('Param', 'y')),
         ('Block', ),
         ('Block', )))

def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList',
          ('Param', 'x'),
          ('Param', 'y')),
         ('Block',
          ('Assignment', ':=',
           ('Identifier', 'mX'),
           ('Identifier', 'x')),
          ('Assignment', ':=',
           ('Identifier', 'mY'),
           ('Identifier', 'y'))),
         ('Block', )))

def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'universal'),
         ('ParamList', ),
         ('Block', ),
         ('Block', 
          ('Assignment', ':=',
           ('Identifier', 'gAnswer'),
           ('Constant', 'int', 42)))))

##
## Try/Catch
##

def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catch',
           ('Identifier', 'e'),
           ('Block', )))))

def test_try_catch_usr_minimal():
    s = """
    try {
    } catchUsr(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catchUsr',
           ('Identifier', 'e'),
           ('Block', )))))

def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catchLng',
           ('Identifier', 'e'),
           ('Block', )))))

##
## Backtrack
##

def test_check_minimal():
    node =  parse_single_statement('check {}')
    eq_(node.to_tuples(),
        ('Check',
         ('Block',)))

##
## Single expression
##

def test_check_single_expr():
    node = parser.parse('x!')
1e239aaf4fa47403df4e35d2a1cb73b1b801ad2d
<mask token>


def parse_statements(text):
    root = parser.parse(text)
    return root


<mask token>


def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(), ('UnaryOp', operator, ('Constant', typ, val)))


def assert_binop_from_node(node, operator, left, right):
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_(node.right.value, right)


def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)


<mask token>


def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))


<mask token>


def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))


<mask token>


def test_list_minimal():
    node = parse_single_statement('[];')


<mask token>


def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(), ('Set', ('Identifier', 'foo')))


<mask token>


def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b')))


<mask token>


def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


<mask token>


def test_set_comprehension_prime():
    s = '{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'set', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('BinaryOp', '*', (
        'Identifier', 'a'), ('Identifier', 'b')), ('IteratorChain', '', (
        'Iterator', ('Identifier', 'a'), ('Range', 'set', ('Constant',
        'int', 1), ('Constant', 'int', 3))), ('Iterator', ('Identifier',
        'b'), ('Range', 'set', ('Constant', 'int', 1), ('Constant', 'int', 
        3))))))


def test_list_comprehension_prime():
    s = '[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'list', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


<mask token>


def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)


<mask token>


def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)


def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(), ('BinaryOp', '+', ('Identifier', 'foo'), (
        'Identifier', 'bar')))


<mask token>


def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',)))


<mask token>


def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'), ('Identifier', 'baz'))))


<mask token>


def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(), ('Call', ('Call', ('Identifier', 'getCallback'),
        ('ArgumentList', ('Constant', 'string', 'onExit'))), ('ArgumentList',))
        )


def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'b')))


<mask token>


def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'a'), ('Identifier', 'b')))


<mask token>


def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'foo')),
        ('Constant', 'literal', 'bar')))


<mask token>


def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'foo'), (
        'Constant', 'int', 42)))


<mask token>


def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(), ('Assert', ('Identifier', 'isOverflown'),
        ('Constant', 'bool', False)))


def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(), ('Backtrack',))


<mask token>


def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(), ('Continue',))


<mask token>


def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(), ('Return', ('Constant', 'int', 42)))


<mask token>


def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(), ('Quantor', 'all', ('Iterator', ('Identifier',
        'n'), ('Range', 'list', ('Constant', 'int', 1), ('Constant', 'int',
        10))), ('BinaryOp', '<=', ('BinaryOp', '**', ('Identifier', 'n'), (
        'Constant', 'int', 2)), ('BinaryOp', '**', ('Constant', 'int', 2),
        ('Identifier', 'n')))))


def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(), ('If', ('Constant', 'bool', True), ('Block',)))


def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(), ('If', ('BinaryOp', '>=', ('Identifier', 'x'), (
        'Constant', 'int', 5)), ('Block', ('Return', ('Constant', 'bool', 
        True)))))


def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '&&', ('Identifier',
        'isRaining'), ('Identifier', 'isCold')), ('Block', ('Assignment',
        ':=', ('Identifier', 'prediction'), ('Constant', 'string',
        'Let it snow!')), ('Return', ('Identifier', 'prediction')))))


<mask token>


def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('Identifier', 'isGreen'), ('Block', (
        'Return', ('Constant', 'string', 'Green'))), ('If', ('Identifier',
        'isRed'), ('Block', ('Return', ('Constant', 'string', 'Red'))), (
        'Block', ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'grade'),
        ('Constant', 'string', 'A')), ('Block', ('Return', ('Constant',
        'string', 'Excellent'))), ('If', ('BinaryOp', '==', ('Identifier',
        'grade'), ('Constant', 'string', 'B')), ('Block', ('Return', (
        'Constant', 'string', 'Good'))), ('If', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'string', 'C')), ('Block', (
        'Return', ('Constant', 'string', 'Satisfactory'))), ('If', (
        'BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'D')), ('Block', ('Return', ('Constant', 'string', 'Pass'))), ('If',
        ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'F')), ('Block', ('Return', ('Constant', 'string', 'Fail'))), (
        'Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


<mask token>


def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('Block', ('Return', (
        'Constant', 'string', 'bar')))))))


<mask token>


def test_match_regex_condition():
    s = """
    match(s) {
        regex '\\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', '\\w+'), ('As', ('Identifier', 'x'
        )), ('BinaryOp', '>=', ('UnaryOp', '#', ('Identifier', 'x')), (
        'Constant', 'int', 42)), ('Block', ('Return', ('Identifier', 'x')))))))


<mask token>


def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))), ('MatchCase', ('Pattern', ('Identifier', 'h'), (
        'Identifier', 't')), ('Block', ('Return', ('Constant', 'literal',
        'Not empty!')))))))


<mask token>


def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(), ('While', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


<mask token>


def test_for_loop_minimal():
    node = parse_single_statement('for(x in primes) {}')
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'x'), (
        'Identifier', 'primes')), ('Block',)))


def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'c'), (
        'Identifier', 'characters')), ('Block', ('If', ('BinaryOp', 'in', (
        'Identifier', 'c'), ('Identifier', 'vowels')), ('Block', ('Return',
        ('Identifier', 'c')))))))


def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('IteratorChain', '', ('Iterator', (
        'Identifier', 'i'), ('Identifier', 'uids')), ('Iterator', (
        'Identifier', 's'), ('Identifier', 'street')), ('Iterator', (
        'Identifier', 'n'), ('Identifier', 'street_number'))), ('Block', (
        'Assignment', ':=', ('Subscription', ('Identifier', 'address'), (
        'Identifier', 'i')), ('Interpolation', ('Constant', 'literal',
        '$street$ $street_number$'), ('ExprList',))))))


def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), (
        'Block',)))


<mask token>


def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList', (
        'Param', 'x')), ('Block', ('Return', ('BinaryOp', '**', (
        'Identifier', 'x'), ('Identifier', 'x'))))))


<mask token>


def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',),
        ('Block',), ('Block',)))


<mask token>


def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',
        ('Param', 'x'), ('Param', 'y')), ('Block', ('Assignment', ':=', (
        'Identifier', 'mX'), ('Identifier', 'x')), ('Assignment', ':=', (
        'Identifier', 'mY'), ('Identifier', 'y'))), ('Block',)))


def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'universal'), (
        'ParamList',), ('Block',), ('Block', ('Assignment', ':=', (
        'Identifier', 'gAnswer'), ('Constant', 'int', 42)))))


def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catch', ('Identifier', 'e'), ('Block',)))))


<mask token>


def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchLng', ('Identifier', 'e'), ('Block',)))))


def test_check_minimal():
    node = parse_single_statement('check {}')
    eq_(node.to_tuples(), ('Check', ('Block',)))


<mask token>

<mask token>


def parse_statements(text):
    root = parser.parse(text)
    return root


<mask token>


def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(), ('UnaryOp', operator, ('Constant', typ, val)))


def assert_binop_from_node(node, operator, left, right):
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_(node.right.value, right)


def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)


def assert_assignment(text, operator, left, right):
    """ Asserts that a given assignment has the
    expected operator, left- and ride hand side.

    Args:
        text: The string containing an assignment
        operator: The expected assignment operator 
        left: The expected left hand side variable
        right: The expected right hand side variable
    Raises:
        AssertionError: if result != expected
    """
    try:
        node = parse_single_statement(text)
        eq_(node.op, operator)
        eq_(node.target.name, left)
        eq_(node.right.value, right)
    except AssertionError as e:
        node.show()
        raise e


def assert_assignment_explicit(text, operator, left, right):
    node = parse_single_statement(text)
    eq_(node.op, operator)
    for var, l in zip(node.left.assignments, left):
        eq_(var.name, l)
    eq_(node.right.value, right)


<mask token>


def test_atomic_value_int():
    node = parse_single_statement('42;')
    eq_(node.to_tuples(), ('Constant', 'int', 42))


def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))


<mask token>


def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))


def test_atomic_value_interpolation():
    node = parse_single_statement('"n := $n$";')
    eq_(node.to_tuples(), ('Interpolation', ('Constant', 'literal',
        'n := $n$'), ('ExprList',)))


def test_list_minimal():
    node = parse_single_statement('[];')


<mask token>


def test_list_three_elements():
    node = parse_single_statement('[foo,bar,baz];')
    eq_(node.to_tuples(), ('List', ('Identifier', 'foo'), ('Identifier',
        'bar'), ('Identifier', 'baz')))


<mask token>


def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(), ('Set', ('Identifier', 'foo')))


<mask token>


def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b')))


def test_atomic_value_set_abc():
    node = parse_single_statement('{a,b..c};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b'), ('Identifier', 'c')))


def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


<mask token>


def test_set_comprehension_prime():
    s = '{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'set', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('BinaryOp', '*', (
        'Identifier', 'a'), ('Identifier', 'b')), ('IteratorChain', '', (
        'Iterator', ('Identifier', 'a'), ('Range', 'set', ('Constant',
        'int', 1), ('Constant', 'int', 3))), ('Iterator', ('Identifier',
        'b'), ('Range', 'set', ('Constant', 'int', 1), ('Constant', 'int', 
        3))))))


def test_list_comprehension_prime():
    s = '[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'list', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_variables():
    node = parse_single_statement('foo;')
    eq_(node.to_tuples(), ('Identifier', 'foo'))


<mask token>


def test_binop_contain():
    assert_binop('true    in false;', 'in', True, False)
    assert_binop('true notin false;', 'notin', True, False)


def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)


<mask token>


def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)


def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(), ('BinaryOp', '+', ('Identifier', 'foo'), (
        'Identifier', 'bar')))


def test_three_operands_precedence_and_or():
    node = parse_single_statement('true && false || true;')
    eq_(node.to_tuples(), ('BinaryOp', '||', ('BinaryOp', '&&', ('Constant',
        'bool', True), ('Constant', 'bool', False)), ('Constant', 'bool', 
        True)))


<mask token>


def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',)))


<mask token>


def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'), ('Identifier', 'baz'))))


<mask token>


def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(), ('Call', ('Call', ('Identifier', 'getCallback'),
        ('ArgumentList', ('Constant', 'string', 'onExit'))), ('ArgumentList',))
        )


def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'b')))


<mask token>


def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'a'), ('Identifier', 'b')))


<mask token>


def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'foo')),
        ('Constant', 'literal', 'bar')))


def test_lambda_minimal_one_param_brackets():
    node = parse_single_statement('x |-> x ** x;')
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'x')), (
        'BinaryOp', '**', ('Identifier', 'x'), ('Identifier', 'x'))))


<mask token>


def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'foo'), (
        'Constant', 'int', 42)))


def test_assignment_underscore():
    node = parse_single_statement('_ := true;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', '_'), (
        'Constant', 'bool', True)))


<mask token>


def test_assignment_attributeref():
    assignment = parse_single_statement('foo.bar := true;')
    eq_(assignment.to_tuples(), ('Assignment', ':=', ('AttributeRef', (
        'Identifier', 'foo'), ('Identifier', 'bar')), ('Constant', 'bool', 
        True)))


<mask token>


def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(), ('Assert', ('Identifier', 'isOverflown'),
        ('Constant', 'bool', False)))


def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(), ('Backtrack',))


<mask token>


def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(), ('Continue',))


<mask token>


def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(), ('Return', ('Constant', 'int', 42)))


<mask token>


def test_term_no_arguments():
    node = parse_single_statement('F();')
    eq_(node.to_tuples(), ('Term', 'F', ('ArgumentList',)))


<mask token>


def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(), ('Quantor', 'all', ('Iterator', ('Identifier',
        'n'), ('Range', 'list', ('Constant', 'int', 1), ('Constant', 'int',
        10))), ('BinaryOp', '<=', ('BinaryOp', '**', ('Identifier', 'n'), (
        'Constant', 'int', 2)), ('BinaryOp', '**', ('Constant', 'int', 2),
        ('Identifier', 'n')))))


def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(), ('If', ('Constant', 'bool', True), ('Block',)))


def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(), ('If', ('BinaryOp', '>=', ('Identifier', 'x'), (
        'Constant', 'int', 5)), ('Block', ('Return', ('Constant', 'bool', 
        True)))))


def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '&&', ('Identifier',
        'isRaining'), ('Identifier', 'isCold')), ('Block', ('Assignment',
        ':=', ('Identifier', 'prediction'), ('Constant', 'string',
        'Let it snow!')), ('Return', ('Identifier', 'prediction')))))


def test_if_else_minimal():
    node = parse_single_statement('if(foo) {} else {}')
    eq_(node.to_tuples(), ('If', ('Identifier', 'foo'), ('Block',), ('Block',))
        )


def test_if_single_else():
    s = """
    if(i % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'i'), ('Constant', 'int', 2)), ('Constant', 'int', 0)
        ), ('Block', ('Return', ('Constant', 'string', 'Even'))), ('Block',
        ('Return', ('Constant', 'string', 'Odd')))))


def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('Identifier', 'isGreen'), ('Block', (
        'Return', ('Constant', 'string', 'Green'))), ('If', ('Identifier',
        'isRed'), ('Block', ('Return', ('Constant', 'string', 'Red'))), (
        'Block', ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'grade'),
        ('Constant', 'string', 'A')), ('Block', ('Return', ('Constant',
        'string', 'Excellent'))), ('If', ('BinaryOp', '==', ('Identifier',
        'grade'), ('Constant', 'string', 'B')), ('Block', ('Return', (
        'Constant', 'string', 'Good'))), ('If', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'string', 'C')), ('Block', (
        'Return', ('Constant', 'string', 'Satisfactory'))), ('If', (
        'BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'D')), ('Block', ('Return', ('Constant', 'string', 'Pass'))), ('If',
        ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'F')), ('Block', ('Return', ('Constant', 'string', 'Fail'))), (
        'Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


def test_if_nested_else():
    s = """
    if(num1 == num2) {
        return "Equal";
    } else {
        if(num1 > num2) { 
            return "Num1 greater";
        } else {
            return "Num2 greater";
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'num1'),
        ('Identifier', 'num2')), ('Block', ('Return', ('Constant', 'string',
        'Equal'))), ('Block', ('If', ('BinaryOp', '>', ('Identifier',
        'num1'), ('Identifier', 'num2')), ('Block', ('Return', ('Constant',
        'string', 'Num1 greater'))), ('Block', ('Return', ('Constant',
        'string', 'Num2 greater')))))))


<mask token>


def test_switch_case_with_default():
    s = """
    switch {
        case grade == 'A' : return 'Excellent';
        case grade == 'B' : return 'Good';
        case grade == 'C' : return 'Satisfactory';
        case grade == 'D' : return 'Pass';
        case grade == 'F' : return 'Fail';
        default           : return 'Invalid input';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Switch', ('CaseList', ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'A')), (
        'Block', ('Return', ('Constant', 'literal', 'Excellent')))), (
        'Case', ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant',
        'literal', 'B')), ('Block', ('Return', ('Constant', 'literal',
        'Good')))), ('Case', ('BinaryOp', '==', ('Identifier', 'grade'), (
        'Constant', 'literal', 'C')), ('Block', ('Return', ('Constant',
        'literal', 'Satisfactory')))), ('Case', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'literal', 'D')), ('Block', (
        'Return', ('Constant', 'literal', 'Pass')))), ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'F')), (
        'Block', ('Return', ('Constant', 'literal', 'Fail'))))), ('Default',
        ('Block', ('Return', ('Constant', 'literal', 'Invalid input'))))))


def test_match_minimal():
    s = """
    match(s) {
        case [] : return s;
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List',), ('Block', ('Return', ('Identifier', 's')))))))


def test_match_one_pattern():
    s = """
    match(s) {
        case [h|t] : return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_pattern_default():
    s = """
    match(s) {
        case [h|t] : return 'Not empty!';
        default    : return 'Empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Constant', 'literal', 'Not empty!'))))), (
        'Default', ('Block', ('Return', ('Constant', 'literal', 'Empty!'))))))


<mask token>


def test_match_two_cases_default():
    s = """
    match (s) {
      case [h] : return h;
      case [h|t]: return t;
      default : return "Error!";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List', ('Identifier', 'h')), ('Block', ('Return', (
        'Identifier', 'h')))), ('MatchCase', ('Pattern', ('Identifier', 'h'
        ), ('Identifier', 't')), ('Block', ('Return', ('Identifier', 't')))
        )), ('Default', ('Block', ('Return', ('Constant', 'string',
        'Error!'))))))


def test_match_pattern_condition():
    s = """
    match(s) {
        case [h|t] | h != null: return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('BinaryOp', '!=', ('Identifier', 'h'), ('Identifier', 'null')), (
        'Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('Block', ('Return', (
        'Constant', 'string', 'bar')))))))


def test_match_one_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))))))


def test_match_regex_condition():
    s = """
    match(s) {
        regex '\\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', '\\w+'), ('As', ('Identifier', 'x'
        )), ('BinaryOp', '>=', ('UnaryOp', '#', ('Identifier', 'x')), (
        'Constant', 'int', 42)), ('Block', ('Return', ('Identifier', 'x')))))))


<mask token>


def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))), ('MatchCase', ('Pattern', ('Identifier', 'h'), (
        'Identifier', 't')), ('Block', ('Return', ('Constant', 'literal',
        'Not empty!')))))))


<mask token>


def test_scan_two_regex_using_default():
    s = """
    scan(c) using chr {
        regex '[0-9]' : return "Number";
        regex '[a-zA-Z]' : return "Character";
        default: return "Neither number nor character";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Scan', ('Identifier', 'c'), ('As', (
        'Identifier', 'chr')), ('CaseList', ('Regex', ('Constant',
        'literal', '[0-9]'), ('Block', ('Return', ('Constant', 'string',
        'Number')))), ('Regex', ('Constant', 'literal', '[a-zA-Z]'), (
        'Block', ('Return', ('Constant', 'string', 'Character'))))), (
        'Default', ('Block', ('Return', ('Constant', 'string',
        'Neither number nor character'))))))


<mask token>


def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(), ('While', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


def test_do_while_loop_minimal():
    node = parse_single_statement('do {} while(!empty); ')
    eq_(node.to_tuples(), ('DoWhile', ('UnaryOp', 'not', ('Identifier',
        'empty')), ('Block',)))


<mask token>


def test_for_loop_minimal():
    node = parse_single_statement('for(x in primes) {}')
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'x'), (
        'Identifier', 'primes')), ('Block',)))


def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'c'), (
        'Identifier', 'characters')), ('Block', ('If', ('BinaryOp', 'in', (
        'Identifier', 'c'), ('Identifier', 'vowels')), ('Block', ('Return',
        ('Identifier', 'c')))))))


def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('IteratorChain', '', ('Iterator', (
        'Identifier', 'i'), ('Identifier', 'uids')), ('Iterator', (
        'Identifier', 's'), ('Identifier', 'street')), ('Iterator', (
        'Identifier', 'n'), ('Identifier', 'street_number'))), ('Block', (
        'Assignment', ':=', ('Subscription', ('Identifier', 'address'), (
        'Identifier', 'i')), ('Interpolation', ('Constant', 'literal',
        '$street$ $street_number$'), ('ExprList',))))))


def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), (
        'Block',)))


<mask token>


def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList', (
        'Param', 'x')), ('Block', ('Return', ('BinaryOp', '**', (
        'Identifier', 'x'), ('Identifier', 'x'))))))


def test_cached_procedure_max():
    s = """
    max := cachedProcedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'max'), (
        'Procedure', '', 'cached', ('ParamList', ('Param', 'a'), ('Param',
        'b')), ('Block', ('If', ('BinaryOp', '>', ('Identifier', 'a'), (
        'Identifier', 'b')), ('Block', ('Return', ('Identifier', 'a'))), (
        'Block', ('Return', ('Identifier', 'b'))))))))


<mask token>


def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',),
        ('Block',), ('Block',)))


<mask token>


def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',
        ('Param', 'x'), ('Param', 'y')), ('Block', ('Assignment', ':=', (
        'Identifier', 'mX'), ('Identifier', 'x')), ('Assignment', ':=', (
        'Identifier', 'mY'), ('Identifier', 'y'))), ('Block',)))


def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'universal'), (
        'ParamList',), ('Block',), ('Block', ('Assignment', ':=', (
        'Identifier', 'gAnswer'), ('Constant', 'int', 42)))))


def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catch', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_usr_minimal():
    s = """
    try {
    } catchUsr(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchUsr', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchLng', ('Identifier', 'e'), ('Block',)))))


def test_check_minimal():
    node = parse_single_statement('check {}')
    eq_(node.to_tuples(), ('Check', ('Block',)))


def test_check_single_expr():
    node = parser.parse('x!')

<mask token>


def parse_statements(text):
    root = parser.parse(text)
    return root


<mask token>


def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(), ('UnaryOp', operator, ('Constant', typ, val)))


def assert_binop_from_node(node, operator, left, right):
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_(node.right.value, right)


def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)


def assert_assignment(text, operator, left, right):
    """ Asserts that a given assignment has the
    expected operator, left- and ride hand side.

    Args:
        text: The string containing an assignment
        operator: The expected assignment operator 
        left: The expected left hand side variable
        right: The expected right hand side variable
    Raises:
        AssertionError: if result != expected
    """
    try:
        node = parse_single_statement(text)
        eq_(node.op, operator)
        eq_(node.target.name, left)
        eq_(node.right.value, right)
    except AssertionError as e:
        node.show()
        raise e


def assert_assignment_explicit(text, operator, left, right):
    node = parse_single_statement(text)
    eq_(node.op, operator)
    for var, l in zip(node.left.assignments, left):
        eq_(var.name, l)
    eq_(node.right.value, right)


<mask token>


def test_atomic_value_int():
    node = parse_single_statement('42;')
    eq_(node.to_tuples(), ('Constant', 'int', 42))


def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))


<mask token>


def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))


def test_atomic_value_interpolation():
    node = parse_single_statement('"n := $n$";')
    eq_(node.to_tuples(), ('Interpolation', ('Constant', 'literal',
        'n := $n$'), ('ExprList',)))


def test_list_minimal():
    node = parse_single_statement('[];')


<mask token>


def test_list_three_elements():
    node = parse_single_statement('[foo,bar,baz];')
    eq_(node.to_tuples(), ('List', ('Identifier', 'foo'), ('Identifier',
        'bar'), ('Identifier', 'baz')))


<mask token>


def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(), ('Set', ('Identifier', 'foo')))


<mask token>


def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b')))


def test_atomic_value_set_abc():
    node = parse_single_statement('{a,b..c};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b'), ('Identifier', 'c')))


def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


<mask token>


def test_set_comprehension_prime():
    s = '{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'set', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('BinaryOp', '*', (
        'Identifier', 'a'), ('Identifier', 'b')), ('IteratorChain', '', (
        'Iterator', ('Identifier', 'a'), ('Range', 'set', ('Constant',
        'int', 1), ('Constant', 'int', 3))), ('Iterator', ('Identifier',
        'b'), ('Range', 'set', ('Constant', 'int', 1), ('Constant', 'int', 
        3))))))


def test_list_comprehension_prime():
    s = '[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'list', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_variables():
    node = parse_single_statement('foo;')
    eq_(node.to_tuples(), ('Identifier', 'foo'))


<mask token>


def test_binop_contain():
    assert_binop('true    in false;', 'in', True, False)
    assert_binop('true notin false;', 'notin', True, False)


def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)


<mask token>


def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)


def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(), ('BinaryOp', '+', ('Identifier', 'foo'), (
        'Identifier', 'bar')))


def test_three_operands_precedence_and_or():
    node = parse_single_statement('true && false || true;')
    eq_(node.to_tuples(), ('BinaryOp', '||', ('BinaryOp', '&&', ('Constant',
        'bool', True), ('Constant', 'bool', False)), ('Constant', 'bool', 
        True)))


<mask token>


def test_more_than_one_statement_simple():
    stmts = parse_statements('1 + 2; 3 * 4;')
    eq_(stmts.to_tuples(), ('FileAST', ('BinaryOp', '+', ('Constant', 'int',
        1), ('Constant', 'int', 2)), ('BinaryOp', '*', ('Constant', 'int', 
        3), ('Constant', 'int', 4))))


<mask token>


def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',)))


def test_call_one_argument():
    node = parse_single_statement('foo(bar);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'))))


def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'), ('Identifier', 'baz'))))


<mask token>


def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(), ('Call', ('Call', ('Identifier', 'getCallback'),
        ('ArgumentList', ('Constant', 'string', 'onExit'))), ('ArgumentList',))
        )


def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'b')))


<mask token>


def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'a'), ('Identifier', 'b')))


<mask token>


def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'foo')),
        ('Constant', 'literal', 'bar')))


def test_lambda_minimal_one_param_brackets():
    node = parse_single_statement('x |-> x ** x;')
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'x')), (
        'BinaryOp', '**', ('Identifier', 'x'), ('Identifier', 'x'))))


<mask token>


def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'foo'), (
        'Constant', 'int', 42)))


def test_assignment_underscore():
    node = parse_single_statement('_ := true;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', '_'), (
        'Constant', 'bool', True)))


<mask token>


def test_assignment_attributeref():
    assignment = parse_single_statement('foo.bar := true;')
    eq_(assignment.to_tuples(), ('Assignment', ':=', ('AttributeRef', (
        'Identifier', 'foo'), ('Identifier', 'bar')), ('Constant', 'bool', 
        True)))


<mask token>


def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(), ('Assert', ('Identifier', 'isOverflown'),
        ('Constant', 'bool', False)))


def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(), ('Backtrack',))


<mask token>


def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(), ('Continue',))


<mask token>


def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(), ('Return', ('Constant', 'int', 42)))


<mask token>


def test_term_no_arguments():
    node = parse_single_statement('F();')
    eq_(node.to_tuples(), ('Term', 'F', ('ArgumentList',)))


<mask token>


def test_term_multi_arg():
    node = parse_single_statement('F(true, false);')
    eq_(node.to_tuples(), ('Term', 'F', ('ArgumentList', ('Constant',
        'bool', True), ('Constant', 'bool', False))))


<mask token>


def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(), ('Quantor', 'all', ('Iterator', ('Identifier',
        'n'), ('Range', 'list', ('Constant', 'int', 1), ('Constant', 'int',
        10))), ('BinaryOp', '<=', ('BinaryOp', '**', ('Identifier', 'n'), (
        'Constant', 'int', 2)), ('BinaryOp', '**', ('Constant', 'int', 2),
        ('Identifier', 'n')))))


def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(), ('If', ('Constant', 'bool', True), ('Block',)))


def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(), ('If', ('BinaryOp', '>=', ('Identifier', 'x'), (
        'Constant', 'int', 5)), ('Block', ('Return', ('Constant', 'bool', 
        True)))))


def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '&&', ('Identifier',
        'isRaining'), ('Identifier', 'isCold')), ('Block', ('Assignment',
        ':=', ('Identifier', 'prediction'), ('Constant', 'string',
        'Let it snow!')), ('Return', ('Identifier', 'prediction')))))


def test_if_else_minimal():
    node = parse_single_statement('if(foo) {} else {}')
    eq_(node.to_tuples(), ('If', ('Identifier', 'foo'), ('Block',), ('Block',))
        )


def test_if_single_else():
    s = """
    if(i % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'i'), ('Constant', 'int', 2)), ('Constant', 'int', 0)
        ), ('Block', ('Return', ('Constant', 'string', 'Even'))), ('Block',
        ('Return', ('Constant', 'string', 'Odd')))))


def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('Identifier', 'isGreen'), ('Block', (
        'Return', ('Constant', 'string', 'Green'))), ('If', ('Identifier',
        'isRed'), ('Block', ('Return', ('Constant', 'string', 'Red'))), (
        'Block', ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'grade'),
        ('Constant', 'string', 'A')), ('Block', ('Return', ('Constant',
        'string', 'Excellent'))), ('If', ('BinaryOp', '==', ('Identifier',
        'grade'), ('Constant', 'string', 'B')), ('Block', ('Return', (
        'Constant', 'string', 'Good'))), ('If', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'string', 'C')), ('Block', (
        'Return', ('Constant', 'string', 'Satisfactory'))), ('If', (
        'BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'D')), ('Block', ('Return', ('Constant', 'string', 'Pass'))), ('If',
        ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'F')), ('Block', ('Return', ('Constant', 'string', 'Fail'))), (
        'Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


def test_if_nested_else():
    s = """
    if(num1 == num2) {
        return "Equal";
    } else {
        if(num1 > num2) { 
            return "Num1 greater";
        } else {
            return "Num2 greater";
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'num1'),
        ('Identifier', 'num2')), ('Block', ('Return', ('Constant', 'string',
        'Equal'))), ('Block', ('If', ('BinaryOp', '>', ('Identifier',
        'num1'), ('Identifier', 'num2')), ('Block', ('Return', ('Constant',
        'string', 'Num1 greater'))), ('Block', ('Return', ('Constant',
        'string', 'Num2 greater')))))))


<mask token>


def test_switch_case_with_default():
    s = """
    switch {
        case grade == 'A' : return 'Excellent';
        case grade == 'B' : return 'Good';
        case grade == 'C' : return 'Satisfactory';
        case grade == 'D' : return 'Pass';
        case grade == 'F' : return 'Fail';
        default           : return 'Invalid input';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Switch', ('CaseList', ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'A')), (
        'Block', ('Return', ('Constant', 'literal', 'Excellent')))), (
        'Case', ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant',
        'literal', 'B')), ('Block', ('Return', ('Constant', 'literal',
        'Good')))), ('Case', ('BinaryOp', '==', ('Identifier', 'grade'), (
        'Constant', 'literal', 'C')), ('Block', ('Return', ('Constant',
        'literal', 'Satisfactory')))), ('Case', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'literal', 'D')), ('Block', (
        'Return', ('Constant', 'literal', 'Pass')))), ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'F')), (
        'Block', ('Return', ('Constant', 'literal', 'Fail'))))), ('Default',
        ('Block', ('Return', ('Constant', 'literal', 'Invalid input'))))))


def test_match_minimal():
    s = """
    match(s) {
        case [] : return s;
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List',), ('Block', ('Return', ('Identifier', 's')))))))


def test_match_one_pattern():
    s = """
    match(s) {
        case [h|t] : return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_pattern_default():
    s = """
    match(s) {
        case [h|t] : return 'Not empty!';
        default    : return 'Empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Constant', 'literal', 'Not empty!'))))), (
        'Default', ('Block', ('Return', ('Constant', 'literal', 'Empty!'))))))


<mask token>


def test_match_two_cases_default():
    s = """
    match (s) {
      case [h] : return h;
      case [h|t]: return t;
      default : return "Error!";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List', ('Identifier', 'h')), ('Block', ('Return', (
        'Identifier', 'h')))), ('MatchCase', ('Pattern', ('Identifier', 'h'
        ), ('Identifier', 't')), ('Block', ('Return', ('Identifier', 't')))
        )), ('Default', ('Block', ('Return', ('Constant', 'string',
        'Error!'))))))


def test_match_pattern_condition():
    s = """
    match(s) {
        case [h|t] | h != null: return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('BinaryOp', '!=', ('Identifier', 'h'), ('Identifier', 'null')), (
        'Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('Block', ('Return', (
        'Constant', 'string', 'bar')))))))


def test_match_one_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))))))


def test_match_regex_condition():
    s = """
    match(s) {
        regex '\\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', '\\w+'), ('As', ('Identifier', 'x'
        )), ('BinaryOp', '>=', ('UnaryOp', '#', ('Identifier', 'x')), (
        'Constant', 'int', 42)), ('Block', ('Return', ('Identifier', 'x')))))))


<mask token>


def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))), ('MatchCase', ('Pattern', ('Identifier', 'h'), (
        'Identifier', 't')), ('Block', ('Return', ('Constant', 'literal',
        'Not empty!')))))))


<mask token>


def test_scan_two_regex_using_default():
    s = """
    scan(c) using chr {
        regex '[0-9]' : return "Number";
        regex '[a-zA-Z]' : return "Character";
        default: return "Neither number nor character";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Scan', ('Identifier', 'c'), ('As', (
        'Identifier', 'chr')), ('CaseList', ('Regex', ('Constant',
        'literal', '[0-9]'), ('Block', ('Return', ('Constant', 'string',
        'Number')))), ('Regex', ('Constant', 'literal', '[a-zA-Z]'), (
        'Block', ('Return', ('Constant', 'string', 'Character'))))), (
        'Default', ('Block', ('Return', ('Constant', 'string',
        'Neither number nor character'))))))


<mask token>


def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(), ('While', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


def test_do_while_loop_minimal():
    node = parse_single_statement('do {} while(!empty); ')
    eq_(node.to_tuples(), ('DoWhile', ('UnaryOp', 'not', ('Identifier',
        'empty')), ('Block',)))


def test_do_while_loop_bigger_body():
    node = parse_single_statement('do { x *= 2; i -= 1;} while(i < n);')
    eq_(node.to_tuples(), ('DoWhile', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


def test_for_loop_minimal():
    node = parse_single_statement('for(x in primes) {}')
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'x'), (
        'Identifier', 'primes')), ('Block',)))


def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'c'), (
        'Identifier', 'characters')), ('Block', ('If', ('BinaryOp', 'in', (
        'Identifier', 'c'), ('Identifier', 'vowels')), ('Block', ('Return',
        ('Identifier', 'c')))))))


def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('IteratorChain', '', ('Iterator', (
        'Identifier', 'i'), ('Identifier', 'uids')), ('Iterator', (
        'Identifier', 's'), ('Identifier', 'street')), ('Iterator', (
        'Identifier', 'n'), ('Identifier', 'street_number'))), ('Block', (
        'Assignment', ':=', ('Subscription', ('Identifier', 'address'), (
        'Identifier', 'i')), ('Interpolation', ('Constant', 'literal',
        '$street$ $street_number$'), ('ExprList',))))))


def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), (
        'Block',)))


<mask token>


def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList', (
        'Param', 'x')), ('Block', ('Return', ('BinaryOp', '**', (
        'Identifier', 'x'), ('Identifier', 'x'))))))


def test_cached_procedure_max():
    s = """
    max := cachedProcedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'max'), (
        'Procedure', '', 'cached', ('ParamList', ('Param', 'a'), ('Param',
        'b')), ('Block', ('If', ('BinaryOp', '>', ('Identifier', 'a'), (
        'Identifier', 'b')), ('Block', ('Return', ('Identifier', 'a'))), (
        'Block', ('Return', ('Identifier', 'b'))))))))


<mask token>


def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',),
        ('Block',), ('Block',)))


<mask token>


def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',
        ('Param', 'x'), ('Param', 'y')), ('Block', ('Assignment', ':=', (
        'Identifier', 'mX'), ('Identifier', 'x')), ('Assignment', ':=', (
        'Identifier', 'mY'), ('Identifier', 'y'))), ('Block',)))


def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'universal'), (
        'ParamList',), ('Block',), ('Block', ('Assignment', ':=', (
        'Identifier', 'gAnswer'), ('Constant', 'int', 42)))))


def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catch', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_usr_minimal():
    s = """
    try {
    } catchUsr(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchUsr', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchLng', ('Identifier', 'e'), ('Block',)))))


def test_check_minimal():
    node = parse_single_statement('check {}')
    eq_(node.to_tuples(), ('Check', ('Block',)))


def test_check_single_expr():
    node = parser.parse('x!')

<mask token>


def parse_statements(text):
    root = parser.parse(text)
    return root


<mask token>


def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(), ('UnaryOp', operator, ('Constant', typ, val)))


def assert_binop_from_node(node, operator, left, right):
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_(node.right.value, right)


def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)


def assert_assignment(text, operator, left, right):
    """ Asserts that a given assignment has the
    expected operator, left- and ride hand side.

    Args:
        text: The string containing an assignment
        operator: The expected assignment operator 
        left: The expected left hand side variable
        right: The expected right hand side variable
    Raises:
        AssertionError: if result != expected
    """
    try:
        node = parse_single_statement(text)
        eq_(node.op, operator)
        eq_(node.target.name, left)
        eq_(node.right.value, right)
    except AssertionError as e:
        node.show()
        raise e


def assert_assignment_explicit(text, operator, left, right):
    node = parse_single_statement(text)
    eq_(node.op, operator)
    for var, l in zip(node.left.assignments, left):
        eq_(var.name, l)
    eq_(node.right.value, right)


<mask token>


def test_atomic_value_int():
    node = parse_single_statement('42;')
    eq_(node.to_tuples(), ('Constant', 'int', 42))


def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))


<mask token>


def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))


def test_atomic_value_interpolation():
    node = parse_single_statement('"n := $n$";')
    eq_(node.to_tuples(), ('Interpolation', ('Constant', 'literal',
        'n := $n$'), ('ExprList',)))


def test_list_minimal():
    node = parse_single_statement('[];')


<mask token>


def test_list_three_elements():
    node = parse_single_statement('[foo,bar,baz];')
    eq_(node.to_tuples(), ('List', ('Identifier', 'foo'), ('Identifier',
        'bar'), ('Identifier', 'baz')))


<mask token>


def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(), ('Set', ('Identifier', 'foo')))


<mask token>


def test_set_head_tail():
    node = parse_single_statement('{h|t};')
    eq_(node.to_tuples(), ('Pattern', ('Identifier', 'h'), ('Identifier', 't'))
        )


def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(), ('Pattern', ('ExprList', ('Identifier', 'x'), (
        'Identifier', 'y')), ('Identifier', 'z')))


<mask token>


def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b')))


def test_atomic_value_set_abc():
    node = parse_single_statement('{a,b..c};')
    eq_(node.to_tuples(), ('Range', 'set', ('Identifier', 'a'), (
        'Identifier', 'b'), ('Identifier', 'c')))


def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


<mask token>


def test_set_comprehension_prime():
    s = '{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'set', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'set', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Identifier', 'x'))))


def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(), ('Comprehension', 'list', ('BinaryOp', '*', (
        'Identifier', 'a'), ('Identifier', 'b')), ('IteratorChain', '', (
        'Iterator', ('Identifier', 'a'), ('Range', 'set', ('Constant',
        'int', 1), ('Constant', 'int', 3))), ('Iterator', ('Identifier',
        'b'), ('Range', 'set', ('Constant', 'int', 1), ('Constant', 'int', 
        3))))))


def test_list_comprehension_prime():
    s = '[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];'
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Comprehension', 'list', ('Identifier', 'p'), (
        'Iterator', ('Identifier', 'p'), ('Range', 'set', ('Constant',
        'int', 2), ('Constant', 'int', 100))), ('BinaryOp', '==', (
        'Comprehension', 'list', ('Identifier', 'x'), ('Iterator', (
        'Identifier', 'x'), ('Range', 'set', ('Constant', 'int', 1), (
        'Identifier', 'p'))), ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'p'), ('Identifier', 'x')), ('Constant', 'int', 0))),
        ('Set', ('Constant', 'int', 1), ('Identifier', 'p')))))


def test_variables():
    node = parse_single_statement('foo;')
    eq_(node.to_tuples(), ('Identifier', 'foo'))


<mask token>


def test_binop_contain():
    assert_binop('true    in false;', 'in', True, False)
    assert_binop('true notin false;', 'notin', True, False)


def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)


def test_binop_product():
    assert_binop('42 *  43;', '*', 42, 43)
    assert_binop('42 /  43;', '/', 42, 43)
    assert_binop('42 \\  43;', '\\', 42, 43)
    assert_binop('42 %  43;', '%', 42, 43)
    assert_binop('42 >< 43;', '><', 42, 43)
    assert_binop('42 ** 43;', '**', 42, 43)


def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)


def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(), ('BinaryOp', '+', ('Identifier', 'foo'), (
        'Identifier', 'bar')))


def test_three_operands_precedence_and_or():
    node = parse_single_statement('true && false || true;')
    eq_(node.to_tuples(), ('BinaryOp', '||', ('BinaryOp', '&&', ('Constant',
        'bool', True), ('Constant', 'bool', False)), ('Constant', 'bool', 
        True)))


<mask token>


def test_precendce_arithmetic():
    node = parse_single_statement('a + b  * c / ( d - e );')
    eq_(node.to_tuples(), ('BinaryOp', '+', ('Identifier', 'a'), (
        'BinaryOp', '/', ('BinaryOp', '*', ('Identifier', 'b'), (
        'Identifier', 'c')), ('BinaryOp', '-', ('Identifier', 'd'), (
        'Identifier', 'e')))))


<mask token>


def test_more_than_one_statement_simple():
    stmts = parse_statements('1 + 2; 3 * 4;')
    eq_(stmts.to_tuples(), ('FileAST', ('BinaryOp', '+', ('Constant', 'int',
        1), ('Constant', 'int', 2)), ('BinaryOp', '*', ('Constant', 'int', 
        3), ('Constant', 'int', 4))))


def test_attributeref_minimal():
    node = parse_single_statement('x.y;')
    eq_(node.to_tuples(), ('AttributeRef', ('Identifier', 'x'), (
        'Identifier', 'y')))


<mask token>


def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',)))


def test_call_one_argument():
    node = parse_single_statement('foo(bar);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'))))


def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'foo'), ('ArgumentList',
        ('Identifier', 'bar'), ('Identifier', 'baz'))))


def test_call_two_expressions():
    node = parse_single_statement('substring(size-1,size+3);')
    eq_(node.to_tuples(), ('Call', ('Identifier', 'substring'), (
        'ArgumentList', ('BinaryOp', '-', ('Identifier', 'size'), (
        'Constant', 'int', 1)), ('BinaryOp', '+', ('Identifier', 'size'), (
        'Constant', 'int', 3)))))


def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(), ('Call', ('Call', ('Identifier', 'getCallback'),
        ('ArgumentList', ('Constant', 'string', 'onExit'))), ('ArgumentList',))
        )


def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'b')))


<mask token>


def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(), ('Slice', ('Identifier', 'list'), ('Identifier',
        'a'), ('Identifier', 'b')))


<mask token>


def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'foo')),
        ('Constant', 'literal', 'bar')))


def test_lambda_minimal_one_param_brackets():
    node = parse_single_statement('x |-> x ** x;')
    eq_(node.to_tuples(), ('Lambda', ('ParamList', ('Identifier', 'x')), (
        'BinaryOp', '**', ('Identifier', 'x'), ('Identifier', 'x'))))


<mask token>


def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'foo'), (
        'Constant', 'int', 42)))


def test_assignment_underscore():
    node = parse_single_statement('_ := true;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', '_'), (
        'Constant', 'bool', True)))


<mask token>


def test_assignment_attributeref():
    assignment = parse_single_statement('foo.bar := true;')
    eq_(assignment.to_tuples(), ('Assignment', ':=', ('AttributeRef', (
        'Identifier', 'foo'), ('Identifier', 'bar')), ('Constant', 'bool', 
        True)))


def test_assignment_member_access_chained():
    assignment = parse_single_statement('foo.bar.baz := 42;')
    eq_(assignment.to_tuples(), ('Assignment', ':=', ('AttributeRef', (
        'AttributeRef', ('Identifier', 'foo'), ('Identifier', 'bar')), (
        'Identifier', 'baz')), ('Constant', 'int', 42)))


<mask token>


def test_assignment_mixed_attributeref_subscription():
    node = parse_single_statement('foo[x+1].bar.baz[42][y**2] := z;')
    eq_(node.to_tuples(), ('Assignment', ':=', ('Subscription', (
        'Subscription', ('AttributeRef', ('AttributeRef', ('Subscription',
        ('Identifier', 'foo'), ('BinaryOp', '+', ('Identifier', 'x'), (
        'Constant', 'int', 1))), ('Identifier', 'bar')), ('Identifier',
        'baz')), ('Constant', 'int', 42)), ('BinaryOp', '**', ('Identifier',
        'y'), ('Constant', 'int', 2))), ('Identifier', 'z')))


<mask token>


def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(), ('Assert', ('Identifier', 'isOverflown'),
        ('Constant', 'bool', False)))


def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(), ('Backtrack',))


<mask token>


def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(), ('Continue',))


def test_break_statement():
    node = parse_single_statement('exit;')
    eq_(node.to_tuples(), ('Exit',))


<mask token>


def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(), ('Return', ('Constant', 'int', 42)))


<mask token>


def test_term_no_arguments():
    node = parse_single_statement('F();')
    eq_(node.to_tuples(), ('Term', 'F', ('ArgumentList',)))


<mask token>


def test_term_multi_arg():
    node = parse_single_statement('F(true, false);')
    eq_(node.to_tuples(), ('Term', 'F', ('ArgumentList', ('Constant',
        'bool', True), ('Constant', 'bool', False))))


<mask token>


def test_quantifier_exists():
    node = parse_single_statement('exists (x in 1 | true);')
    eq_(node.to_tuples(), ('Quantor', 'any', ('Iterator', ('Identifier',
        'x'), ('Constant', 'int', 1)), ('Constant', 'bool', True)))


def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(), ('Quantor', 'all', ('Iterator', ('Identifier',
        'n'), ('Range', 'list', ('Constant', 'int', 1), ('Constant', 'int',
        10))), ('BinaryOp', '<=', ('BinaryOp', '**', ('Identifier', 'n'), (
        'Constant', 'int', 2)), ('BinaryOp', '**', ('Constant', 'int', 2),
        ('Identifier', 'n')))))


def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(), ('If', ('Constant', 'bool', True), ('Block',)))


def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(), ('If', ('BinaryOp', '>=', ('Identifier', 'x'), (
        'Constant', 'int', 5)), ('Block', ('Return', ('Constant', 'bool', 
        True)))))


def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '&&', ('Identifier',
        'isRaining'), ('Identifier', 'isCold')), ('Block', ('Assignment',
        ':=', ('Identifier', 'prediction'), ('Constant', 'string',
        'Let it snow!')), ('Return', ('Identifier', 'prediction')))))


def test_if_else_minimal():
    node = parse_single_statement('if(foo) {} else {}')
    eq_(node.to_tuples(), ('If', ('Identifier', 'foo'), ('Block',), ('Block',))
        )


def test_if_single_else():
    s = """
    if(i % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('BinaryOp', '%', (
        'Identifier', 'i'), ('Constant', 'int', 2)), ('Constant', 'int', 0)
        ), ('Block', ('Return', ('Constant', 'string', 'Even'))), ('Block',
        ('Return', ('Constant', 'string', 'Odd')))))


def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('Identifier', 'isGreen'), ('Block', (
        'Return', ('Constant', 'string', 'Green'))), ('If', ('Identifier',
        'isRed'), ('Block', ('Return', ('Constant', 'string', 'Red'))), (
        'Block', ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'grade'),
        ('Constant', 'string', 'A')), ('Block', ('Return', ('Constant',
        'string', 'Excellent'))), ('If', ('BinaryOp', '==', ('Identifier',
        'grade'), ('Constant', 'string', 'B')), ('Block', ('Return', (
        'Constant', 'string', 'Good'))), ('If', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'string', 'C')), ('Block', (
        'Return', ('Constant', 'string', 'Satisfactory'))), ('If', (
        'BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'D')), ('Block', ('Return', ('Constant', 'string', 'Pass'))), ('If',
        ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'string',
        'F')), ('Block', ('Return', ('Constant', 'string', 'Fail'))), (
        'Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


def test_if_nested_else():
    s = """
    if(num1 == num2) {
        return "Equal";
    } else {
        if(num1 > num2) { 
            return "Num1 greater";
        } else {
            return "Num2 greater";
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('If', ('BinaryOp', '==', ('Identifier', 'num1'),
        ('Identifier', 'num2')), ('Block', ('Return', ('Constant', 'string',
        'Equal'))), ('Block', ('If', ('BinaryOp', '>', ('Identifier',
        'num1'), ('Identifier', 'num2')), ('Block', ('Return', ('Constant',
        'string', 'Num1 greater'))), ('Block', ('Return', ('Constant',
        'string', 'Num2 greater')))))))


def test_switch_case_minimal():
    node = parse_single_statement('switch {}')
    eq_(node.to_tuples(), ('Switch', ('CaseList',)))


<mask token>


def test_switch_case_with_default():
    s = """
    switch {
        case grade == 'A' : return 'Excellent';
        case grade == 'B' : return 'Good';
        case grade == 'C' : return 'Satisfactory';
        case grade == 'D' : return 'Pass';
        case grade == 'F' : return 'Fail';
        default           : return 'Invalid input';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Switch', ('CaseList', ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'A')), (
        'Block', ('Return', ('Constant', 'literal', 'Excellent')))), (
        'Case', ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant',
        'literal', 'B')), ('Block', ('Return', ('Constant', 'literal',
        'Good')))), ('Case', ('BinaryOp', '==', ('Identifier', 'grade'), (
        'Constant', 'literal', 'C')), ('Block', ('Return', ('Constant',
        'literal', 'Satisfactory')))), ('Case', ('BinaryOp', '==', (
        'Identifier', 'grade'), ('Constant', 'literal', 'D')), ('Block', (
        'Return', ('Constant', 'literal', 'Pass')))), ('Case', ('BinaryOp',
        '==', ('Identifier', 'grade'), ('Constant', 'literal', 'F')), (
        'Block', ('Return', ('Constant', 'literal', 'Fail'))))), ('Default',
        ('Block', ('Return', ('Constant', 'literal', 'Invalid input'))))))


def test_match_minimal():
    s = """
    match(s) {
        case [] : return s;
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List',), ('Block', ('Return', ('Identifier', 's')))))))


def test_match_one_pattern():
    s = """
    match(s) {
        case [h|t] : return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_pattern_default():
    s = """
    match(s) {
        case [h|t] : return 'Not empty!';
        default    : return 'Empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('Block', ('Return', ('Constant', 'literal', 'Not empty!'))))), (
        'Default', ('Block', ('Return', ('Constant', 'literal', 'Empty!'))))))


<mask token>


def test_match_two_cases_default():
    s = """
    match (s) {
      case [h] : return h;
      case [h|t]: return t;
      default : return "Error!";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('List', ('Identifier', 'h')), ('Block', ('Return', (
        'Identifier', 'h')))), ('MatchCase', ('Pattern', ('Identifier', 'h'
        ), ('Identifier', 't')), ('Block', ('Return', ('Identifier', 't')))
        )), ('Default', ('Block', ('Return', ('Constant', 'string',
        'Error!'))))))


def test_match_pattern_condition():
    s = """
    match(s) {
        case [h|t] | h != null: return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'MatchCase', ('Pattern', ('Identifier', 'h'), ('Identifier', 't')),
        ('BinaryOp', '!=', ('Identifier', 'h'), ('Identifier', 'null')), (
        'Block', ('Return', ('Identifier', 'h')))))))


def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('Block', ('Return', (
        'Constant', 'string', 'bar')))))))


def test_match_one_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))))))


def test_match_regex_condition():
    s = """
    match(s) {
        regex '\\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', '\\w+'), ('As', ('Identifier', 'x'
        )), ('BinaryOp', '>=', ('UnaryOp', '#', ('Identifier', 'x')), (
        'Constant', 'int', 42)), ('Block', ('Return', ('Identifier', 'x')))))))


<mask token>


def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Match', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))), ('MatchCase', ('Pattern', ('Identifier', 'h'), (
        'Identifier', 't')), ('Block', ('Return', ('Constant', 'literal',
        'Not empty!')))))))


<mask token>


def test_match_one_regex_as():
    s = """
    scan(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Scan', ('Identifier', 's'), ('CaseList', (
        'Regex', ('Constant', 'literal', 'foo'), ('As', ('List', (
        'Identifier', 'bar'))), ('Block', ('Return', ('Constant', 'string',
        'baz')))))))


<mask token>


def test_scan_two_regex_using_default():
    s = """
    scan(c) using chr {
        regex '[0-9]' : return "Number";
        regex '[a-zA-Z]' : return "Character";
        default: return "Neither number nor character";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Scan', ('Identifier', 'c'), ('As', (
        'Identifier', 'chr')), ('CaseList', ('Regex', ('Constant',
        'literal', '[0-9]'), ('Block', ('Return', ('Constant', 'string',
        'Number')))), ('Regex', ('Constant', 'literal', '[a-zA-Z]'), (
        'Block', ('Return', ('Constant', 'string', 'Character'))))), (
        'Default', ('Block', ('Return', ('Constant', 'string',
        'Neither number nor character'))))))


<mask token>


def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(), ('While', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


def test_do_while_loop_minimal():
    node = parse_single_statement('do {} while(!empty); ')
    eq_(node.to_tuples(), ('DoWhile', ('UnaryOp', 'not', ('Identifier',
        'empty')), ('Block',)))


def test_do_while_loop_bigger_body():
    node = parse_single_statement('do { x *= 2; i -= 1;} while(i < n);')
    eq_(node.to_tuples(), ('DoWhile', ('BinaryOp', '<', ('Identifier', 'i'),
        ('Identifier', 'n')), ('Block', ('Assignment', '*=', ('Identifier',
        'x'), ('Constant', 'int', 2)), ('Assignment', '-=', ('Identifier',
        'i'), ('Constant', 'int', 1)))))


def test_for_loop_minimal():
    node = parse_single_statement('for(x in primes) {}')
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'x'), (
        'Identifier', 'primes')), ('Block',)))


def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('Iterator', ('Identifier', 'c'), (
        'Identifier', 'characters')), ('Block', ('If', ('BinaryOp', 'in', (
        'Identifier', 'c'), ('Identifier', 'vowels')), ('Block', ('Return',
        ('Identifier', 'c')))))))


def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('For', ('IteratorChain', '', ('Iterator', (
        'Identifier', 'i'), ('Identifier', 'uids')), ('Iterator', (
        'Identifier', 's'), ('Identifier', 'street')), ('Iterator', (
        'Identifier', 'n'), ('Identifier', 'street_number'))), ('Block', (
        'Assignment', ':=', ('Subscription', ('Identifier', 'address'), (
        'Identifier', 'i')), ('Interpolation', ('Constant', 'literal',
        '$street$ $street_number$'), ('ExprList',))))))


def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), (
        'Block',)))


<mask token>


def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList', (
        'Param', 'x')), ('Block', ('Return', ('BinaryOp', '**', (
        'Identifier', 'x'), ('Identifier', 'x'))))))


def test_cached_procedure_max():
    s = """
    max := cachedProcedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Assignment', ':=', ('Identifier', 'max'), (
        'Procedure', '', 'cached', ('ParamList', ('Param', 'a'), ('Param',
        'b')), ('Block', ('If', ('BinaryOp', '>', ('Identifier', 'a'), (
        'Identifier', 'b')), ('Block', ('Return', ('Identifier', 'a'))), (
        'Block', ('Return', ('Identifier', 'b'))))))))


<mask token>


def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',),
        ('Block',), ('Block',)))


<mask token>


def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'point'), ('ParamList',
        ('Param', 'x'), ('Param', 'y')), ('Block', ('Assignment', ':=', (
        'Identifier', 'mX'), ('Identifier', 'x')), ('Assignment', ':=', (
        'Identifier', 'mY'), ('Identifier', 'y'))), ('Block',)))


def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Class', ('Identifier', 'universal'), (
        'ParamList',), ('Block',), ('Block', ('Assignment', ':=', (
        'Identifier', 'gAnswer'), ('Constant', 'int', 42)))))


def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catch', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_usr_minimal():
    s = """
    try {
    } catchUsr(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchUsr', ('Identifier', 'e'), ('Block',)))))


def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), ('Try', ('Block',), ('Catches', ('CatchClause',
        'catchLng', ('Identifier', 'e'), ('Block',)))))


def test_check_minimal():
    node = parse_single_statement('check {}')
    eq_(node.to_tuples(), ('Check', ('Block',)))


def test_check_single_expr():
    node = parser.parse('x!')

#------------------------------------------------------------------------------
# setlx2py: test_parser.py
#
# Unit tests for the Parser class in setlx_parser.py
# Beware: Only the syntax is tested here!
#         Some of the code does not make semantically sense at all!
#
# Copyright (C) 2013, Jan-Christoph Klie
# License: Apache v2
#------------------------------------------------------------------------------

from nose.tools import eq_, with_setup, nottest

from setlx2py.setlx_lexer import Lexer
from setlx2py.setlx_parser import Parser
from setlx2py.setlx_ast import *

##
## Test housekeeping
##

parser = Parser(yacc_optimize=False)

##
## Parse helper
##

def parse_statements(text):    
    root = parser.parse(text) # FileAST
    return root # List of statements

def parse_single_statement(text):
    return parse_statements(text).stmts[0] # first statement after FileAST

##
## Custom asserts
##

def assert_unop(text, operator, typ, val):
    node = parse_single_statement(text)
    eq_(node.to_tuples(),
        ('UnaryOp', operator, ('Constant', typ, val)))

def assert_binop_from_node(node, operator, left, right):    
    eq_(node.op, operator)
    eq_(node.left.value, left)
    eq_( node.right.value, right)
    
def assert_binop(text, operator, left, right):
    node = parse_single_statement(text)
    assert_binop_from_node(node, operator, left, right)

def assert_assignment(text, operator, left, right):
    """ Asserts that a given assignment has the
    expected operator, left- and ride hand side.

    Args:
        text: The string containing an assignment
        operator: The expected assignment operator 
        left: The expected left hand side variable
        right: The expected right hand side variable
    Raises:
        AssertionError: if result != expected
    """
    try:
        node = parse_single_statement(text)
        eq_(node.op, operator)
        eq_(node.target.name, left)
        eq_( node.right.value, right)
    except AssertionError as e:
        node.show()
        raise e

def assert_assignment_explicit(text, operator, left, right):
    node = parse_single_statement(text)
    eq_(node.op, operator)
    for var, l in zip(node.left.assignments, left):
        eq_(var.name, l)
    eq_(node.right.value, right)

#    _______       _
#   |__   __|     | |
#      | | ___ ___| |_ ___
#      | |/ _ / __| __/ __|
#      | |  __\__ | |_\__ \
#      |_|\___|___/\__|___/
                                       
def test_should_be_creatable():
    assert parser is not None
    
##
## Atomic Values
##    

def test_atomic_value_int():
    node = parse_single_statement('42;')
    eq_(node.to_tuples(), ('Constant', 'int', 42))

def test_atomic_value_double():
    node = parse_single_statement('42.0;')
    eq_(node.to_tuples(), ('Constant', 'double', 42.0))

def test_atomic_value_true():
    node = parse_single_statement('true;')
    eq_(node.to_tuples(), ('Constant', 'bool', True))

def test_atomic_value_false():
    node = parse_single_statement('false;')
    eq_(node.to_tuples(), ('Constant', 'bool', False))

def test_atomic_value_interpolation():
    node = parse_single_statement('"n := $n$";')
    eq_(node.to_tuples(),
        ('Interpolation',
         ('Constant', 'literal', 'n := $n$'),
         ('ExprList', )))
    
##
## Collections
##

# List

def test_list_minimal():
    node = parse_single_statement('[];')

def test_list_single_element():
    node = parse_single_statement('[foo];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo')))

def test_list_two_elements():
    node = parse_single_statement('[foo,bar];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

def test_list_three_elements():
    node = parse_single_statement('[foo,bar,baz];')
    eq_(node.to_tuples(),
        ('List',
         ('Identifier', 'foo'),
         ('Identifier', 'bar'),
         ('Identifier', 'baz')))

def test_list_head_tail():
    node = parse_single_statement('[h|t];')
    eq_(node.to_tuples(),
        ('Pattern',
         ('Identifier', 'h'),
         ('Identifier', 't')))

def test_list_two_head_tail():
    node = parse_single_statement('[x,y|z];')
    eq_(node.to_tuples(),
        ('Pattern',
         ('ExprList',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))    

# Set

def test_set_minimal():
    node = parse_single_statement('{};')

def test_set_single_element():
    node = parse_single_statement('{foo};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo')))

def test_set_two_elements():
    node = parse_single_statement('{foo,bar};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

def test_set_three_elements():
    node = parse_single_statement('{foo,bar,baz};')
    eq_(node.to_tuples(),
        ('Set',
         ('Identifier', 'foo'),
         ('Identifier', 'bar'),
         ('Identifier', 'baz')))

def test_set_head_tail():
    node = parse_single_statement('{h|t};')
    eq_(node.to_tuples(),
        ('Pattern',
         ('Identifier', 'h'),
         ('Identifier', 't')))

def test_set_two_head_tail():
    node = parse_single_statement('{x,y|z};')
    eq_(node.to_tuples(),
        ('Pattern',
         ('ExprList',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))    
    
##
## Ranges
##

# List

def test_atomic_value_list_ab():
    node = parse_single_statement('[1 .. 10];')
    eq_(node.to_tuples(),
        ('Range', 'list',
         ('Constant', 'int', 1),
         ('Constant', 'int', 10)))

def test_atomic_value_list_abc():
    node = parse_single_statement('[a,b..c];')
    eq_(node.to_tuples(),
        ('Range', 'list',
         ('Identifier', 'a'),
         ('Identifier', 'b'),
         ('Identifier', 'c')))    
    
# Set

def test_atomic_value_set_ab():
    node = parse_single_statement('{a..b};')
    eq_(node.to_tuples(),
        ('Range', 'set',
         ('Identifier', 'a'),
         ('Identifier', 'b')))

def test_atomic_value_set_abc():
    node = parse_single_statement('{a,b..c};')
    eq_(node.to_tuples(),
        ('Range', 'set',
         ('Identifier', 'a'),
         ('Identifier', 'b'),
         ('Identifier', 'c')))

##
## Comprehension
##

# Set

def test_set_comprehension_minimal():
    node = parse_single_statement('{p: p in x};')
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Identifier', 'x'))))

def test_set_comprehension_two_iterators():
    node = parse_single_statement('{a * b: a in {1..3}, b in {1..3}};')
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('BinaryOp', '*',
          ('Identifier', 'a'),
          ('Identifier', 'b')),
         ('IteratorChain', '',
          ('Iterator',
           ('Identifier', 'a'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))),
          ('Iterator',
           ('Identifier', 'b'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))))))

def test_set_comprehension_prime():
    s = """{ p : p in {2..100} | { x : x in {1..p} | p % x == 0 } == {1, p} };"""
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Comprehension', 'set',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Range', 'set',
           ('Constant', 'int', 2),
           ('Constant', 'int', 100))),
         ('BinaryOp', '==',
          ('Comprehension', 'set',
           ('Identifier', 'x'),
           ('Iterator',
            ('Identifier','x'),
            ('Range', 'set',
             ('Constant', 'int', 1),
             ('Identifier', 'p'))),
           ('BinaryOp', '==',
            ('BinaryOp', '%',
             ('Identifier', 'p'),
             ('Identifier', 'x')),
            ('Constant', 'int', 0))),
          ('Set',
           ('Constant', 'int', 1),
           ('Identifier', 'p')))))

# List

def test_list_comprehension_minimal():
    node = parse_single_statement('[p: p in x];')
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Identifier', 'x'))))

def test_list_comprehension_two_iterators():
    node = parse_single_statement('[a * b: a in {1..3}, b in {1..3}];')
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('BinaryOp', '*',
          ('Identifier', 'a'),
          ('Identifier', 'b')),
         ('IteratorChain', '',
          ('Iterator',
           ('Identifier', 'a'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))),
          ('Iterator',
           ('Identifier', 'b'),
           ('Range', 'set',
            ('Constant', 'int', 1),
            ('Constant', 'int', 3))))))

def test_list_comprehension_prime():
    s = """[ p : p in {2..100} | [ x : x in {1..p} | p % x == 0 ] == {1, p} ];"""
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Comprehension', 'list',
         ('Identifier', 'p'),
         ('Iterator',
          ('Identifier', 'p'),
          ('Range', 'set',
           ('Constant', 'int', 2),
           ('Constant', 'int', 100))),
         ('BinaryOp', '==',
          ('Comprehension', 'list',
           ('Identifier', 'x'),
           ('Iterator',
            ('Identifier','x'),
            ('Range', 'set',
             ('Constant', 'int', 1),
             ('Identifier', 'p'))),
           ('BinaryOp', '==',
            ('BinaryOp', '%',
             ('Identifier', 'p'),
             ('Identifier', 'x')),
            ('Constant', 'int', 0))),
          ('Set',
           ('Constant', 'int', 1),
           ('Identifier', 'p')))))
    
##
## Variables
##

def test_variables():
    node = parse_single_statement('foo;')
    eq_(node.to_tuples(),
        ('Identifier', 'foo'))

##    
## Binary Operations
##

def test_binop_boolean():
    assert_binop('true <==> true;', '<==>', True, True)
    assert_binop('true <!=> true;', '<!=>', True, True)    
    assert_binop('true => true;',     '=>', True, True)    
    assert_binop('true || false;',    '||', True, False)
    assert_binop('true && false;',    '&&', True, False)
    assert_binop('"foo" == \'bar\';', '==', "foo", "bar")
    assert_binop('true != false;',    '!=', True, False)
    assert_binop('1 <  2;',            '<', 1, 2)
    assert_binop('true <= false;',    '<=', True, False)
    assert_binop('true >  false;',     '>', True, False)
    assert_binop('true >= false;',    '>=', True, False)    

def test_binop_contain():
    assert_binop('true    in false;',    'in', True, False)
    assert_binop('true notin false;', 'notin', True, False)

def test_binop_sum():
    assert_binop('42 + 43;', '+', 42, 43)
    assert_binop('42 - 43;', '-', 42, 43)

def test_binop_product():
    assert_binop('42 *  43;',  '*', 42, 43)
    assert_binop('42 /  43;',  '/', 42, 43)
    assert_binop('42 \  43;', '\\', 42, 43)
    assert_binop('42 %  43;',  '%', 42, 43)
    assert_binop('42 >< 43;', '><', 42, 43)
    assert_binop('42 ** 43;', '**', 42, 43)

def test_binop_reduce():
    assert_binop('42 +/ 43;', '+/', 42, 43)
    assert_binop('42 */ 43;', '*/', 42, 43)

def test_expr_paren_simple():
    node = parse_single_statement('(foo + bar);')
    eq_(node.to_tuples(),
        ('BinaryOp', '+',
         ('Identifier', 'foo'),
         ('Identifier', 'bar')))

##    
## Precedence
##

def test_three_operands_precedence_and_or():
    node = parse_single_statement('true && false || true;')
    eq_(node.to_tuples(),
        ('BinaryOp', '||',
         ('BinaryOp', '&&',
          ('Constant', 'bool', True),
          ('Constant', 'bool', False)),
         ('Constant', 'bool', True)))

def test_three_operands_precedence_minus_divide():
    node = parse_single_statement('8 - 4 / 2.0;')
    eq_(node.to_tuples(),
        ('BinaryOp', '-',
         ('Constant', 'int', 8),
         ('BinaryOp', '/',
          ('Constant', 'int', 4 ),
          ('Constant', 'double', 2.0 ))))

def test_precendce_arithmetic():
    node = parse_single_statement('a + b  * c / ( d - e );')
    eq_(node.to_tuples(),
        ('BinaryOp', '+',
         ('Identifier', 'a'),
         ('BinaryOp', '/',
          ('BinaryOp', '*',
           ('Identifier', 'b'),
           ('Identifier', 'c')),
          ('BinaryOp', '-',
           ('Identifier', 'd'),
           ('Identifier', 'e')))))    

##
## Unary operations
##
    
def test_unop():
    assert_unop('+/ 42;',  '+/', 'int', 42)
    assert_unop('*/ 42;',  '*/', 'int', 42)
    assert_unop('-  42;',   '-', 'int', 42)
    assert_unop('#  42;',   '#', 'int', 42)
    assert_unop('@  42;',   '@', 'int', 42)
    assert_unop('!true;', 'not', 'bool', True)
    assert_unop('1337!;', 'fac', 'int', 1337)

####    
##
## Simple Statements
##
####
    
def test_more_than_one_statement_simple():
    stmts = parse_statements('1 + 2; 3 * 4;')
    eq_(stmts.to_tuples(),
        ('FileAST',
         ('BinaryOp', '+',
          ('Constant', 'int', 1),
          ('Constant', 'int', 2)),
         ('BinaryOp', '*',
          ('Constant', 'int', 3),
          ('Constant', 'int', 4))))

##
## Attributeref
##

def test_attributeref_minimal():
    node = parse_single_statement('x.y;')
    eq_(node.to_tuples(),
        ('AttributeRef',
         ('Identifier', 'x'),
         ('Identifier', 'y')))

def test_attributeref_chained():
    node = parse_single_statement('x.y.z;')
    eq_(node.to_tuples(),
        ('AttributeRef',
         ('AttributeRef',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Identifier', 'z')))

##
## Subscription
##

def test_subscription_minimal():
    node = parse_single_statement('x[y];')
    eq_(node.to_tuples(),
        ('Subscription',
         ('Identifier', 'x'),
         ('Identifier', 'y')))

def test_subscription_chained():
    node = parse_single_statement('x[y][0];')
    eq_(node.to_tuples(),
        ('Subscription',
         ('Subscription',
          ('Identifier', 'x'),
          ('Identifier', 'y')),
         ('Constant', 'int', 0)))    
        
##
## Call
##

def test_call_minimal():
    node = parse_single_statement('foo();')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList', )))

def test_call_one_argument():
    node = parse_single_statement('foo(bar);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList',
          ('Identifier', 'bar'))))

def test_call_two_arguments():
    node = parse_single_statement('foo(bar,baz);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'foo'),
         ('ArgumentList',
          ('Identifier', 'bar'),
          ('Identifier', 'baz'))))

def test_call_two_expressions():
    node = parse_single_statement('substring(size-1,size+3);')
    eq_(node.to_tuples(),
        ('Call',
         ('Identifier', 'substring'),
         ('ArgumentList',
          ('BinaryOp', '-', ('Identifier', 'size'), ('Constant', 'int', 1)),
          ('BinaryOp', '+', ('Identifier', 'size'), ('Constant', 'int', 3)))))

def test_call_chained():
    node = parse_single_statement('getCallback("onExit")();')
    eq_(node.to_tuples(),
        ('Call',
         ('Call',
          ('Identifier', 'getCallback'),
          ('ArgumentList',
           ('Constant', 'string', 'onExit'))),
         ('ArgumentList', )))

##
## Slicing
##

def test_slicing_left():
    node = parse_single_statement('list[..b];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'b')))

def test_slicing_right():
    node = parse_single_statement('list[a..];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'a')))
    
def test_slicing_both():
    node = parse_single_statement('list[a..b];')
    eq_(node.to_tuples(),
        ('Slice',
         ('Identifier', 'list'),
         ('Identifier', 'a'),
         ('Identifier', 'b'))) 
##
## Lambda
##

def test_lambda_minimal_zero_params():
    node = parse_single_statement('[ ] |-> true;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ),
         ('Constant', 'bool', True)))

def test_lamba_minimal_one_param_no_brackets():
    node = parse_single_statement("foo |-> 'bar';")
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'foo')),
         ('Constant', 'literal', 'bar')))

def test_lambda_minimal_one_param_brackets():
    node = parse_single_statement('x |-> x ** x;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'x')),
         ('BinaryOp', '**',
          ('Identifier', 'x'),
          ('Identifier', 'x'))))

def test_lambda_two_params():
    node = parse_single_statement('[x,y] |-> x+y;')
    eq_(node.to_tuples(),
        ('Lambda',
         ('ParamList', ('Identifier', 'x'), ('Identifier', 'y')),
         ('BinaryOp', '+',
          ('Identifier', 'x'),
          ('Identifier', 'y'))))
    
##
## Assignment
##

def test_assignment_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'foo'),
         ('Constant', 'int', 42)))    
    
def test_assignment_underscore():
    node = parse_single_statement('_ := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', '_'),
         ('Constant', 'bool', True)))    

def test_assignment_explicit_minimal():
    node = parse_single_statement('foo := 42;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'foo'),
         ('Constant', 'int', 42)))

def test_assignment_explicit_two():
    node = parse_single_statement('[foo, bar] := "xy";')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('List',
          ('Identifier', 'foo'),
          ('Identifier', 'bar')),
         ('Constant', 'string', 'xy')))
    
def test_assignment_attributeref():
    assignment = parse_single_statement('foo.bar := true;')
    eq_(assignment.to_tuples(),
        ('Assignment', ':=',
        ('AttributeRef',
          ('Identifier', 'foo'),
          ('Identifier', 'bar')),
         ('Constant', 'bool', True)))

def test_assignment_member_access_chained():
    assignment = parse_single_statement('foo.bar.baz := 42;')
    eq_(assignment.to_tuples(),
        ('Assignment', ':=',
         ('AttributeRef',
          ('AttributeRef',
           ('Identifier', 'foo'),
           ('Identifier', 'bar')),
          ('Identifier', 'baz')),
         ('Constant', 'int', 42)))

def test_assignment_subscription():
    node = parse_single_statement('foo[0] := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Identifier', 'foo'),
          ('Constant', 'int', 0)),
         ('Constant', 'bool', True)))

def test_assignment_subscription_chained():
    node = parse_single_statement('foo[0][1] := true;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Subscription',
           ('Identifier', 'foo'),
           ('Constant', 'int', 0)),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))

def test_assignment_mixed_attributeref_subscription():
    node = parse_single_statement('foo[x+1].bar.baz[42][y**2] := z;')
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Subscription',
          ('Subscription',
           ('AttributeRef',
            ('AttributeRef',
             ('Subscription',
              ('Identifier', 'foo'),
              ('BinaryOp', '+', ('Identifier', 'x'), ('Constant', 'int', 1))),
             ('Identifier', 'bar')),
            ('Identifier', 'baz')),
           ('Constant', 'int', 42)),
          ('BinaryOp', '**', ('Identifier', 'y'), ('Constant', 'int', 2))),
         ('Identifier', 'z')))

##
## Augmented Assignment
##

def test_augmented_assignment():
    assert_assignment('foo += 42;', '+=',  'foo', 42)
    assert_assignment('foo -= 1;',  '-=',  'foo', 1)
    assert_assignment('foo *= 2;',  '*=',  'foo', 2)
    assert_assignment('foo /= 2;',  '/=',  'foo', 2)
    assert_assignment('foo \\= 3;', '\\=', 'foo', 3)
    assert_assignment('foo %= 10;', '%=',  'foo', 10)

##    
## Assert
##

def test_assert_simple():
    assert_stmt = parse_single_statement('assert(isOverflown, false);')
    eq_(assert_stmt.to_tuples(),
        ('Assert',
         ('Identifier', 'isOverflown'),
         ('Constant', 'bool', False)))

##
## Jump statements
##

def test_backtrack_statement():
    node = parse_single_statement('backtrack;')
    eq_(node.to_tuples(),
        ('Backtrack',))

def test_break_statement():
    node = parse_single_statement('break;')
    eq_(node.to_tuples(),
        ('Break',))

def test_continue_statement():
    node = parse_single_statement('continue;')
    eq_(node.to_tuples(),
        ('Continue',))

def test_break_statement():
    node = parse_single_statement('exit;')
    eq_(node.to_tuples(),
        ('Exit',))

def test_return_statement_void():
    node = parse_single_statement('return;')
    eq_(node.to_tuples(),
        ('Return',))

def test_return_statement_const():
    node = parse_single_statement('return 42;')
    eq_(node.to_tuples(),
        ('Return',
         ('Constant', 'int', 42)))

def test_return_statement_binop():
    node = parse_single_statement('return 42 ** 2;')
    eq_(node.to_tuples(),
        ('Return',
         ('BinaryOp', '**',
          ('Constant', 'int', 42),
          ('Constant', 'int', 2))))

##    
## Terms
##

def test_term_no_arguments():
    node = parse_single_statement('F();')
    eq_(node.to_tuples(),
        ('Term', 'F', ('ArgumentList',)))

def test_term_single_arg():
    node = parse_single_statement('F(true);')
    eq_(node.to_tuples(),
        ('Term', 'F',
         (('ArgumentList'),
          ('Constant', 'bool', True))))

def test_term_multi_arg():
    node = parse_single_statement('F(true, false);')
    eq_(node.to_tuples(),
        ('Term', 'F',
         ('ArgumentList', 
          ('Constant', 'bool', True),
          ('Constant', 'bool', False))))

##
## Quantifier
##

def test_quantifier_all():
    node =  parse_single_statement('forall (x in 1 | true);')
    eq_(node.to_tuples(),
        ('Quantor', 'all',
         ('Iterator',
          ('Identifier', 'x'),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))
    

def test_quantifier_exists():
    node =  parse_single_statement('exists (x in 1 | true);')
    eq_(node.to_tuples(),
        ('Quantor', 'any',
         ('Iterator',
          ('Identifier', 'x'),
          ('Constant', 'int', 1)),
         ('Constant', 'bool', True)))

def test_quantifier_cray():
    node = parse_single_statement('forall (n in [1 .. 10] | n**2 <= 2**n);')
    eq_(node.to_tuples(),
        ('Quantor', 'all',
         ('Iterator',
          ('Identifier', 'n'),
          ('Range', 'list',
           ('Constant', 'int', 1),
           ('Constant', 'int', 10))),
         ('BinaryOp', '<=',
          ('BinaryOp', '**',
           ('Identifier', 'n'),
           ('Constant', 'int', 2)),
          ('BinaryOp', '**',
           ('Constant', 'int', 2),
           ('Identifier', 'n')))))
    
####
##
## Compound Statements
##
####
    
##
## If statements
##

def test_if_minimal():
    node = parse_single_statement('if(true) {}')
    eq_(node.to_tuples(),
        ('If',
         ('Constant', 'bool', True),
         ('Block', )))

def test_if_no_else():
    node = parse_single_statement('if(x >= 5) { return true; }')
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '>=',
          ('Identifier', 'x'),
          ('Constant', 'int', 5)),
         ('Block',
          ('Return', ('Constant', 'bool', True)))))

def test_if_no_else_longer_block():
    s = """
    if(isRaining && isCold) {
        prediction := "Let it snow!";
        return prediction;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '&&',
          ('Identifier', 'isRaining'),
          ('Identifier', 'isCold')),
         ('Block',
          ('Assignment', ':=',
           ('Identifier', 'prediction'),
           ('Constant', 'string', 'Let it snow!')),
          ('Return', ('Identifier', 'prediction')))))

# If-Else

def test_if_else_minimal():
    node = parse_single_statement("if(foo) {} else {}")
    eq_(node.to_tuples(),
        ('If',
         ('Identifier', 'foo'),
         ('Block',),
         ('Block',)))
    

def test_if_single_else():
    s = """
    if(i % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '==',
          ('BinaryOp', '%',
           ('Identifier', 'i'),
           ('Constant', 'int', 2)),
          ('Constant', 'int', 0)),
         ('Block', ('Return', ('Constant', 'string', 'Even'))),
         ('Block', ('Return', ('Constant', 'string', 'Odd')))))

def test_if_else_if_else_simple():
    s = """
    if(isGreen) {
        return "Green";
    } else if(isRed) {
        return "Red";
    } else {
        return "Not green nor red";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('Identifier', 'isGreen'),
         ('Block',
          ('Return', ('Constant', 'string', 'Green'))),
         ('If',
          ('Identifier', 'isRed'),
          ('Block',
           ('Return', ('Constant', 'string', 'Red'))),
          ('Block',
           ('Return', ('Constant', 'string', 'Not green nor red'))))))


def test_if_four_else_if_else():
    s = """
    if(grade == "A") {
        return "Excellent";
    } else if(grade == "B") {
        return "Good";
    } else if(grade == "C") {
        return "Satisfactory";
    } else if(grade == "D") {
        return "Pass";
    } else if(grade == "F") {
        return "Fail";
    } else {
        return "Invalid input";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(), 
         ('If', ('BinaryOp', '==',
                 ('Identifier', 'grade'),
                 ('Constant', 'string', 'A')),
          ('Block', ('Return', ('Constant', 'string', 'Excellent'))),
          ('If', ('BinaryOp', '==',
                  ('Identifier', 'grade'),
                  ('Constant', 'string', 'B')),
           ('Block', ('Return', ('Constant', 'string', 'Good'))),
           ('If', ('BinaryOp', '==',
                   ('Identifier', 'grade'),
                   ('Constant', 'string', 'C')),
            ('Block', ('Return', ('Constant', 'string', 'Satisfactory'))),
            ('If', ('BinaryOp', '==',
                    ('Identifier', 'grade'),
                    ('Constant', 'string', 'D')),
             ('Block', ('Return', ('Constant', 'string', 'Pass'))),
             ('If', ('BinaryOp', '==',
                     ('Identifier', 'grade'),
                     ('Constant', 'string', 'F')),
              ('Block', ('Return', ('Constant', 'string', 'Fail'))),
              ('Block', ('Return', ('Constant', 'string', 'Invalid input')))))))))


def test_if_nested_else():
    s = """
    if(num1 == num2) {
        return "Equal";
    } else {
        if(num1 > num2) { 
            return "Num1 greater";
        } else {
            return "Num2 greater";
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('If',
         ('BinaryOp', '==', ('Identifier', 'num1'), ('Identifier', 'num2')), 
         ('Block', ('Return', ('Constant', 'string', 'Equal'))),  
         ('Block',
          ('If',
           ('BinaryOp', '>', ('Identifier', 'num1'), ('Identifier', 'num2')),
           ('Block', ('Return', ('Constant', 'string', 'Num1 greater'))),
           ('Block', ('Return', ('Constant', 'string', 'Num2 greater')))))))

##
## Switch/Case
##

def test_switch_case_minimal():
    node = parse_single_statement('switch {}')
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList', )))

def test_switch_case_simple_no_default():
    s = """
    // Check whether an integer n is even
    switch {
        case n % 2 == 0 : return 'Even';
        case n % 2 == 1 : return 'Odd';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList',
          ('Case',
           ('BinaryOp', '==',
            ('BinaryOp', '%', ('Identifier', 'n'), ('Constant', 'int', 2)),
            ('Constant', 'int', 0)),
           ('Block', ('Return', ('Constant', 'literal', 'Even')))),
          ('Case',
           ('BinaryOp', '==',
            ('BinaryOp', '%', ('Identifier', 'n'), ('Constant', 'int', 2)),
            ('Constant', 'int', 1)),
           ('Block', ('Return', ('Constant', 'literal', 'Odd')))))))
    
def test_switch_case_with_default():
    s = """
    switch {
        case grade == 'A' : return 'Excellent';
        case grade == 'B' : return 'Good';
        case grade == 'C' : return 'Satisfactory';
        case grade == 'D' : return 'Pass';
        case grade == 'F' : return 'Fail';
        default           : return 'Invalid input';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Switch',
         ('CaseList',
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'A')),
          ('Block', ('Return', ('Constant', 'literal', 'Excellent')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'B')),
          ('Block', ('Return', ('Constant', 'literal', 'Good')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'C')),
          ('Block', ('Return', ('Constant', 'literal', 'Satisfactory')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'D')),
          ('Block', ('Return', ('Constant', 'literal', 'Pass')))),
          ('Case',
           ('BinaryOp', '==', ('Identifier', 'grade'), ('Constant', 'literal', 'F')),
          ('Block', ('Return', ('Constant', 'literal', 'Fail'))))),
          ('Default', ('Block', ('Return', ('Constant', 'literal', 'Invalid input'))))))

##
## Match
##


def test_match_minimal():
    s = """
    match(s) {
        case [] : return s;
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List', ),
           ('Block',
            ('Return', ('Identifier', 's')))))))

def test_match_one_pattern():
    s = """
    match(s) {
        case [h|t] : return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Identifier', 'h')))))))

def test_match_one_pattern_default():
    s = """
    match(s) {
        case [h|t] : return 'Not empty!';
        default    : return 'Empty!';
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!'))))),
         ('Default',
          ('Block',
           ('Return', ('Constant', 'literal', 'Empty!'))))))

def test_match_two_cases():
    s = """
    match (s) {
      case [] : return "Empty";
      case [h|t]: return "Not empty";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List',),
           ('Block', ('Return', ('Constant', 'string', 'Empty')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'string', 'Not empty')))))))

def test_match_two_cases_default():
    s = """
    match (s) {
      case [h] : return h;
      case [h|t]: return t;
      default : return "Error!";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('List', ('Identifier', 'h')),
           ('Block', ('Return', ('Identifier', 'h')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Identifier', 't'))))),
          ('Default',
           ('Block',
            ('Return', ('Constant', 'string', 'Error!'))))))

def test_match_pattern_condition():
    s = """
    match(s) {
        case [h|t] | h != null: return h;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('BinaryOp', '!=',
            ('Identifier', 'h'),
            ('Identifier', 'null')),
           ('Block',
            ('Return', ('Identifier', 'h')))))))
    
# Regex

def test_match_one_regex():
    s = """
    match(s) {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

def test_match_one_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_match_regex_condition():
    s = """
    match(s) {
        regex '\w+' as x | # x >= 42: return x;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', '\w+'),
           ('As', ('Identifier', 'x')),
           ('BinaryOp', '>=',
            ('UnaryOp', '#', ('Identifier', 'x')),
            ('Constant', 'int', 42)),
           ('Block',
            ('Return', ('Identifier', 'x')))))))

# Case + Regex

def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))),
          ('Case',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!')))))))

def test_match_pattern_regex_as():
    s = """
    match(s) {
        regex 'foo' as [ bar ] : return "baz";
        case [h|t] : return 'Not empty!';
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Match',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))),
          ('MatchCase',
           ('Pattern',
            ('Identifier', 'h'),
            ('Identifier', 't')),
           ('Block',
            ('Return', ('Constant', 'literal', 'Not empty!')))))))
    
##
## Scan
##

def test_scan_one_regex():
    s = """
    scan(s) {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

def test_scan_one_regex_using():
    s = """
    scan(x) using y {
        regex 'foo' : return "bar";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 'x'),
         ('As', ('Identifier', 'y')),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))))))

    
def test_match_one_regex_as():
    s = """
    scan(s) {
        regex 'foo' as [ bar ] : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('As',
            ('List', ('Identifier', 'bar'))),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_scan_two_regex():
    s = """
    scan(s) {
        regex 'foo' : return "bar";
        regex 'bar' : return "baz";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 's'),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', 'foo'),
           ('Block',
            ('Return', ('Constant', 'string', 'bar')))),
          ('Regex',
           ('Constant', 'literal', 'bar'),
           ('Block',
            ('Return', ('Constant', 'string', 'baz')))))))

def test_scan_two_regex_using_default():
    s = """
    scan(c) using chr {
        regex '[0-9]' : return "Number";
        regex '[a-zA-Z]' : return "Character";
        default: return "Neither number nor character";
    }
    """    
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Scan',
         ('Identifier', 'c'),
         ('As', ('Identifier','chr')),
         ('CaseList',
          ('Regex',
           ('Constant', 'literal', '[0-9]'),
           ('Block',
            ('Return', ('Constant', 'string', 'Number')))),
          ('Regex',
           ('Constant', 'literal', '[a-zA-Z]'),
           ('Block',
            ('Return', ('Constant', 'string', 'Character'))))),
          ('Default',
           ('Block',
            ('Return', ('Constant', 'string', 'Neither number nor character'))))))
    
##
## Try/Catch
##    
    
##
## While-Loop
##

def test_while_minimal():
    node = parse_single_statement("while(!empty) {}")
    eq_(node.to_tuples(),
        ('While',
         ('UnaryOp', 'not', ('Identifier', 'empty')),
         ('Block',)))

def test_while_bigger_body():
    node = parse_single_statement('while(i < n) { x *= 2; i -= 1;}')
    eq_(node.to_tuples(),
        ('While',
         ('BinaryOp', '<', ('Identifier', 'i'), ('Identifier', 'n')),
         ('Block',
          ('Assignment', '*=', ('Identifier', 'x'), ('Constant', 'int', 2)),
          ('Assignment', '-=', ('Identifier', 'i'), ('Constant', 'int', 1)))))
##
## Do-While-Loop
##

def test_do_while_loop_minimal():
    node = parse_single_statement("do {} while(!empty); ")
    eq_(node.to_tuples(),
        ('DoWhile',
         ('UnaryOp', 'not', ('Identifier', 'empty')),
         ('Block',)))

def test_do_while_loop_bigger_body():
    node = parse_single_statement('do { x *= 2; i -= 1;} while(i < n);')
    eq_(node.to_tuples(),
        ('DoWhile',
         ('BinaryOp', '<', ('Identifier', 'i'), ('Identifier', 'n')),
         ('Block',
          ('Assignment', '*=', ('Identifier', 'x'), ('Constant', 'int', 2)),
          ('Assignment', '-=', ('Identifier', 'i'), ('Constant', 'int', 1)))))

##
## For-Loop
##

def test_for_loop_minimal():
    node = parse_single_statement("for(x in primes) {}")
    eq_(node.to_tuples(),
        ('For',
         ('Iterator',
          ('Identifier', 'x'),
          ('Identifier', 'primes')),
         ('Block', )))

def test_for_loop_bigger_body():
    s = """
    // Finds the first vowel in a list of characters
    // and returns it
    for(c in characters) {
        if(c in vowels) {
            return c;
        }
    }

    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('For',
         ('Iterator',
          ('Identifier', 'c'),
          ('Identifier', 'characters')),
         ('Block',
          ('If',
           ('BinaryOp', 'in',
            ('Identifier', 'c'),
            ('Identifier', 'vowels')),
           ('Block', ('Return', ('Identifier', 'c')))))))

def test_for_loop_three_iterators():
    s = """
    for(i in uids, s in street, n in street_number) {
        address[i] := "$street$ $street_number$";
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('For',
         ('IteratorChain', '',
          ('Iterator', ('Identifier', 'i'), ('Identifier', 'uids')),
          ('Iterator', ('Identifier', 's'), ('Identifier', 'street')),
          ('Iterator', ('Identifier', 'n'), ('Identifier', 'street_number'))),
         ('Block',
          ('Assignment', ':=',
           ('Subscription',
            ('Identifier', 'address'),
            ('Identifier', 'i')),
           ('Interpolation',
            ('Constant', 'literal', '$street$ $street_number$'), ('ExprList',))))))
        

##
## Procedures
##

def test_procedure_minimal():
    node = parse_single_statement('procedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'vanilla', ('ParamList',), ('Block',)))

def test_procedure_square():
    node = parse_single_statement('procedure(x) { return x ** x; };')
    eq_(node.to_tuples(),
        ('Procedure', '', 'vanilla',
         ('ParamList', ('Param', 'x')),
         ('Block', ('Return',
           ('BinaryOp', '**',
            ('Identifier', 'x'),
            ('Identifier', 'x'))))))

def test_procedure_max():
    s = """
    max := procedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'max'),
         ('Procedure', '', 'vanilla',
          ('ParamList', ('Param', 'a'), ('Param', 'b')),
          ('Block',
           ('If',
            ('BinaryOp', '>', ('Identifier', 'a'), ('Identifier', 'b')),
            ('Block', ('Return', ('Identifier', 'a'))),
            ('Block', ('Return', ('Identifier', 'b'))))))))

def test_procedure_fac():
    s = """
    fac := procedure(n) {
        i := 1;
        while(n >= 1) {
            i *= n;
            n -= 1;
        }
        return i;
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'fac'),
         ('Procedure', '', 'vanilla',
          ('ParamList', ('Param', 'n')),
          ('Block',
           ('Assignment', ':=', ('Identifier', 'i'), ('Constant', 'int', 1)),
           ('While',
            ('BinaryOp', '>=', ('Identifier', 'n'), ('Constant', 'int', 1)),
            ('Block',
             ('Assignment', '*=', ('Identifier', 'i'), ('Identifier', 'n')),
             ('Assignment', '-=', ('Identifier', 'n'), ('Constant', 'int', 1)))),
            ('Return', ('Identifier', 'i'))))))

##
## Cached procedures
##

def test_cached_procedure_minimal():
    node = parse_single_statement('cachedProcedure() {};')
    eq_(node.to_tuples(), ('Procedure', '', 'cached', ('ParamList',), ('Block',)))

def test_cached_procedure_square():
    node = parse_single_statement('cachedProcedure(x) { return x ** x; };')
    eq_(node.to_tuples(),
        ('Procedure', '', 'cached',
         ('ParamList', ('Param', 'x')),
         ('Block', ('Return',
           ('BinaryOp', '**',
            ('Identifier', 'x'),
            ('Identifier', 'x'))))))

def test_cached_procedure_max():
    s = """
    max := cachedProcedure(a, b) {
        if(a > b) {
            return a;
        } else {
            return b;
        }
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'max'),
         ('Procedure', '', 'cached',
          ('ParamList', ('Param', 'a'), ('Param', 'b')),
          ('Block',
           ('If',
            ('BinaryOp', '>', ('Identifier', 'a'), ('Identifier', 'b')),
            ('Block', ('Return', ('Identifier', 'a'))),
            ('Block', ('Return', ('Identifier', 'b'))))))))

def test_cached_procedure_fac():
    s = """
    fac := cachedProcedure(n) {
        i := 1;
        while(n >= 1) {
            i *= n;
            n -= 1;
        }
        return i;
    };
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Assignment', ':=',
         ('Identifier', 'fac'),
         ('Procedure', '', 'cached',
          ('ParamList', ('Param', 'n')),
          ('Block',
           ('Assignment', ':=', ('Identifier', 'i'), ('Constant', 'int', 1)),
           ('While',
            ('BinaryOp', '>=', ('Identifier', 'n'), ('Constant', 'int', 1)),
            ('Block',
             ('Assignment', '*=', ('Identifier', 'i'), ('Identifier', 'n')),
             ('Assignment', '-=', ('Identifier', 'n'), ('Constant', 'int', 1)))),
            ('Return', ('Identifier', 'i'))))))

##
##
##

def test_class_minimal():
    node = parse_single_statement('class point() {}')
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList', ),
         ('Block', ),
         ('Block', )))

def test_class_with_parameters():
    node = parse_single_statement('class point(x,y) {}')
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList',
          ('Param', 'x'),
          ('Param', 'y')),
         ('Block', ),
         ('Block', )))

def test_class_with_parameters_and_body():
    s = """
    class point(x,y) {
        mX := x;
        mY := y;
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'point'),
         ('ParamList',
          ('Param', 'x'),
          ('Param', 'y')),
         ('Block',
          ('Assignment', ':=',
           ('Identifier', 'mX'),
           ('Identifier', 'x')),
          ('Assignment', ':=',
           ('Identifier', 'mY'),
           ('Identifier', 'y'))),
         ('Block', )))

def test_class_with_static_body():
    s = """
    class universal() {
        static {
            gAnswer := 42;
        }
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Class',
         ('Identifier', 'universal'),
         ('ParamList', ),
         ('Block', ),
         ('Block', 
          ('Assignment', ':=',
           ('Identifier', 'gAnswer'),
           ('Constant', 'int', 42)))))

##
## Try/Catch
##

def test_try_catch_minimal():
    s = """
    try {
    } catch(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catch',
           ('Identifier', 'e'),
           ('Block', )))))

def test_try_catch_usr_minimal():
    s = """
    try {
    } catchUsr(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catchUsr',
           ('Identifier', 'e'),
           ('Block', )))))

def test_try_catch_lng_minimal():
    s = """
    try {
    } catchLng(e) {
    }
    """
    node = parse_single_statement(s)
    eq_(node.to_tuples(),
        ('Try',
         ('Block', ),
         ('Catches',
          ('CatchClause',
           'catchLng',
           ('Identifier', 'e'),
           ('Block', )))))

##
## Backtrack
##

def test_check_minimal():
    node =  parse_single_statement('check {}')
    eq_(node.to_tuples(),
        ('Check',
         ('Block',)))

##
## Single expression
##

def test_check_single_expr():
    node = parser.parse('x!')