#!/usr/bin/env python3
"""a2c - 6502 machine code -> C transpiler."""
import sys

TBL = [
 ("ADC",0x69,"imm",2),("ADC",0x65,"zp",2),("ADC",0x75,"zpx",2),("ADC",0x6D,"abs",3),
 ("ADC",0x7D,"abx",3),("ADC",0x79,"aby",3),("ADC",0x61,"izx",2),("ADC",0x71,"izy",2),
 ("AND",0x29,"imm",2),("AND",0x25,"zp",2),("AND",0x35,"zpx",2),("AND",0x2D,"abs",3),
 ("AND",0x3D,"abx",3),("AND",0x39,"aby",3),("AND",0x21,"izx",2),("AND",0x31,"izy",2),
 ("ASL",0x0A,"acc",1),("ASL",0x06,"zp",2),("ASL",0x16,"zpx",2),("ASL",0x0E,"abs",3),("ASL",0x1E,"abx",3),
 ("BCC",0x90,"rel",2),("BCS",0xB0,"rel",2),("BEQ",0xF0,"rel",2),("BMI",0x30,"rel",2),
 ("BNE",0xD0,"rel",2),("BPL",0x10,"rel",2),("BVC",0x50,"rel",2),("BVS",0x70,"rel",2),
 ("BIT",0x24,"zp",2),("BIT",0x2C,"abs",3),("BRK",0x00,"imp",1),
 ("CLC",0x18,"imp",1),("CLD",0xD8,"imp",1),("CLI",0x58,"imp",1),("CLV",0xB8,"imp",1),
 ("CMP",0xC9,"imm",2),("CMP",0xC5,"zp",2),("CMP",0xD5,"zpx",2),("CMP",0xCD,"abs",3),
 ("CMP",0xDD,"abx",3),("CMP",0xD9,"aby",3),("CMP",0xC1,"izx",2),("CMP",0xD1,"izy",2),
 ("CPX",0xE0,"imm",2),("CPX",0xE4,"zp",2),("CPX",0xEC,"abs",3),
 ("CPY",0xC0,"imm",2),("CPY",0xC4,"zp",2),("CPY",0xCC,"abs",3),
 ("DEC",0xC6,"zp",2),("DEC",0xD6,"zpx",2),("DEC",0xCE,"abs",3),("DEC",0xDE,"abx",3),
 ("DEX",0xCA,"imp",1),("DEY",0x88,"imp",1),
 ("EOR",0x49,"imm",2),("EOR",0x45,"zp",2),("EOR",0x55,"zpx",2),("EOR",0x4D,"abs",3),
 ("EOR",0x5D,"abx",3),("EOR",0x59,"aby",3),("EOR",0x41,"izx",2),("EOR",0x51,"izy",2),
 ("INC",0xE6,"zp",2),("INC",0xF6,"zpx",2),("INC",0xEE,"abs",3),("INC",0xFE,"abx",3),
 ("INX",0xE8,"imp",1),("INY",0xC8,"imp",1),
 ("JMP",0x4C,"abs",3),("JMP",0x6C,"ind",3),("JSR",0x20,"abs",3),
 ("LDA",0xA9,"imm",2),("LDA",0xA5,"zp",2),("LDA",0xB5,"zpx",2),("LDA",0xAD,"abs",3),
 ("LDA",0xBD,"abx",3),("LDA",0xB9,"aby",3),("LDA",0xA1,"izx",2),("LDA",0xB1,"izy",2),
 ("LDX",0xA2,"imm",2),("LDX",0xA6,"zp",2),("LDX",0xB6,"zpy",2),("LDX",0xAE,"abs",3),("LDX",0xBE,"aby",3),
 ("LDY",0xA0,"imm",2),("LDY",0xA4,"zp",2),("LDY",0xB4,"zpx",2),("LDY",0xAC,"abs",3),("LDY",0xBC,"abx",3),
 ("LSR",0x4A,"acc",1),("LSR",0x46,"zp",2),("LSR",0x56,"zpx",2),("LSR",0x4E,"abs",3),("LSR",0x5E,"abx",3),
 ("NOP",0xEA,"imp",1),
 ("ORA",0x09,"imm",2),("ORA",0x05,"zp",2),("ORA",0x15,"zpx",2),("ORA",0x0D,"abs",3),
 ("ORA",0x1D,"abx",3),("ORA",0x19,"aby",3),("ORA",0x01,"izx",2),("ORA",0x11,"izy",2),
 ("PHA",0x48,"imp",1),("PHP",0x08,"imp",1),("PLA",0x68,"imp",1),("PLP",0x28,"imp",1),
 ("ROL",0x2A,"acc",1),("ROL",0x26,"zp",2),("ROL",0x36,"zpx",2),("ROL",0x2E,"abs",3),("ROL",0x3E,"abx",3),
 ("ROR",0x6A,"acc",1),("ROR",0x66,"zp",2),("ROR",0x76,"zpx",2),("ROR",0x6E,"abs",3),("ROR",0x7E,"abx",3),
 ("RTI",0x40,"imp",1),("RTS",0x60,"imp",1),
 ("SBC",0xE9,"imm",2),("SBC",0xE5,"zp",2),("SBC",0xF5,"zpx",2),("SBC",0xED,"abs",3),
 ("SBC",0xFD,"abx",3),("SBC",0xF9,"aby",3),("SBC",0xE1,"izx",2),("SBC",0xF1,"izy",2),
 ("SEC",0x38,"imp",1),("SED",0xF8,"imp",1),("SEI",0x78,"imp",1),
 ("STA",0x85,"zp",2),("STA",0x95,"zpx",2),("STA",0x8D,"abs",3),("STA",0x9D,"abx",3),
 ("STA",0x99,"aby",3),("STA",0x81,"izx",2),("STA",0x91,"izy",2),
 ("STX",0x86,"zp",2),("STX",0x96,"zpy",2),("STX",0x8E,"abs",3),
 ("STY",0x84,"zp",2),("STY",0x94,"zpx",2),("STY",0x8C,"abs",3),
 ("TAX",0xAA,"imp",1),("TAY",0xA8,"imp",1),("TSX",0xBA,"imp",1),
 ("TXA",0x8A,"imp",1),("TXS",0x9A,"imp",1),("TYA",0x98,"imp",1),
]
OP = {}
for m,op,mode,ln in TBL:
    OP[op]=(m,mode,ln)

