# Silly proof-of-concept register VM.

def compile_binary_op(op, ast):
    result = []
    for x in compile(ast[1]):
        result.append(x)
    result.append(PUSH_REG)
    result.append(RES)
    for x in compile(ast[2]):
        result.append(x)
    result.append(ASSIGN_REG_REG)
    result.append(Y)
    result.append(RES)
    result.append(POP)
    result.append(X)
    result.append(op)
    return result

def compile(ast):
    result = []

    if ast[0] == 'CONST':
        result.append(ASSIGN_REG_LIT)
        result.append(RES)
        result.append(ast[1])
    elif ast[0] == 'ADD':
        result += compile_binary_op(ADD, ast)
    elif ast[0] == 'SUB':
        result += compile_binary_op(SUB, ast)
    elif ast[0] == 'MUL':
        result += compile_binary_op(MUL, ast)
    elif ast[0] == 'DIV':
        result += compile_binary_op(DIV, ast)
    elif ast[0] == 'RETURN':
        result.append(RETURN)
    else:
        raise Exception('Cannot compile unknown AST node: {}'.format(ast[0]))

    return result

# opcodes
ASSIGN_REG_LIT = 0x0
ASSIGN_REG_REG = 0x1
ADD = 0x2
SUB = 0x3
MUL = 0x4
DIV = 0x5
SWAP = 0x6
RETURN = 0x7
PUSH_REG = 0x8
POP = 0x9

# register indices
X = 0x0
Y = 0x1
RES = 0x2

registers = [0x0] * 8
stack = []

def reg_name(i):
    if i == X: return 'x'
    if i == Y: return 'x'
    if i == RES: return 'res'

def print_instructions(xs):
    i = 0

    while i < len(xs):
        if xs[i] == ASSIGN_REG_LIT:
            # print('ASSIGN_REG_LIT {} {}'.format(reg_name(xs[i + 1]), xs[i + 2]))
            print('{} <- {}'.format(reg_name(xs[i + 1]), xs[i + 2]))
            i += 3
        elif xs[i] == ASSIGN_REG_REG:
            # print('ASSIGN_REG_REG {} {}'.format(reg_name(xs[i + 1]), reg_name(xs[i + 2])))
            print('{} <- ${}'.format(reg_name(xs[i + 1]), reg_name(xs[i + 2])))
            i += 3
        elif xs[i] == ADD:
            print('add')
            i += 1
        elif xs[i] == SUB:
            print('sub')
            i += 1
        elif xs[i] == MUL:
            print('mul')
            i += 1
        elif xs[i] == DIV:
            print('div')
            i += 1
        elif xs[i] == PUSH_REG:
            print('push ${}'.format(reg_name(xs[i + 1])))
            i += 2
        elif xs[i] == POP:
            print('{} <- pop'.format(reg_name(xs[i + 1])))
            i += 2
        else:
            raise Exception('Cannot print instruction: {}'.format(xs[i]))

def eval(instructions):
    print_instructions(instructions)
    ip = 0
    cont = True
    while ip < len(instructions):
        if instructions[ip] == ASSIGN_REG_LIT:
            r = instructions[ip + 1]
            x = instructions[ip + 2]
            registers[r] = x
            ip += 3
        elif instructions[ip] == ASSIGN_REG_REG:
            r_dst = instructions[ip + 1]
            r_src = instructions[ip + 2]
            registers[r_dst] = registers[r_src]
            ip += 3
        elif instructions[ip] == ADD:
            registers[RES] = registers[X] + registers[Y]
            ip += 1
        elif instructions[ip] == MUL:
            registers[RES] = registers[X] * registers[Y]
            ip += 1
        elif instructions[ip] == SUB:
            registers[RES] = registers[X] - registers[Y]
            ip += 1
        elif instructions[ip] == MUL:
            registers[RES] = registers[X] * registers[Y]
            ip += 1
        elif instructions[ip] == DIV:
            registers[RES] = registers[X] / registers[Y]
            ip += 1
        elif instructions[ip] == SWAP:
            r1 = instructions[ip + 1]
            r2 = instructions[ip + 2]
            registers[r1], registers[r2] = registers[r2], registers[r1]
            ip += 3
        elif instructions[ip] == RETURN:
            ip += 1
            cont = False
            return registers[RES]
        elif instructions[ip] == PUSH_REG:
            src = instructions[ip + 1]
            stack.append(registers[src])
            ip += 2
        elif instructions[ip] == POP:
            dst = instructions[ip + 1]
            registers[dst] = stack.pop()
            ip += 2
        else:
            raise Exception('Cannot eval instruction: {}'.format(instructions[ip]))
    return registers[RES]

def main():
    ast = ['ADD',
           ['MUL',
            ['MUL', ['CONST', 2], ['CONST', 3]],
            ['DIV', ['CONST', 5], ['CONST', 5]]],
           ['ADD',
            ['SUB', ['CONST', 10], ['CONST', 1]],
            ['MUL', ['CONST', 2], ['CONST', 2]]]]

    print('result: {}'.format(eval(compile(ast))))

main()
