#!/usr/bin/python
import sys

verbose = 0

prologue = '"""' + """Opcodes file.

    Tables for the assembly of 6502-family instructions, mapping
    opcodes and addressing modes to binary instructions.""" + '"""' + """

# Copyright 2002-2012 Michael C. Martin and additional contributors.
# You may use, modify, and distribute this file under the MIT
# license: See README for details.

# DO NOT EDIT THIS FILE DIRECTLY.
# This file was automatically generated by gensets.py based on the
# the tables in tools/opcodes. Edit those tables, not these.

# Names of addressing modes
modes = ["Implied",
         "Immediate",
         "ImmediateLong",
         "Zero Page",
         "Zero Page, X",
         "Zero Page, Y",
         "Absolute",
         "Absolute, X",
         "Absolute, Y",
         "(Absolute)",
         "(Absolute, X)",
         "(Absolute), Y",
         "(Absolute), Z",
         "(Zero Page)",
         "(Zero Page, X)",
         "(Zero Page), Y",
         "(Zero Page, SP), Y",
         "(Zero Page), Z",
         "Relative",
         "RelativeLong",
         "Zero Page, Relative"]


# Lengths of the argument
lengths = [0, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2]
"""

# These values should match the ones in the prologue string.
modes = ["Implied",
         "Immediate",
         "Immediate.W",
         "Zero Page",
         "Zero Page, X",
         "Zero Page, Y",
         "Absolute",
         "Absolute, X",
         "Absolute, Y",
         "(Absolute)",
         "(Absolute, X)",
         "(Absolute), Y",
         "(Absolute), Z",
         "(Zero Page)",
         "(Zero Page, X)",
         "(Zero Page), Y",
         "(Zero Page, SP), Y",
         "(Zero Page), Z",
         "Relative",
         "RelativeLong",
         "Zero Page, Relative"]


flatmodes = [x.lower() for x in modes]


# WARNING: This decommenter assumes that # never appears anywhere else
# in a line.
def decomment(l):
    if '#' in l:
        l = l[:l.index('#')]
    return l.strip()


def decomment_readlines(fname):
    result = [decomment(x) for x in file(fname).readlines()]
    return [x for x in result if len(x) > 0]


def parse_chipset_file(fname):
    result = [None] * 256
    ls = [[x.strip() for x in y]
          for y in [z.split(':', 1) for z in decomment_readlines(fname)]]
    for l in ls:
        if len(l) == 2:
            try:
                op = int(l[0], 16)
                syns = l[1].split(';')
                for s in syns:
                    s_p = s.split('-')
                    if len(s_p) == 2:
                        mnem = s_p[0].lower().strip()
                        mode = s_p[1].lower().strip()
                        if mode in flatmodes:
                            if result[op] is None:
                                result[op] = []
                            result[op].append((mnem, flatmodes.index(mode)))
                        else:
                            print "Unknown mode '%s'" % s_p[1]
            except ValueError:
                print "Illegal opcode '%s'" % l[0]
    return result


def collate_chipset_map(cs_list, base):
    result = {}
    for (opcode, insts) in zip(range(256), cs_list):
        if insts is not None:
            for inst in insts:
                (mnem, mode) = inst
                if mnem not in result:
                    result[mnem] = [None] * len(modes)
                if result[mnem][mode] is not None:
                    print "Warning: Reassigning %s - %s" % (mnem, modes[mode])
                result[mnem][mode] = opcode
    if base is not None:
        todel = []
        for x in result:
            if x in base:
                if result[x] == base[x]:
                    todel.append(x)
                elif verbose != 0:
                    print "# Opcode %s changed" % x
            elif verbose != 0:
                print "# Opcode %s added" % x
        for x in todel:
            del result[x]
    return result


def mapval(x):
    if x is None:
        return "None"
    else:
        return "0x%02X" % x


def dump_map(m, prologue=''):
    mnems = m.keys()
    mnems.sort()
    for mnem in mnems:
        codes = [mapval(x) for x in m[mnem]]
        print "%s'%s': [%s,\n%s     %s]," % (prologue, mnem,
                                             ', '.join(codes[:8]),
                                             prologue + " " * len(mnem),
                                             ', '.join(codes[8:]))


if __name__ == '__main__':
    if len(sys.argv) > 1:
        chipsets = sys.argv[1:]
    else:
        chipsets = ['chipsets.txt']
    archs = []
    for x in chipsets:
        try:
            ls = [[x.strip() for x in y]
                  for y in [z.split(':', 1) for z in decomment_readlines(x)]]
            for l in ls:
                if len(l) != 2:
                    print "Could not parse the chipset line '%s'" % ":".join(l)
                else:
                    archs.append((l[0], l[1]))
        except IOError:
            print "Could not read file %s" % x
    print prologue
    baseset = None
    for (field, fname) in archs:
        chipset_list = parse_chipset_file(fname)
        instruction_map = collate_chipset_map(chipset_list, baseset)
        if baseset is None:
            baseset = instruction_map
        print "%s = {" % field
        dump_map(instruction_map, ' ' * (len(field) + 4))
        print "%s}" % (' ' * (len(field) + 3))
