#!/usr/bin/env python3

import sys
import pathlib

def check_fmt(fmt):
    assert(len(fmt) == 6)
    assert(fmt[0] == '_' or fmt[0] == 'r')

    for i in range(1, 5):
        c = fmt[i]
        assert(c == 'R' or c == 'F' or c == '_')

    assert(fmt[5] == '_' or fmt[5] == 'I' or fmt[5] == 'D')

def param(fmt, i):
    c = fmt[i + 1]
    return c + str(i)

def gen_label(name, r0, r1, r2, r3):
    return '{}_{}{}{}{}'.format(name, r0, r1, r2, r3)

def gen_enum(name, r0, r1, r2, r3):
    return gen_label(name, r0, r1, r2, r3).upper()

def gen_exec_instance(f, name, r0, r1, r2, r3):
    label = gen_label(name, r0, r1, r2, r3) + ': '
    macro = '{}({}, {}, {}, {}); '.format(name.upper(), r0, r1, r2, r3)
    next = 'NEXT_INSN;'
    f.write(label + macro + next + '\n')

# gen should maybe just generate the string and print_ would print it?
def gen_static_instance(f, name, r0, r1, r2, r3):
    label = gen_label(name, r0, r1, r2, r3)
    enum = gen_enum(name, r0, r1, r2, r3)
    f.write('[' + enum + '] = &&' + label + ',\n')

def gen_params(fmt):
    n = 0
    r = ""
    for p in fmt[1:-1]:
        if p == 'R' or p == 'F':
            # todo: could be useful to have different types for general purpose
            # registers and floating point registers, might avoid bugs that
            # would otherwise be pretty annoying to track down.
            r += ', size_t a' + str(n)
            n += 1

    i = ""
    if fmt[5] == 'I':
        i = ', gbreg_t imm'
    elif fmt[5] == 'D':
        i = ', fbreg_t dimm'

    return '(struct compile_state *cs' + r + i + ')'

def gen_signature(name, fmt):
    ret = 'void '
    if fmt[0] == 'r':
        ret = 'breloc_t '

    n = 'select_' + name
    p = gen_params(fmt)
    return ret + n + p

def gen_cond(r, i):
    if r == '':
        return '1'

    return 'a' + str(i) + ' == ' + r[1:]

def gen_select_instance(f, name, r0, r1, r2, r3):
    c0 = gen_cond(r0, 0)
    c1 = gen_cond(r1, 1)
    c2 = gen_cond(r2, 2)
    c3 = gen_cond(r3, 3)
    cond = c0 + ' && ' + c1 + ' && ' + c2 + ' && ' + c3
    action = 'label = ' + gen_enum(name, r0, r1, r2, r3) + ';'
    f.write('else if (' + cond + ') {' + action + '}\n')

def gen_enum_instance(f, name, r0, r1, r2, r3):
    enum = gen_enum(name, r0, r1, r2, r3)
    f.write(enum + ',\n')

class BCGen:
    def __init__(self, r = 3, f = 3, head=''):
        self.r = r
        self.f = f

        # make sure gen dir exists
        pathlib.Path('gen').mkdir(exist_ok=True)

        # clear out files
        with open('gen/body.inc', 'w') as inc:
            pass

        with open('gen/macro.inc', 'w') as inc:
            pass

        with open('gen/static.inc', 'w') as inc:
            pass

        with open('gen/select.inc', 'w') as inc:
            pass

        with open('gen/enum.inc', 'w') as inc:
            pass

        with open('gen/select.h', 'w') as h:
            pass

        with open('gen/head.inc', 'w') as inc:
            inc.write(head)

        with open('gen/regs.inc', 'w') as inc:
            gregs = ['gbreg_t r' + str(i) + ' = 0;\n' for i in range(r)]
            fregs = ['fbreg_t f' + str(i) + ' = 0;\n' for i in range(f)]

            for g in gregs:
                inc.write(g);

            for f in fregs:
                inc.write(f)

    def gen_macro(self, name, fmt, body):
        with open('gen/macro.inc', 'a') as f:
            name = name.upper()

            p0 = param(fmt, 0)
            p1 = param(fmt, 1)
            p2 = param(fmt, 2)
            p3 = param(fmt, 3)

            macro = '#define {}({}, {}, {}, {}) {}\n'.format(
                    name, p0, p1, p2, p3, body)

            f.write(macro)

    def place(self, fmt, i):
        c = fmt[i + 1]
        if c == 'R':
            return ['r' + str(i) for i in range(self.r)]

        if c == 'F':
            return ['f' + str(i) for i in range(self.f)]

        return [""]

    def gen_body(self, name, fmt):
        with open('gen/body.inc', 'a') as f:
            for r0 in self.place(fmt, 0):
                for r1 in self.place(fmt, 1):
                    for r2 in self.place(fmt, 2):
                        for r3 in self.place(fmt, 3):
                            gen_exec_instance(f, name, r0, r1, r2, r3)

    def gen_static(self, name, fmt):
        with open('gen/static.inc', 'a') as f:
            for r0 in self.place(fmt, 0):
                for r1 in self.place(fmt, 1):
                    for r2 in self.place(fmt, 2):
                        for r3 in self.place(fmt, 3):
                            gen_static_instance(f, name, r0, r1, r2, r3)

    def gen_select(self, name, fmt):
        with open('gen/select.h', 'a') as f:
            sign = gen_signature(name, fmt)
            f.write(sign + ';\n')

        with open('gen/select.inc', 'a') as f:
            sign = gen_signature(name, fmt)
            f.write(sign + '\n')
            f.write('{\nenum bcode_insn label = 0;\n')
            if fmt[0] == 'r':
                f.write('breloc_t reloc = RELOC;\n')
            # kind of ugly but simplifies gen_select_instance a fair bit
            f.write('if (0) {}\n')

            for r0 in self.place(fmt, 0):
                for r1 in self.place(fmt, 1):
                    for r2 in self.place(fmt, 2):
                        for r3 in self.place(fmt, 3):
                            gen_select_instance(f, name, r0, r1, r2, r3)

            if fmt[5] == 'I':
                f.write('PUSH_IMM_OP(label, imm);\n')
            elif fmt[5] == 'D':
                f.write('PUSH_DIMM_OP(label, dimm);\n')
            else:
                f.write('PUSH_OP(label);\n')

            if fmt[0] == 'r':
                f.write('return reloc;\n')

            f.write('}\n')

    def gen_enum(self, name, fmt):
        with open('gen/enum.inc', 'a') as f:
            for r0 in self.place(fmt, 0):
                for r1 in self.place(fmt, 1):
                    for r2 in self.place(fmt, 2):
                        for r3 in self.place(fmt, 3):
                            gen_enum_instance(f, name, r0, r1, r2, r3)


    def rule(self, name, fmt, body):
        check_fmt(fmt)
        self.gen_macro(name, fmt, body)
        self.gen_static(name, fmt)
        self.gen_select(name, fmt)
        self.gen_body(name, fmt)
        self.gen_enum(name, fmt)

assert(len(sys.argv) == 2)
exec(open(sys.argv[1]).read())
