#!/usr/bin/python
# license:BSD-3-Clause
# copyright-holders:Olivier Galibert
from __future__ import print_function

USAGE = """
Usage:
%s h8.lst <mode> <type> h8.inc (mode = s/d, type = o/h/s20/s26)
"""
import sys

def name_to_type(name):
    if name == "o":
        return 0
    if name == "h":
        return 1
    if name == "s20":
        return 2
    if name == "s26":
        return 3
    if name == "g":
        return 4
    sys.stderr.write("Unknown chip type name %s\n" % name)
    sys.exit(1)

def type_to_device(dtype, mode):
    if mode == 's':
        if dtype == 0:
            return "h8_device"
        if dtype == 1:
            return "h8h_device"
        if dtype == 2:
            return "h8s2000_device"
        if dtype == 3:
            return "h8s2600_device"
        return "gt913_device"
    else:
        if dtype == 0:
            return "h8_disassembler"
        if dtype == 1:
            return "h8h_disassembler"
        if dtype == 2:
            return "h8s2000_disassembler"
        if dtype == 3:
            return "h8s2600_disassembler"
        return "gt913_disassembler"

def hexsplit(str):
    res = []
    for i in range(0, len(str), 2):
        res.append(int(str[i:i+2], 16))
    return res
        
def has_memory(ins):
    for s in ["read", "write"]:
        if s in ins:
            return True
    return False

def has_eat(ins):
    if "eat-all-cycles" in ins:
        return True
    return False

def save_full_one(f, t, name, source):
    print("void %s::%s_full()" % (t, name), file=f)
    print("{", file=f)
    substate = 1
    for line in source:
        if has_memory(line):
            print(line, file=f)
            print("\tif(m_icount <= m_bcount) {", file=f)
            print("\t\tif(access_to_be_redone()) {", file=f)
            print("\t\t\tm_icount++;", file=f)
            print("\t\t\tm_inst_substate = %d;" % substate, file=f)
            print("\t\t} else", file=f)
            print("\t\t\tm_inst_substate = %d;" % (substate+1), file=f)
            print("\t\treturn;", file=f)
            print("\t}", file=f)
            substate += 2
        elif has_eat(line):
            print("\tif(m_icount) { m_icount = m_bcount; } m_inst_substate = %d; return;" % substate, file=f)
            substate += 1
        else:
            print(line, file=f)
    print("}", file=f)
    print("", file=f)

def save_partial_one(f, t, name, source):
    print("void %s::%s_partial()" % (t, name), file=f)
    print("{", file=f)
    print("switch(m_inst_substate) {", file=f)
    print("case 0:", file=f)
    substate = 1
    for line in source:
        if has_memory(line):
            print("\t[[fallthrough]];", file=f)
            print("case %d:;" % substate, file=f)
            print(line, file=f)
            print("\tif(m_icount <= m_bcount) {", file=f)
            print("\t\tif(access_to_be_redone()) {", file=f)
            print("\t\t\tm_icount++;", file=f)
            print("\t\t\tm_inst_substate = %d;" % substate, file=f)
            print("\t\t} else", file=f)
            print("\t\t\tm_inst_substate = %d;" % (substate+1), file=f)
            print("\t\treturn;", file=f)
            print("\t}", file=f)
            print("\t[[fallthrough]];", file=f)
            print("case %d:;" % (substate+1), file=f)
            substate += 2
        elif has_eat(line):
            print("\tif(m_icount) { m_icount = m_bcount; } m_inst_substate = %d; return;" % substate, file=f)
            print("case %d:;" % substate, file=f)
            substate += 1
        else:
            print(line, file=f)
    print("\tbreak;", file=f)
    print("}", file=f)
    print("\tm_inst_substate = 0;", file=f)
    print("}", file=f)
    print("", file=f)

class Hash:
    def __init__(self, premask):
        self.mask = 0x00
        self.enabled = False
        self.premask = premask
        self.d = {}

    def get(self, val, premask):
        if val in self.d:
            h = self.d[val]
            if h.premask != premask:
                sys.stderr.write("Premask conflict\n")
                sys.exit(1)
            return h
        h = Hash(premask)
        self.d[val] = h
        return h
    
    def set(self, val, opc):
        if val in self.d:
            sys.stderr.write("Collision on %s\n" % opc.description())
            sys.exit(1)
        self.d[val] = opc

