auto WDC65816::disassembleInstruction(n24 address, bool e, bool m, bool x) -> string {
  string s;

  n24 pc = address;
  string name;
  string operand;
  maybe<n24> effective;

  auto read = [&](n24 address) -> n8 {
    //$00-3f,80-bf:2000-5fff: do not attempt to read I/O registers from the disassembler:
    //this is because such reads are much more likely to have side effects to emulation.
    if((address & 0x40ffff) >= 0x2000 && (address & 0x40ffff) <= 0x5fff) return 0x00;
    return readDisassembler(address);
  };

  auto readByte = [&](n24 address) -> n8 {
    return read(address);
  };
  auto readWord = [&](n24 address) -> n16 {
    n16    data = readByte(address + 0) << 0;
    return data | readByte(address + 1) << 8;
  };
  auto readLong = [&](n24 address) -> n24 {
    n24    data = readByte(address + 0) << 0;
    return data | readWord(address + 1) << 8;
  };

  auto opcode   = read(address); address.bit(0,15)++;
  auto operand0 = read(address); address.bit(0,15)++;
  auto operand1 = read(address); address.bit(0,15)++;
  auto operand2 = read(address); address.bit(0,15)++;

  n8  operandByte = operand0 << 0;
  n16 operandWord = operand0 << 0 | operand1 << 8;
  n24 operandLong = operand0 << 0 | operand1 << 8 | operand2 << 16;

  auto absolute = [&]() -> string {
    effective = r.b << 16 | operandWord;
    return {"$", hex(operandWord, 4L)};
  };

  auto absolutePC = [&]() -> string {
    effective = pc.mask(16,23) | operandWord;
    return {"$", hex(operandWord, 4L)};
  };

  auto absoluteX = [&]() -> string {
    effective = (r.b << 16) + operandWord + r.x.w;
    return {"$", hex(operandWord, 4L), ",x"};
  };

  auto absoluteY = [&]() -> string {
    effective = (r.b << 16) + operandWord + r.y.w;
    return {"$", hex(operandWord, 4L), ",y"};
  };

  auto absoluteLong = [&]() -> string {
    effective = operandLong;
    return {"$", hex(operandLong, 6L)};
  };

  auto absoluteLongX = [&]() -> string {
    effective = operandLong + r.x.w;
    return {"$", hex(operandLong, 6L), ",x"};
  };

  auto direct = [&]() -> string {
    effective = n16(r.d.w + operandByte);
    return {"$", hex(operandByte, 2L)};
  };

  auto directX = [&]() -> string {
    effective = n16(r.d.w + operandByte + r.x.w);
    return {"$", hex(operandByte, 2L), ",x"};
  };

  auto directY = [&]() -> string {
    effective = n16(r.d.w + operandByte + r.y.w);
    return {"$", hex(operandByte, 2L), ",y"};
  };

  auto immediate = [&]() -> string {
    return {"#$", hex(operandByte, 2L)};
  };

  auto immediateA = [&]() -> string {
    return {"#$", m ? hex(operandByte, 2L) : hex(operandWord, 4L)};
  };

  auto immediateX = [&]() -> string {
    return {"#$", x ? hex(operandByte, 2L) : hex(operandWord, 4L)};
  };

  auto implied = [&]() -> string {
    return {};
  };

  auto indexedIndirectX = [&]() -> string {
    effective = n16(r.d.w + operandByte + r.x.w);
    effective = r.b << 16 | readWord(*effective);
    return {"($", hex(operandByte, 2L), ",x)"};
  };

  auto indirect = [&]() -> string {
    effective = n16(r.d.w + operandByte);
    effective = (r.b << 16) + readWord(*effective);
    return {"($", hex(operandByte, 2L), ")"};
  };

  auto indirectPC = [&]() -> string {
    effective = operandWord;
    effective = pc.mask(16,23) | readWord(*effective);
    return {"($", hex(operandWord, 4L), ")"};
  };

  auto indirectX = [&]() -> string {
    effective = operandWord;
    effective = pc.mask(16,23) | n16(*effective + r.x.w);
    effective = pc.mask(16,23) | readWord(*effective);
    return {"($", hex(operandWord, 4L), ",x)"};
  };

  auto indirectIndexedY = [&]() -> string {
    effective = n16(r.d.w + operandByte);
    effective = (r.b << 16) + readWord(*effective) + r.y.w;
    return {"($", hex(operandByte, 2L), "),y"};
  };

  auto indirectLong = [&]() -> string {
    effective = n16(r.d.w + operandByte);
    effective = readLong(*effective);
    return {"[$", hex(operandByte, 2L), "]"};
  };

  auto indirectLongPC = [&]() -> string {
    effective = readLong(operandWord);
    return {"[$", hex(operandWord, 4L), "]"};
  };

  auto indirectLongY = [&]() -> string {
    effective = n16(r.d.w + operandByte);
    effective = readLong(*effective) + r.y.w;
    return {"[$", hex(operandByte, 2L), "],y"};
  };

  auto move = [&]() -> string {
    return {"$", hex(operand0, 2L), "=$", hex(operand1, 2L)};
  };

  auto relative = [&]() -> string {
    effective = pc.mask(16,23) | n16(pc + 2 + (i8)operandByte);
    return {"$", hex(*effective, 4L)};
  };

  auto relativeWord = [&]() -> string {
    effective = pc.mask(16,23) | n16(pc + 3 + (i16)operandWord);
    return {"$", hex(*effective, 4L)};
  };

  auto stack = [&]() -> string {
    effective = n16(r.s.w + operandByte);
    return {"$", hex(operandByte, 2L), ",s"};
  };

  auto stackIndirect = [&]() -> string {
    effective = n16(operandByte + r.s.w);
    effective = (r.b << 16) + readWord(*effective) + r.y.w;
    return {"($", hex(operandByte, 2L), ",s),y"};
  };

  #define op(id, label, function) case id: name = label; operand = function(); break;
  switch(opcode) {
  op(0x00, "brk", immediate)
  op(0x01, "ora", indexedIndirectX)
  op(0x02, "cop", immediate)
  op(0x03, "ora", stack)
  op(0x04, "tsb", direct)
  op(0x05, "ora", direct)
  op(0x06, "asl", direct)
  op(0x07, "ora", indirectLong)
  op(0x08, "php", implied)
  op(0x09, "ora", immediateA)
  op(0x0a, "asl", implied)
  op(0x0b, "phd", implied)
  op(0x0c, "tsb", absolute)
  op(0x0d, "ora", absolute)
  op(0x0e, "asl", absolute)
  op(0x0f, "ora", absoluteLong)
  op(0x10, "bpl", relative)
  op(0x11, "ora", indirectIndexedY)
  op(0x12, "ora", indirect)
  op(0x13, "ora", stackIndirect)
  op(0x14, "trb", direct)
  op(0x15, "ora", directX)
  op(0x16, "asl", directX)
  op(0x17, "ora", indirectLongY)
  op(0x18, "clc", implied)
  op(0x19, "ora", absoluteY)
  op(0x1a, "inc", implied)
  op(0x1b, "tas", implied)
  op(0x1c, "trb", absolute)
  op(0x1d, "ora", absoluteX)
  op(0x1e, "asl", absoluteX)
  op(0x1f, "ora", absoluteLongX)

  op(0x20, "jsr", absolutePC)
  op(0x21, "and", indexedIndirectX)
  op(0x22, "jsl", absoluteLong)
  op(0x23, "and", stack)
  op(0x24, "bit", direct)
  op(0x25, "and", direct)
  op(0x26, "rol", direct)
  op(0x27, "and", indirectLong)
  op(0x28, "plp", implied)
  op(0x29, "and", immediateA)
  op(0x2a, "rol", implied)
  op(0x2b, "pld", implied)
  op(0x2c, "bit", absolute)
  op(0x2d, "and", absolute)
  op(0x2e, "rol", absolute)
  op(0x2f, "and", absoluteLong)
  op(0x30, "bmi", relative)
  op(0x31, "and", indirectIndexedY)
  op(0x32, "and", indirect)
  op(0x33, "and", stackIndirect)
  op(0x34, "bit", directX)
  op(0x35, "and", directX)
  op(0x36, "rol", directX)
  op(0x37, "and", indirectLongY)
  op(0x38, "sec", implied)
  op(0x39, "and", absoluteY)
  op(0x3a, "dec", implied)
  op(0x3b, "tsa", implied)
  op(0x3c, "bit", absoluteX)
  op(0x3d, "and", absoluteX)
  op(0x3e, "rol", absoluteX)
  op(0x3f, "and", absoluteLongX)

  op(0x40, "rti", implied)
  op(0x41, "eor", indexedIndirectX)
  op(0x42, "wdm", immediate)
  op(0x43, "eor", stack)
  op(0x44, "mvp", move)
  op(0x45, "eor", direct)
  op(0x46, "lsr", direct)
  op(0x47, "eor", indirectLong)
  op(0x48, "pha", implied)
  op(0x49, "eor", immediateA)
  op(0x4a, "lsr", implied)
  op(0x4b, "phk", implied)
  op(0x4c, "jmp", absolutePC)
  op(0x4d, "eor", absolute)
  op(0x4e, "lsr", absolute)
  op(0x4f, "eor", absoluteLong)
  op(0x50, "bvc", relative)
  op(0x51, "eor", indirectIndexedY)
  op(0x52, "eor", indirect)
  op(0x53, "eor", stackIndirect)
  op(0x54, "mvn", move)
  op(0x55, "eor", directX)
  op(0x56, "lsr", directX)
  op(0x57, "eor", indirectLongY)
  op(0x58, "cli", implied)
  op(0x59, "eor", absoluteY)
  op(0x5a, "phy", implied)
  op(0x5b, "tad", implied)
  op(0x5c, "jml", absoluteLong)
  op(0x5d, "eor", absoluteX)
  op(0x5e, "lsr", absoluteX)
  op(0x5f, "eor", absoluteLongX)

  op(0x60, "rts", implied)
  op(0x61, "adc", indexedIndirectX)
  op(0x62, "per", relativeWord)
  op(0x63, "adc", stack)
  op(0x64, "stz", direct)
  op(0x65, "adc", direct)
  op(0x66, "ror", direct)
  op(0x67, "adc", indirectLong)
  op(0x68, "pla", implied)
  op(0x69, "adc", immediateA)
  op(0x6a, "ror", implied)
  op(0x6b, "rtl", implied)
  op(0x6c, "jmp", indirectPC)
  op(0x6d, "adc", absolute)
  op(0x6e, "ror", absolute)
  op(0x6f, "adc", absoluteLong)
  op(0x70, "bvs", relative)
  op(0x71, "adc", indirectIndexedY)
  op(0x72, "adc", indirect)
  op(0x73, "adc", stackIndirect)
  op(0x74, "stz", absoluteX)
  op(0x75, "adc", absoluteX)
  op(0x76, "ror", absoluteX)
  op(0x77, "adc", indirectLongY)
  op(0x78, "sei", implied)
  op(0x79, "adc", absoluteY)
  op(0x7a, "ply", implied)
  op(0x7b, "tda", implied)
  op(0x7c, "jmp", indirectX)
  op(0x7d, "adc", absoluteX)
  op(0x7e, "ror", absoluteX)
  op(0x7f, "adc", absoluteLongX)

  op(0x80, "bra", relative)
  op(0x81, "sta", indexedIndirectX)
  op(0x82, "brl", relativeWord)
  op(0x83, "sta", stack)
  op(0x84, "sty", direct)
  op(0x85, "sta", direct)
  op(0x86, "stx", direct)
  op(0x87, "sta", indirectLong)
  op(0x88, "dey", implied)
  op(0x89, "bit", immediateA)
  op(0x8a, "txa", implied)
  op(0x8b, "phb", implied)
  op(0x8c, "sty", absolute)
  op(0x8d, "sta", absolute)
  op(0x8e, "stx", absolute)
  op(0x8f, "sta", absoluteLong)
  op(0x90, "bcc", relative)
  op(0x91, "sta", indirectIndexedY)
  op(0x92, "sta", indirect)
  op(0x93, "sta", stackIndirect)
  op(0x94, "sty", directX)
  op(0x95, "sta", directX)
  op(0x96, "stx", directY)
  op(0x97, "sta", indirectLongY)
  op(0x98, "tya", implied)
  op(0x99, "sta", absoluteY)
  op(0x9a, "txs", implied)
  op(0x9b, "txy", implied)
  op(0x9c, "stz", absolute)
  op(0x9d, "sta", absoluteX)
  op(0x9e, "stz", absoluteX)
  op(0x9f, "sta", absoluteLongX)

  op(0xa0, "ldy", immediateX)
  op(0xa1, "lda", indexedIndirectX)
  op(0xa2, "ldx", immediateX)
  op(0xa3, "lda", stack)
  op(0xa4, "ldy", direct)
  op(0xa5, "lda", direct)
  op(0xa6, "ldx", direct)
  op(0xa7, "lda", indirectLong)
  op(0xa8, "tay", implied)
  op(0xa9, "lda", immediateA)
  op(0xaa, "tax", implied)
  op(0xab, "plb", implied)
  op(0xac, "ldy", absolute)
  op(0xad, "lda", absolute)
  op(0xae, "ldx", absolute)
  op(0xaf, "lda", absoluteLong)
  op(0xb0, "bcs", relative)
  op(0xb1, "lda", indirectIndexedY)
  op(0xb2, "lda", indirect)
  op(0xb3, "lda", stackIndirect)
  op(0xb4, "ldy", directX)
  op(0xb5, "lda", directX)
  op(0xb6, "ldx", directY)
  op(0xb7, "lda", indirectLongY)
  op(0xb8, "clv", implied)
  op(0xb9, "lda", absoluteY)
  op(0xba, "tsx", implied)
  op(0xbb, "tyx", implied)
  op(0xbc, "ldy", absoluteX)
  op(0xbd, "lda", absoluteX)
  op(0xbe, "ldx", absoluteY)
  op(0xbf, "lda", absoluteLongX)

  op(0xc0, "cpy", immediateX)
  op(0xc1, "cmp", indexedIndirectX)
  op(0xc2, "rep", immediate)
  op(0xc3, "cmp", stack)
  op(0xc4, "cpy", direct)
  op(0xc5, "cmp", direct)
  op(0xc6, "dec", direct)
  op(0xc7, "cmp", indirectLong)
  op(0xc8, "iny", implied)
  op(0xc9, "cmp", immediateA)
  op(0xca, "dex", implied)
  op(0xcb, "wai", implied)
  op(0xcc, "cpy", absolute)
  op(0xcd, "cmp", absolute)
  op(0xce, "dec", absolute)
  op(0xcf, "cmp", absoluteLong)
  op(0xd0, "bne", relative)
  op(0xd1, "cmp", indirectIndexedY)
  op(0xd2, "cmp", indirect)
  op(0xd3, "cmp", stackIndirect)
  op(0xd4, "pei", indirect)
  op(0xd5, "cmp", directX)
  op(0xd6, "dec", directX)
  op(0xd7, "cmp", indirectLongY)
  op(0xd8, "cld", implied)
  op(0xd9, "cmp", absoluteY)
  op(0xda, "phx", implied)
  op(0xdb, "stp", implied)
  op(0xdc, "jmp", indirectLongPC)
  op(0xdd, "cmp", absoluteX)
  op(0xde, "dec", absoluteX)
  op(0xdf, "cmp", absoluteLongX)

  op(0xe0, "cpx", immediateX)
  op(0xe1, "sbc", indexedIndirectX)
  op(0xe2, "sep", immediate)
  op(0xe3, "sbc", stack)
  op(0xe4, "cpx", direct)
  op(0xe5, "sbc", direct)
  op(0xe6, "inc", direct)
  op(0xe7, "sbc", indirectLong)
  op(0xe8, "inx", implied)
  op(0xe9, "sbc", immediateA)
  op(0xea, "nop", implied)
  op(0xeb, "xba", implied)
  op(0xec, "cpx", absolute)
  op(0xed, "sbc", absolute)
  op(0xee, "inc", absolute)
  op(0xef, "sbc", absoluteLong)
  op(0xf0, "beq", relative)
  op(0xf1, "sbc", indirectIndexedY)
  op(0xf2, "sbc", indirect)
  op(0xf3, "sbc", stackIndirect)
  op(0xf4, "pea", absolute)
  op(0xf5, "sbc", directX)
  op(0xf6, "inc", directX)
  op(0xf7, "sbc", indirectLongY)
  op(0xf8, "sed", implied)
  op(0xf9, "sbc", absoluteY)
  op(0xfa, "plx", implied)
  op(0xfb, "xce", implied)
  op(0xfc, "jsr", indirectX)
  op(0xfd, "sbc", absoluteX)
  op(0xfe, "inc", absoluteX)
  op(0xff, "sbc", absoluteLongX)
  }
  #undef op

  s.append(name, " ", operand);
  while(s.size() < 23) s.append(" ");
  if(effective) s.append("[", hex(*effective, 6L), "]");
  while(s.size() < 31) s.append(" ");
  return s;
}

