def test_return_statement(vm):
    n_args = 3
    frame_offset = 512
    current_sp = frame_offset + 100
    arg1 = 111
    arg2 = 222
    arg3 = 333
    caller_arg = 254
    caller_lcl = caller_arg + 5
    caller_this = 1234
    caller_that = 2345
    return_addr = 6000
    return_value = 42

    vm.ram[frame_offset + 0] = arg1
    vm.ram[frame_offset + 1] = arg2
    vm.ram[frame_offset + 2] = arg3
    vm.ram[frame_offset + 3] = return_addr
    vm.ram[frame_offset + 4] = caller_lcl
    vm.ram[frame_offset + 5] = caller_arg
    vm.ram[frame_offset + 6] = caller_this
    vm.ram[frame_offset + 7] = caller_that
    vm.ram[current_sp - 1] = return_value

    vm.ram[vm.symbols['LCL']] = frame_offset + n_args + 5
    vm.ram[vm.symbols['ARG']] = frame_offset
    vm.ram[vm.symbols['THIS']] = 5555
    vm.ram[vm.symbols['THAT']] = 6666
    vm.ram[vm.symbols['SP']] = current_sp

    g = CodeGenerator()
    g.translate(commands=[
            (0, 'function', 'fn', 0),
        ],
        filename='<input>',
    )
    code = g.translate(commands=[
            (0, 'return'),
        ],
        filename='<input>',
    )
    vm.execute(code, 100)

    assert vm.ram == {
        vm.symbols['SP']: frame_offset + 1,
        vm.symbols['LCL']: caller_lcl,
        vm.symbols['ARG']: caller_arg,
        vm.symbols['THIS']: caller_this,
        vm.symbols['THAT']: caller_that,

        frame_offset + 0: return_value,
        frame_offset + 1: arg2,
        frame_offset + 2: arg3,
        frame_offset + 3: return_addr,
        frame_offset + 4: caller_lcl,
        frame_offset + 5: caller_arg,
        frame_offset + 6: caller_this,
        frame_offset + 7: caller_that,
        current_sp - 1: return_value,
    }
def test_function_calls(vm):
    g = CodeGenerator()
    code = g.translate(commands=[
            (0,  'call', 'main', 0),
            (1,  'goto', 'end'),

            (2,  'function', 'main', 0),
            (3,  'push', 'constant', 10),
            (4,  'push', 'constant', 20),
            (5,  'call', 'double-sum', 2),
            (6,  'return'),

            (7,  'function', 'double-sum', 1),
            (8,  'push', 'argument', 0),
            (9,  'push', 'argument', 1),
            (10, 'add'),
            (11, 'pop', 'local', 0),
            (12, 'push', 'local', 0),
            (13, 'push', 'local', 0),
            (14, 'add'),
            (15, 'return'),

            (16, 'label', 'end'),
        ],
        filename='<input>',
    )
    vm.execute(code, 500)

    assert vm.ram[vm.symbols['SP']] == vm.stack_offset + 1
    assert vm.ram[vm.stack_offset] == 2 * (10 + 20)
def test_label(vm):
    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'label', 'name1'),
            (1, 'label', 'name2'),
        ],
        filename='<input>',
    )
    vm.execute(code, 100)

    assert vm.symbols['<input>$name1'] == 0
    assert vm.symbols['<input>$name2'] == 1
def test_not(vm):
    vm.ram[vm.stack_offset + 0] = 22
    vm.ram[vm.symbols['SP']] += 1

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'not'),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 1,
        vm.stack_offset + 0: ~22,
    }
def test_goto(vm):
    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'constant', 10),
            (1, 'goto', 'end'),
            (2, 'push', 'constant', 20),
            (3, 'label', 'end'),
        ],
        filename='<input>',
    )
    vm.execute(code, 100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 1,
        vm.stack_offset + 0: 10,
    }
def test_call_statement(vm):
    caller_arg = 254
    caller_lcl = caller_arg + 5
    caller_this = 1234
    caller_that = 2345
    caller_sp = 512

    n_args = 3
    arg1 = 111
    arg2 = 222
    arg3 = 333

    vm.symbols['fn'] = 8192
    vm.ram[vm.symbols['SP']] = caller_sp
    vm.ram[vm.symbols['LCL']] = caller_lcl
    vm.ram[vm.symbols['ARG']] = caller_arg
    vm.ram[vm.symbols['THIS']] = caller_this
    vm.ram[vm.symbols['THAT']] = caller_that

    vm.ram[caller_sp - 3] = arg1
    vm.ram[caller_sp - 2] = arg2
    vm.ram[caller_sp - 1] = arg3

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'call', 'fn', n_args),
        ],
        filename='<input>',
    )
    vm.execute(code, 100)

    assert vm.ram == {
        vm.symbols['SP']: caller_sp + 5,
        vm.symbols['LCL']: caller_sp + 5,
        vm.symbols['ARG']: caller_sp - n_args,
        vm.symbols['THIS']: caller_this,
        vm.symbols['THAT']: caller_that,

        caller_sp - 3: arg1,
        caller_sp - 2: arg2,
        caller_sp - 1: arg3,
        caller_sp + 0: vm.symbols['fn$ret.1'],
        caller_sp + 1: caller_lcl,
        caller_sp + 2: caller_arg,
        caller_sp + 3: caller_this,
        caller_sp + 4: caller_that,
    }
def test_push_constant(vm):
    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'constant', 1),
            (1, 'push', 'constant', 10),
            (2, 'push', 'constant', 100),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 3,
        vm.stack_offset + 0: 1,
        vm.stack_offset + 1: 10,
        vm.stack_offset + 2: 100,
    }