class Insn:
    __slots__=("addr","mn","mode","len","operand")
    def __init__(s,addr,mn,mode,ln,operand):
        s.addr,s.mn,s.mode,s.len,s.operand=addr,mn,mode,ln,operand

def disasm(data, org):
    out,i=[],0
    while i<len(data):
        addr=org+i; b=data[i]
        if b in OP:
            mn,mode,ln=OP[b]
            if i+ln>len(data):
                out.append(("raw",addr,data[i:])); break
            raw=data[i:i+ln]; operand=None
            if mode=="imm" or mode in ("zp","zpx","zpy","izx","izy"):
                operand=raw[1]
            elif mode in ("abs","abx","aby","ind"):
                operand=raw[1]|(raw[2]<<8)
            elif mode=="rel":
                off=raw[1] if raw[1]<128 else raw[1]-256
                operand=addr+2+off
            out.append(Insn(addr,mn,mode,ln,operand)); i+=ln
        else:
            out.append(("raw",addr,bytes([b]))); i+=1
    return out

COND={"BCC":"s->c==0","BCS":"s->c!=0","BEQ":"s->z==1","BMI":"s->n==1",
      "BNE":"s->z==0","BPL":"s->n==0","BVC":"s->v==0","BVS":"s->v!=0"}

def ox(ins):
    md,ins_op=ins.mode,ins.operand
    if md=="imm": return "0x%02X"%ins_op
    if md=="zp": return "s->m[0x%02X]"%ins_op
    if md=="zpx": return "s->m[(0x%02X+s->x)&0xFF]"%ins_op
    if md=="zpy": return "s->m[(0x%02X+s->y)&0xFF]"%ins_op
    if md=="abs": return "s->m[0x%04X]"%ins_op
    if md=="abx": return "s->m[(0x%04X+s->x)&0xFFFF]"%ins_op
    if md=="aby": return "s->m[(0x%04X+s->y)&0xFFFF]"%ins_op
    if md=="izx": return "s->m[ZP16(0x%02X,s->x)]"%ins_op
    if md=="izy": return "s->m[(ZP16(0x%02X,0)+s->y)&0xFFFF]"%ins_op
    raise ValueError(md)