class Opcode:
    def __init__(self, val, mask, skip, name, am1, am2, otype, dtype):
        self.name = name
        self.val = hexsplit(val)
        self.mask = hexsplit(mask)
        self.skip = int(skip)
        self.am1 = am1
        self.am2 = am2
        self.source = []
        self.otype = otype
        self.enabled = otype == -1 or (otype == 0 and dtype == 0) or (otype != 0 and dtype >= otype)
        self.needed = self.enabled and (otype == dtype or (otype == -1 and dtype == 0))
        if dtype == 0 and (am1 == "r16l" or am2 == "r16l"):
            self.mask[len(self.mask) - 1] |= 0x08
        if dtype == 0 and (am1 == "r16h" or am2 == "r16h"):
            self.mask[len(self.mask) - 1] |= 0x80
        extra_words = 0
        if (am1 == "abs16" or am2 == "abs16" or am1 == "abs16e" or am1 == "abs22e" or am1 == "abs24e") and self.skip == 0:
            extra_words += 1
        if (am1 == "abs32" or am2 == "abs32") and self.skip == 0:
            extra_words += 2
        if am1 == "imm16" or am1 == "rel16" or am1 == "r16d16h" or am2 == "r16d16h" or am1 == "r32d16h" or am2 == "r32d16h":
            extra_words += 1
        if am1 == "imm32" or am1 == "r32d32hh" or am2 == "r32d32hh":
            extra_words += 2
        self.extra_words = extra_words
        base_offset = len(self.val)/2 + self.skip
        for i in range(0, extra_words):
            self.source.append("\tm_IR[%d] = read16i(m_PC);" % (i+base_offset))
            self.source.append("\tm_PC += 2;")

    def description(self):
        return "%s %s %s" % (self.name, self.am1, self.am2)
    
    def add_source_line(self, line):
        self.source.append(line)
    
    def is_dispatch(self):
        return False
    
    def function_name(self):
        n = self.name.replace(".", "_")
        if self.am1 != "-":
            n = n + "_" + self.am1
        if self.am2 != "-":
            n = n + "_" + self.am2
        return n

    def save_dasm(self, f):
        if len(self.mask) == 2:
            mask  = (self.mask[0] << 8) | self.mask[1]
            val   = (self.val[0]  << 8) | self.val[1]
            mask2 = 0
            val2  = 0
            slot  = 0
        elif len(self.mask) == 4:
            mask  = (self.mask[0] << 24) | (self.mask[1] << 16) | (self.mask[2] << 8) | self.mask[3]
            val   = (self.val[0]  << 24) | (self.val[1]  << 16) | (self.val[2]  << 8) | self.val[3]
            mask2 = 0
            val2  = 0
            slot = self.skip + 1
        else:
            mask  = (self.mask[2] << 24) | (self.mask[3] << 16) | (self.mask[4] << 8) | self.mask[5]
            val   = (self.val[2]  << 24) | (self.val[3]  << 16) | (self.val[4]  << 8) | self.val[5]
            mask2 = (self.mask[0] << 8)  | self.mask[1]
            val2  = (self.val[0]  << 8)  | self.val[1]
            slot = 4
        
        size = len(self.val) + 2*self.skip + 2*self.extra_words
        
        if self.name == "jsr" or self.name == "bsr":
            flags = "%d | STEP_OVER" % size
        elif self.name == "rts" or self.name == "rte":
            flags = "%d | STEP_OUT" % size
        elif self.am1 == "rel8" and self.name != "bt" and self.name != "bf":
            flags = "%d | STEP_COND" % size
        else:
            flags = "%d" % size
        
        print("\t{ %d, 0x%08x, 0x%08x, 0x%04x, 0x%04x, \"%s\", DASM_%s, DASM_%s, %s }, // %s" % ( slot, val, mask, val2, mask2, self.name, self.am1 if self.am1 != "-" else "none", self.am2 if self.am2 != "-" else "none", flags, "needed" if self.needed else "inherited"), file=f)

class Special:
    def __init__(self, val, name, otype, dtype):
        self.name = name
        self.val = int(val, 16)
        self.enabled = otype == -1 or (otype == 0 and dtype == 0) or (otype != 0 and dtype >= otype)
        self.needed = otype == dtype or (otype == -1 and dtype == 0)
        self.source = []
    
    def add_source_line(self, line):
        self.source.append(line)

class Macro:
    def __init__(self, tokens):
        self.name = tokens[1]
        self.params = []
        for i in range(2, len(tokens)):
            self.params.append(tokens[i])
        self.source = []

    def add_source_line(self, line):
        self.source.append(line)

    def apply(self, target, tokens):
        values = []
        if len(self.params) > 1:
            for i in range(0, len(self.params)-1):
                values.append(tokens[i+1])
        lval = ""
        for i in range(len(self.params)-1, len(tokens)-1):
            if lval != "":
                lval += " "
            lval = lval + tokens[i+1]
        values.append(lval)
        for i in range(0, len(self.source)):
            line = self.source[i]
            for j in range(0, len(self.params)):
                line = line.replace(self.params[j], values[j])
            target.add_source_line(line)