def test_program(vm):
    g = CodeGenerator()
    code = g.gen_initializer()
    code += '\n' + g.translate(commands=[
            (0, 'goto', 'end'),

            (1, 'function', 'Sys.init', 0),
            (2, 'push', 'constant', 0),
            (3, 'return'),

            (4, 'label', 'end')
        ],
        filename='<input>',
    )
    vm.execute(code, 500)

    assert vm.ram[vm.symbols['SP']] == vm.stack_offset + 1
def test_or(vm):
    vm.ram[vm.stack_offset + 0] = 22
    vm.ram[vm.stack_offset + 1] = 21
    vm.ram[vm.symbols['SP']] += 2

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'or'),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 1,
        vm.stack_offset + 0: 22|21,
        vm.stack_offset + 1: 21,
    }
def test_eq(vm, a, b, expected):
    vm.ram[vm.stack_offset + 0] = a
    vm.ram[vm.stack_offset + 1] = b
    vm.ram[vm.symbols['SP']] += 2

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'eq'),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 1,
        vm.stack_offset + 0: expected,
        vm.stack_offset + 1: b,
    }
def test_push_temp(vm):
    vm.ram[vm.symbols['R5'] + 1] = 33
    vm.ram[vm.symbols['R5'] + 3] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'temp', 1),
            (1, 'push', 'temp', 3),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        vm.symbols['R5'] + 1: 33,
        vm.symbols['R5'] + 3: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_push_pointer(vm):
    vm.ram[vm.symbols['R3']] = 33
    vm.ram[vm.symbols['R4']] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'pointer', 0),
            (1, 'push', 'pointer', 1),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        vm.symbols['R3']: 33,
        vm.symbols['R4']: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_function_statement(vm):
    local_segment_size = 2
    initial_sp = 512
    vm.ram[vm.symbols['SP']] = initial_sp
    vm.ram[vm.symbols['LCL']] = vm.ram[vm.symbols['SP']]

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'function', 'fn', local_segment_size),
        ],
        filename='<input>',
    )
    vm.execute(code, 100)

    assert vm.symbols['fn'] == 0
    assert vm.ram == {
        vm.symbols['SP']: initial_sp + local_segment_size,
        vm.symbols['LCL']: initial_sp,
        initial_sp + 0: 0,
        initial_sp + 1: 0,
    }
def test_label_within_function(vm):
    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'call', 'fn', 0),
            (1, 'goto', 'end'),
            (2, 'function', 'fn', 0),
            (5, 'push', 'constant', 0),
            (3, 'goto', 'end'),
            (4, 'label', 'label1'),
            (6, 'return'),
            (4, 'label', 'end'),
            (6, 'return'),
            (7, 'label', 'end'),
        ],
        filename='<input>',
    )
    vm.execute(code, 500)

    assert '<input>$end' in vm.symbols
    assert '<input>$fn$label1' in vm.symbols
    assert '<input>$fn$end' in vm.symbols
def test_push_static(vm):
    static_offset = vm.symbols['R15'] + 1
    vm.ram[static_offset + 0] = 33
    vm.ram[static_offset + 1] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'static', 1),
            (1, 'push', 'static', 10),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        static_offset + 0: 33,
        static_offset + 1: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_pop_temp(vm):
    temp_offset = vm.symbols[kw.special_segmnets['temp']]
    vm.ram[vm.stack_offset + 0] = 33
    vm.ram[vm.stack_offset + 1] = 22
    vm.ram[vm.symbols['SP']] += 2

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'pop', 'temp', 1),
            (1, 'pop', 'temp', 3),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset,
        temp_offset + 1: 22,
        temp_offset + 3: 33,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_push_this(vm):
    this_offset = 512
    vm.ram[vm.symbols['THIS']] = this_offset
    vm.ram[this_offset + 1] = 33
    vm.ram[this_offset + 10] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'this', 1),
            (1, 'push', 'this', 10),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        vm.symbols['THIS']: this_offset,
        this_offset + 1: 33,
        this_offset + 10: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_push_argument(vm):
    argument_offset = 512
    vm.ram[vm.symbols['ARG']] = argument_offset
    vm.ram[argument_offset + 1] = 33
    vm.ram[argument_offset + 10] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'argument', 1),
            (1, 'push', 'argument', 10),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        vm.symbols['ARG']: argument_offset,
        argument_offset + 1: 33,
        argument_offset + 10: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_push_local(vm):
    local_offset = 512
    vm.ram[vm.symbols['LCL']] = local_offset
    vm.ram[local_offset + 1] = 33
    vm.ram[local_offset + 10] = 22

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'push', 'local', 1),
            (1, 'push', 'local', 10),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset + 2,
        vm.symbols['LCL']: local_offset,
        local_offset + 1: 33,
        local_offset + 10: 22,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }
def test_pop_that(vm):
    that_offset = 512
    vm.ram[vm.symbols['THAT']] = that_offset
    vm.ram[vm.stack_offset + 0] = 33
    vm.ram[vm.stack_offset + 1] = 22
    vm.ram[vm.symbols['SP']] += 2

    g = CodeGenerator()
    code = g.translate(commands=[
            (0, 'pop', 'that', 1),
            (1, 'pop', 'that', 10),
        ],
        filename='<input>',
    )
    vm.execute(code, max_steps=100)

    assert vm.ram == {
        vm.symbols['SP']: vm.stack_offset,
        vm.symbols['THAT']: that_offset,
        that_offset + 1: 22,
        that_offset + 10: 33,
        vm.stack_offset + 0: 33,
        vm.stack_offset + 1: 22,
    }