ARITH={"ADC":"s->a=adc(s,%s);","SBC":"s->a=sbc(s,%s);",
 "AND":"setzn(s,s->a&=(uint8_t)(%s));","ORA":"setzn(s,s->a|=(uint8_t)(%s));",
 "EOR":"setzn(s,s->a^=(uint8_t)(%s));","CMP":"cmp8(s,s->a,%s);",
 "CPX":"cmp8(s,s->x,%s);","CPY":"cmp8(s,s->y,%s);",
 "LDA":"setzn(s,s->a=(uint8_t)(%s));","LDX":"setzn(s,s->x=(uint8_t)(%s));",
 "LDY":"setzn(s,s->y=(uint8_t)(%s));","STA":"%s=s->a;","STX":"%s=s->x;","STY":"%s=s->y;",
 "BIT":"bit(s,%s);"}

def emit(ins,labels,entries):
    mn,md=ins.mn,ins.mode
    if mn in COND:
        if ins.operand in labels: return ["if(%s) goto L_%04X;"%(COND[mn],ins.operand)]
        if ins.operand in entries: return ["if(%s){fn_%04X(s); return;}"%(COND[mn],ins.operand)]
        return ["/* branch to unknown 0x%04X */"%ins.operand]
    if mn=="JMP":
        if md=="ind": return ["jmp_ind(s,0x%04X); return;"%ins.operand]
        if ins.operand in labels: return ["goto L_%04X;"%ins.operand]
        if ins.operand in entries: return ["fn_%04X(s); return;"%ins.operand]
        return ["return; /* JMP 0x%04X */"%ins.operand]
    if mn=="JSR": return ["fn_%04X(s);"%ins.operand]
    if mn in ("RTS","RTI"): return ["return;"]
    if mn=="BRK": return ["s->brk=1; return;"]
    if mn=="NOP": return ["/* NOP */"]
    if mn=="CLC": return ["s->c=0;"]
    if mn=="SEC": return ["s->c=1;"]
    if mn=="CLD": return ["s->d=0;"]
    if mn=="SED": return ["s->d=1;"]
    if mn=="CLI": return ["s->i=0;"]
    if mn=="SEI": return ["s->i=1;"]
    if mn=="CLV": return ["s->v=0;"]
    if mn=="PHA": return ["push(s,s->a);"]
    if mn=="PHP": return ["push(s,pack(s)|0x10);"]
    if mn=="PLA": return ["setzn(s,s->a=pull(s));"]
    if mn=="PLP": return ["unpack(s,pull(s));"]
    if mn=="TAX": return ["setzn(s,s->x=s->a);"]
    if mn=="TAY": return ["setzn(s,s->y=s->a);"]
    if mn=="TXA": return ["setzn(s,s->a=s->x);"]
    if mn=="TYA": return ["setzn(s,s->a=s->y);"]
    if mn=="TSX": return ["setzn(s,s->x=s->sp);"]
    if mn=="TXS": return ["s->sp=s->x;"]
    if mn=="DEX": return ["setzn(s,(uint8_t)--s->x);"]
    if mn=="DEY": return ["setzn(s,(uint8_t)--s->y);"]
    if mn=="INX": return ["setzn(s,(uint8_t)++s->x);"]
    if mn=="INY": return ["setzn(s,(uint8_t)++s->y);"]
    if mn=="INC": return ["%s=rmw(s,%s,1);"%(ox(ins),ox(ins))]
    if mn=="DEC": return ["%s=rmw(s,%s,-1);"%(ox(ins),ox(ins))]
    if mn in ("ASL","LSR","ROL","ROR"):
        if md=="acc": return ["s->a=shift(s,'%s',s->a);"%mn]
        return ["%s=shift(s,'%s',%s);"%(ox(ins),mn,ox(ins))]
    if mn in ARITH: return [ARITH[mn]%ox(ins)]
    return ["/* unhandled %s */"%mn]

