#!/usr/bin/env python3
# Generate almost all possible combination for instructions from instruction
# tables
# When zasm supported instructions change, use this script to update
# allinstrs.asm

import sys

# Those lines below are improperly assembled by scas and are skipped by tests.
BLACKLIST = {
    "AND (IX)",
    "AND (IY)",
}

argspecTbl = {
    'A': "A",
    'B': "B",
    'C': "C",
    'k': "(C)",
    'D': "D",
    'E': "E",
    'H': "H",
    'L': "L",
    'I': "I",
    'R': "R",
    'h': "HL",
    'l': "(HL)",
    'd': "DE",
    'e': "(DE)",
    'b': "BC",
    'c': "(BC)",
    'a': "AF",
    'f': "AF'",
    'X': "IX",
    'x': "(IX)",
    'Y': "IY",
    'y': "(IY)",
    's': "SP",
    'p': "(SP)",
    'Z': "Z",
    'z': "NZ",
    '=': "NC",
    '+': "P",
    '-': "M",
    '1': "PO",
    '2': "PE",
}

argGrpTbl = {
    chr(0x01): "bdha",
    chr(0x02): "ZzC=",
    chr(0x03): "bdhs",
    chr(0x04): "bdXs",
    chr(0x05): "bdYs",
    chr(0x0a): "ZzC=+-12",
    chr(0x0b): "BCDEHLA",
}

def cleanupLine(line):
    line = line.strip()
    idx = line.rfind(';')
    if idx >= 0:
        line = line[:idx]
    return line

def getDbLines(fp, tblname):
    lookingFor = f"{tblname}:"
    line = fp.readline()
    while line:
        line = cleanupLine(line)
        if line == lookingFor:
            break
        line = fp.readline()
    else:
        raise Exception(f"{tblname} not found")

    result = []
    line = fp.readline()
    while line:
        line = cleanupLine(line)
        if line:
            if not line.startswith('.db'):
                break
            result.append([s.strip() for s in line[4:].split(',')])
        line = fp.readline()
    return result

def genargs(argspec):
    if not argspec:
        return ''
    if not isinstance(argspec, str):
        argspec = chr(argspec)
    if argspec in 'nmNM':
        bits = 16 if argspec in 'NM' else 8
        nbs = [str(1 << i) for i in range(bits)]
        if argspec in 'mM':
            nbs = [f"({n})" for n in nbs]
        return nbs
    if argspec in 'xy':
        # IX/IY displacement is special
        base = argspecTbl[argspec]
        result = [base]
        argspec = argspec.upper()
        for n in [1, 10, 100, 127]:
            result.append(f"(I{argspec}+{n})")
        # TODO: support minus
        return result
    if argspec in argspecTbl:
        return [argspecTbl[argspec]]
    grp = argGrpTbl[argspec]
    return [argspecTbl[a] for a in grp]

# process a 'n' arg into an 'e' one
def eargs(args):
    newargs = ['$+'+s for s in args[:-1]]
    return newargs + ['$-'+s for s in args[:-1]]

def p(line):
    if line not in BLACKLIST:
        print(line)


def main():
    asmfile = sys.argv[1]
    with open(asmfile, 'rt') as fp:
        instrTbl = getDbLines(fp, 'instrTBl')
    for row in instrTbl:
        n = row[0][2:] # remove I_
        # we need to adjust for zero-char name filling
        a1 = eval(row[1])
        a2 = eval(row[2])
        args1 = genargs(a1)
        # special case handling
        if n == 'JP' and isinstance(a1, str) and a1 in 'xy':
            # we don't test the displacements for IX/IY because there can't be
            # any.
            args1 = args1[:1]
        if n in {'BIT', 'SET', 'RES'}:
            # we only want to keep 1, 2, 4
            args1 = args1[:3]
        if n in {'JR', 'DJNZ'} and a1 == 'n':
            args1 = eargs(args1)
        if n == 'IM':
            args1 = [0, 1, 2]
        if args1:
            for arg1 in args1:
                args2 = genargs(a2)
                if args2:
                    if n in {'JR', 'DJNZ'} and a2 == 'n':
                        args2 = eargs(args2)
                    for arg2 in args2:
                        p(f"{n} {arg1}, {arg2}")
                else:
                    p(f"{n} {arg1}")
        else:
            p(n)
    pass

if __name__ == '__main__':
    main()
