/**
   * Writes the given instruction in assembly-code format.
   *
   * @param instr the instruction to display.
   */
  private void writeInstruction(Instruction instr) {

    String targetLabel = "***";
    // get label of destination addr, if instr transfers control
    if (instr.r == Machine.Reg.CB.ordinal()) targetLabel = addrToLabel.get(instr.d);

    Machine.Op instruction = Machine.intToOp[instr.op];
    asmWrite(String.format("%-7s", instruction.toString()));
    switch (instruction) {
      case LOAD:
        blankN();
        writeD(instr.d);
        writeR('[', instr.r, ']');
        break;

      case LOADA:
        blankN();
        writeD(instr.d);
        writeR('[', instr.r, ']');
        break;

      case LOADI:
        break;

      case LOADL:
        blankN();
        writeD(instr.d);
        break;

      case STORE:
        blankN();
        writeD(instr.d);
        writeR('[', instr.r, ']');
        break;

      case STOREI:
        break;

      case CALL:
        if (instr.r == Machine.Reg.PB.ordinal()) {
          blankN();
          writePrimitive(instr.d);
        } else {
          blankN();
          asmWrite(targetLabel);
        }
        break;

      case CALLI:
        break;

      case RETURN:
        writeN(instr.n);
        writeD(instr.d);
        break;

      case CALLD:
        blankN();
        writeD(instr.d);
        break;

      case PUSH:
        blankN();
        writeD(instr.d);
        break;

      case POP:
        blankN();
        writeD(instr.d);
        break;

      case JUMP:
        blankN();
        asmWrite(targetLabel);
        break;

      case JUMPI:
        break;

      case JUMPIF:
        writeN(instr.n);
        asmWrite(targetLabel);
        break;

      case HALT:
        writeN(instr.n);
        break;

      default:
        asmWrite("????  ");
        writeN(instr.n);
        writeD(instr.d);
        writeR('[', instr.r, ']');
        break;
    }
  }
  /** disassembles program held in code store */
  void disassembleProgram(String asmFileName) {

    try {
      asmOut = new FileWriter(asmFileName);
    } catch (IOException e) {
      System.out.println("Disassembler: can not create asm output file " + asmName);
      error = true;
      return;
    }

    // collect all addresses that may be the target of a jump instruction
    SortedSet<Integer> targets = new TreeSet<Integer>();
    for (int addr = Machine.CB; addr < Machine.CT; addr++) {
      Instruction inst = Machine.code[addr];
      Machine.Op op = Machine.intToOp[inst.op];
      switch (op) {
        case CALL:
          // only consider calls (branches) within code memory (i.e. not primitives)
          if (inst.r == Machine.Reg.CB.ordinal()) targets.add(inst.d);
          break;
        case JUMP:
          // address following an unconditional branch is an implicit target
          targets.add(addr + 1);
          /* FALL THROUGH! */
        case JUMPIF:
          // a jump of any sort creates a branch target
          targets.add(inst.d);
      }
    }

    // map branch target addresses to unique labels
    addrToLabel = new HashMap<Integer, String>();
    int labelCounter = 10;
    for (Integer addr : targets) {
      String label = "L" + labelCounter++;
      addrToLabel.put(addr, label);
    }

    // disassemble each instruction
    for (int addr = Machine.CB; addr < Machine.CT; addr++) {

      // generate instruction address
      asmWrite(String.format("%3d  ", addr));

      // if this addr is a branch target, output label
      if (addrToLabel.containsKey(addr))
        asmWrite(String.format("%-7s", addrToLabel.get(addr) + ":"));
      else asmWrite("       ");

      // instruction
      writeInstruction(Machine.code[addr]);

      // newline
      asmWrite("\n");
    }

    // close output file
    try {
      asmOut.close();
    } catch (IOException e) {
      error = true;
    }
  }