class DispatchStep:
    def __init__(self, id, pos, opc):
        self.id = id
        self.pos = pos
        self.name = ""
        self.enabled = False
        self.mask = opc.mask[pos-1]
        for i in range(0, pos):
            self.name += "%02x" % opc.val[i]
        if pos == 2:
            self.skip = opc.skip
        else:
            self.skip = 0

    def is_dispatch(self):
        return True

    def source(self):
        start = self.pos // 2
        end = start + self.skip
        s = []
        for i in range(start, end+1):
            s.append("\tm_IR[%d] = read16i(m_PC);" % i)
            s.append("\tm_PC += 2;")
        s.append("\tm_inst_state = 0x%x0000 | m_IR[%d];" % (self.id, end))
        return s


class OpcodeList:
    def __init__(self, fname, dtype):
        self.opcode_info = []
        self.dispatch_info = []
        self.states_info = []
        self.dispatch = {}
        self.macros = {}
        try:
            f = open(fname, "r")
        except Exception:
            err = sys.exc_info()[1]
            sys.stderr.write("Cannot read opcodes file %s [%s]\n" % (fname, err))
            sys.exit(1)
        
        inf = None
        for line in f:
            if line.startswith("#"):
                continue
            line = line.rstrip()
            if not line:
                continue
            if line.startswith(" ") or line.startswith("\t"):
                if inf is not None:
                    # append instruction to last opcode, maybe expand a macro
                    tokens = line.split()
                    if tokens[0] in self.macros:
                        self.macros[tokens[0]].apply(inf, tokens)
                    else:
                        inf.add_source_line(line)
            else:
                # New opcode
                tokens = line.split()
                if tokens[0] == "macro":
                    inf = Macro(tokens)
                    self.macros[inf.name] = inf
                elif len(tokens) == 2 or len(tokens) == 3:
                    if len(tokens) >= 3:
                        otype = name_to_type(tokens[2])
                    else:
                        otype = -1
                    inf = Special(tokens[0], tokens[1], otype, dtype)
                    self.states_info.append(inf)
                else:
                    if len(tokens) >= 7:
                        otype = name_to_type(tokens[6])
                    else:
                        otype = -1
                    if otype == -1 or dtype == 0 or (otype != 0 and dtype != 0):
                        inf = Opcode(tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5], otype, dtype)
                        self.opcode_info.append(inf)
                    else:
                        inf = None

    def get(self, i):
        if i in self.dispatch:
            return self.dispatch[i]
        h = Hash(0)
        self.dispatch[i] = h
        return h
    
    def build_dispatch(self):
        for opc in self.opcode_info:
            for i in range(0, len(opc.val)):
                v = opc.val[i]
                if i == 0:
                    h = self.get(0)
                if opc.enabled:
                    h.mask = h.mask | opc.mask[i]
                    h.enabled = True
                if (i & 1) == 0:
                    h = h.get(v, opc.mask[i])
                elif i == len(opc.val)-1:
                    if opc.enabled:
                        h.set(v, opc)
                else:
                    if v in h.d:
                        d = h.d[v]
                        if not d.is_dispatch():
                            sys.stderr.write("Collision on %s\n" % opc.description())
                            sys.exit(1)
                        if opc.enabled:
                            d.enabled = True
                        h = self.get(d.id)
                    else:
                        d = DispatchStep(len(self.dispatch_info)+2, i+1, opc)
                        self.dispatch_info.append(d)
                        if opc.enabled:
                            d.enabled = True
                        h.set(v, d)
                        h = self.get(d.id)
    
    def save_dasm(self, f, dname):
        print("const %s::disasm_entry %s::disasm_entries[] = {" % (dname, dname), file=f)
        for opc in self.opcode_info:
            if opc.enabled:
                opc.save_dasm(f)
        print("\t{ 0, 0, 0, 0, 0, \"illegal\", 0, 0, 2 },", file=f)
        print("};", file=f)
        print("", file=f)
    
    def save_opcodes(self, f, t):
        for opc in self.opcode_info:
            if opc.needed:
                save_full_one(f, t, opc.function_name(), opc.source)
                save_partial_one(f, t, opc.function_name(), opc.source)
        
        for sta in self.states_info:
            if sta.needed:
                save_full_one(f, t, "state_" + sta.name, sta.source)
                save_partial_one(f, t, "state_" + sta.name, sta.source)
    
    def save_dispatch(self, f, t):
        for dsp in self.dispatch_info:
            save_full_one(f, t, "dispatch_" + dsp.name, dsp.source())
            save_partial_one(f, t, "dispatch_" + dsp.name, dsp.source())
    
    def save_exec(self, f, t, dtype, v):
        print("void %s::do_exec_%s()" % (t, v), file=f)
        print("{", file=f)
        print("\tswitch(m_inst_state >> 16) {", file=f)
        for i in range(0, len(self.dispatch_info)+2):
            if i == 1:
                print("\tcase 0x01: {", file=f)
                print("\t\tswitch(m_inst_state & 0xffff) {", file=f)
                for sta in self.states_info:
                    if sta.enabled:
                        print("\t\tcase 0x%02x: state_%s_%s(); break;" % (sta.val & 0xffff, sta.name, v), file=f)
                print("\t\t}", file=f)
                print("\t\tbreak;", file=f)
                print("\t}", file=f)
            else:
                if i == 0 or self.dispatch_info[i-2].enabled:
                    print("\tcase 0x%02x: {" % i, file=f)
                    h = self.get(i)
                    print("\t\tswitch((m_inst_state >> 8) & 0x%02x) {" % h.mask, file=f)
                    for val, h2 in sorted(h.d.items()):
                        if h2.enabled:
                            fmask = h2.premask | (h.mask ^ 0xff)
                            c = ""
                            s = 0
                            while s < 0x100:
                                c += "case 0x%02x: " % (val | s)
                                s += 1
                                while s & fmask:
                                    s += s & fmask
                            print("\t\t%s{" % c, file=f)
                            if h2.mask == 0x00:
                                n = h2.d[0]
                                if n.is_dispatch():
                                    print("\t\t\tdispatch_%s_%s();" % (n.name, v), file=f)
                                else:
                                    print("\t\t\t%s_%s();" % (n.function_name(), v), file=f)
                                print("\t\t\tbreak;", file=f)
                            else:
                                print("\t\t\tswitch(m_inst_state & 0x%02x) {" % h2.mask, file=f)
                                if i == 0:
                                    mpos = 1
                                else:
                                    mpos = self.dispatch_info[i-2].pos + 1
                                for val2, n in sorted(h2.d.items()):
                                    if n.enabled:
                                        fmask = h2.mask ^ 0xff
                                        if n.is_dispatch():
                                            fmask = fmask | n.mask
                                        else:
                                            fmask = fmask | n.mask[mpos]
                                        c = ""
                                        s = 0
                                        while s < 0x100:
                                            c += "case 0x%02x: " % (val2 | s)
                                            s += 1
                                            while s & fmask:
                                                s += s & fmask
                                        if n.is_dispatch():
                                            print("\t\t\t%sdispatch_%s_%s(); break;" % (c, n.name, v), file=f)
                                        else:
                                            print("\t\t\t%s%s_%s(); break;" % (c, n.function_name(), v), file=f)
                                print("\t\t\tdefault: illegal(); break;", file=f)
                                print("\t\t\t}", file=f)
                                print("\t\t\tbreak;", file=f)
                            print("\t\t}", file=f)
                    print("\t\tdefault: illegal(); break;", file=f)
                    print("\t\t}", file=f)
                    print("\t\tbreak;", file=f)
                    print("\t}", file=f)
        print("\t}", file=f)
        print("}", file=f)

def main(argv):
    if len(argv) != 5:
        print(USAGE % argv[0])
        return 1

    mode  = argv[2]
    dtype = name_to_type(argv[3])
    dname = type_to_device(dtype, mode)
    opcodes = OpcodeList(argv[1], dtype)
    
    try:
        f = open(argv[4], "w")
    except Exception:
        err = sys.exc_info()[1]
        sys.stderr.write("cannot write file %s [%s]\n" % (argv[4], err))
        sys.exit(1)

    if mode == 's':
        opcodes.build_dispatch()
        opcodes.save_opcodes(f, dname)
        if dtype == 0 or dtype == 4:
            opcodes.save_dispatch(f, dname)
        opcodes.save_exec(f, dname, dtype, "full")
        opcodes.save_exec(f, dname, dtype, "partial")
    else:
        opcodes.save_dasm(f, dname)
    f.close()

# ======================================================================
if __name__ == "__main__":
    sys.exit(main(sys.argv))