def main():
    binpath,org,entry,outpath=sys.argv[1],int(sys.argv[2],16),int(sys.argv[3],16),sys.argv[4]
    data=open(binpath,"rb").read()
    items=disasm(data,org)
    iba={i.addr:i for i in items if isinstance(i,Insn)}

    entries={entry}
    for i in list(iba.values()):
        if i.mn=="JSR" and i.operand in iba: entries.add(i.operand)

    owner={}
    for e in sorted(entries):
        pc=e
        while pc in iba and pc not in owner and (pc==e or pc not in entries):
            owner[pc]=e; ins=iba[pc]
            if ins.mn in ("RTS","RTI","BRK"): break
            pc+=ins.len
    labels=set()
    for e in sorted(entries):
        for pc in sorted(p for p in owner if owner[p]==e):
            ins=iba[pc]
            if ins.mn.startswith("B") and ins.mn not in ("BIT",):
                if ins.operand in iba and owner.get(ins.operand)==e: labels.add(ins.operand)
            if ins.mn=="JMP" and ins.operand in iba and owner.get(ins.operand)==e:
                labels.add(ins.operand)

    L=[]
    A=L.append
    A("/* generated by a2c (6502 -> C). entry 0x%04X, org 0x%04X */"%(entry,org))
    A("#include <stdint.h>")
    A("#include <string.h>")
    A("#include <stdio.h>")
    A("typedef struct{uint8_t a,x,y,sp,c,z,n,v,i,d,brk;uint16_t pc;uint8_t m[65536];}cpu_t;")
    A("#define ZP16(z,ix) ((((z)+(ix))&0xFF)|(((((z)+(ix)+1)&0xFF))<<8))")
    A("static void setzn(cpu_t*s,uint8_t v){s->z=(v==0);s->n=(v&0x80)!=0;}")
    A("static void push(cpu_t*s,uint8_t v){s->m[0x100+s->sp]=v;s->sp--;}")
    A("static uint8_t pull(cpu_t*s){s->sp++;return s->m[0x100+s->sp];}")
    A("static uint8_t pack(cpu_t*s){return (s->n<<7)|(s->v<<6)|0x20|(s->d<<3)|(s->i<<2)|(s->z<<1)|s->c;}")
    A("static void unpack(cpu_t*s,uint8_t p){s->n=(p>>7)&1;s->v=(p>>6)&1;s->d=(p>>3)&1;s->i=(p>>2)&1;s->z=(p>>1)&1;s->c=p&1;}")
    A("static uint8_t adc(cpu_t*s,uint8_t r){unsigned t=(unsigned)s->a+r+s->c;s->c=t>0xFF;s->v=((s->a^t)&(r^t)&0x80)!=0;s->a=(uint8_t)t;setzn(s,s->a);return s->a;}")
    A("static uint8_t sbc(cpu_t*s,uint8_t r){unsigned t=(unsigned)s->a-(r+(1-s->c));s->c=t<0x100;s->v=((s->a^r)&(s->a^t)&0x80)!=0;s->a=(uint8_t)t;setzn(s,s->a);return s->a;}")
    A("static void cmp8(cpu_t*s,uint8_t a,uint8_t r){unsigned t=(unsigned)a-r;s->c=t<0x100;setzn(s,(uint8_t)t);}")
    A("static void bit(cpu_t*s,uint8_t r){s->z=(s->a&r)==0;s->n=(r&0x80)!=0;s->v=(r&0x40)!=0;}")
    A("static uint8_t rmw(cpu_t*s,int v,int d){uint8_t t=(uint8_t)(v+d);setzn(s,t);return t;}")
    A("static uint8_t shift(cpu_t*s,char op,uint8_t v){unsigned c=s->c;uint8_t r;")
    A(" if(op=='A'){s->c=(v&0x80)!=0;r=(uint8_t)(v<<1);}")
    A(" else if(op=='L'){s->c=v&1;r=(uint8_t)(v>>1);}")
    A(" else {s->c=v&1;r=(uint8_t)((v>>1)|(c<<7));}")
    A(" setzn(s,r);return r;}")
    A("static void jmp_ind(cpu_t*s,uint16_t a){s->pc=(uint16_t)(s->m[a]|(s->m[(uint16_t)(a+1)]<<8));}")
    for e in sorted(entries): A("static void fn_%04X(cpu_t*s);"%e)
    for e in sorted(entries):
        A("")
        A("/* routine @ 0x%04X */"%e)
        A("static void fn_%04X(cpu_t*s){"%e)
        for pc in sorted(p for p in owner if owner[p]==e):
            ins=iba[pc]
            if pc in labels or pc==e: A("L_%04X: ;"%pc)
            for st in emit(ins,labels,entries): A("  "+st)
        A("}")
    A("")
    A("int main(void){static cpu_t cpu;memset(&cpu,0,sizeof cpu);")
    A(" fn_%04X(&cpu);"%entry)
    A(" for(unsigned i=0;i<65536;i++) if(cpu.m[i]) printf(\"%04X %02X\\n\",i,cpu.m[i]);")
    A(" return 0;}")
    open(outpath,"w").write("\n".join(L)+"\n")
    ni=len([i for i in items if isinstance(i,Insn)])
    print("wrote %s: %d insns, %d routines"%(outpath,ni,len(entries)))

if __name__=="__main__":
    main()