auto WDC65816::disassembleInstruction() -> string {
  return disassembleInstruction(r.pc.d, r.e, r.p.m, r.p.x);
}

auto WDC65816::disassembleContext(maybe<bool> e) -> string {
  string s;

  s.append("A:", hex(r.a.w, 4L), " ");
  s.append("X:", hex(r.x.w, 4L), " ");
  s.append("Y:", hex(r.y.w, 4L), " ");
  s.append("S:", hex(r.s.w, 4L), " ");
  s.append("D:", hex(r.d.w, 4L), " ");
  s.append("B:", hex(r.b  , 2L), " ");

  if(!e) e = r.e;
  if(*e) {
    s.append(
      r.p.n ? 'N' : 'n',
      r.p.v ? 'V' : 'v',
      r.p.m ? '1' : '0',
      r.p.x ? 'B' : 'b',
      r.p.d ? 'D' : 'd',
      r.p.i ? 'I' : 'i',
      r.p.z ? 'Z' : 'z',
      r.p.c ? 'C' : 'c'
    );
  } else {
    s.append(
      r.p.n ? 'N' : 'n',
      r.p.v ? 'V' : 'v',
      r.p.m ? 'M' : 'm',
      r.p.x ? 'X' : 'x',
      r.p.d ? 'D' : 'd',
      r.p.i ? 'I' : 'i',
      r.p.z ? 'Z' : 'z',
      r.p.c ? 'C' : 'c'
    );
  }

  return s;
}
