#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

#define OLD_ICODE 1

#ifndef FALSE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif

static int debug_input = FALSE;
static char *indent = "        ";
FILE *icode_file;
FILE *source_file;
int source_line = 0;
#define MAX_LINE 1024
char line[MAX_LINE+1];
/*
   This is primarily for the old-style (as described in the thesis) icode,
   with any 'unused' opcodes plugged in with the newer description from
   http://www.gtoal.com/athome/edinburgh/imp/imp77/icode.html

   Lower case text in the array below means 'new style'.

   There are also all the new opcodes >= 128 which I have not yet
   incorporated - the coding can be found by grepping for ^c\( in file
   http://www.gtoal.com/athome/edinburgh/acorn/3l/COMPILERS/bend/imp/pass2

   I think some of the old-style opcodes were redefined in an incompatible
   way for the new style.  So be careful.  For now this is really just
   old-style only, which I hope is all that is ever generated by the
   Mouses Imp77 V8.4 compiler which ABD is porting.
 */

#ifdef OLD_ICODE

static char *icode_name[256] = {
"<0>", "<1>", "<2>", "<3>", "<4>", "<5>", "<6>", "<7>",
"<8>", "<9>", "<10>", "<11>", "<12>", "<13>", "<14>", "<15>",
"<16>", "<17>", "<18>", "<19>", "<20>", "<21>", "<22>", "<23>",
"<24>", "<25>", "<26>", "<27>", "<28>", "<29>", "<30>", "<31>",
/*   SP  !   "   #   $   %   &   '   (   )   *   +   ,   -   .   / */
"<32>", "OR", "JUMPIFD", "BNE", "DEF", "XOR", "AND", "PUSHS",
"<'('>", "<')'>", "MUL", "ADD", "+  ", "SUB", "CONCAT", "QUOT",
/*   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ? */
"<'0'>", "<'1'>", "<'2'>", "<'3'>", "<'4'>", "<'5'>", "<'6'>", "<'7'>",
"<'8'>", "<'9'>", "LOCATE", "END", "<'<'>", "<'='>", "<'>'>", "JUMPIF",
/*   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O */
"PUSH", "INIT", "REPEAT", "JUMPIFA", "PUSHR", "CALL", "GOTO", "ALIAS",
"BEGIN", "<'I'>", "JUMP", "FALSE", "LABEL", "MAP", "PUSHI", "LINE",
/*   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _ */
"PLANT", "DIVIDE", "RETURN", "ASSVAL", "TRUE", "NEGATE", "RESULT", "SJUMP",
"IEXP", "DEFAULT", "ASSREF", "LSH", "NOT", "RSH", "PROC", "SLABEL",
/*   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o */
"<'`'>", "ACCESS", "BOUNDS", "MCODE", "DIM", "EVENT", "FOR", "<'g'>",
"ALTBEG", "INDEX", "JAM", "BF", "LANG", "MONITOR", "SELECT", "ON",
/*   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ DEL */
"ASSPAR", "ALTEND", "RESOLVE", "STOP", "BT", "ADDA", "MOD", "SUBA",
"REXP", "DIAG", "CONTROL", "START", "ALT", "FINISH", "pending", "<127>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
};

#else /* New Icode */

static char *icode_name[256] = {
"<0>", "<1>", "<2>", "<3>", "<4>", "<5>", "<6>", "<7>",
"<8>", "<9>", "<10>", "<11>", "<12>", "<13>", "<14>", "<15>",
"<16>", "<17>", "<18>", "<19>", "<20>", "<21>", "<22>", "<23>",
"<24>", "<25>", "<26>", "<27>", "<28>", "<29>", "<30>", "<31>",
/*   SP  !   "   #   $   %   &   '   (   )   *   +   ,   -   .   / */
"<32>", "OR", "JUMPIFD/compare-double?", "BNE", "DEF", "XOR", "AND", "PUSHS",
"ble", "bge", "MUL", "ADD", "+  ", "SUB", "CONCAT", "QUOT",
/*   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ? */
"<'0'>", "<'1'>", "<'2'>", "<'3'>", "<'4'>", "<'5'>", "<'6'>", "<'7'>",
"<'8'>", "<'9'>", "LOCATE", "END", "blt", "beq", "bgt", "JUMPIF/compare-values?",
/*   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O */
"PUSH", "INIT", "REPEAT", "JUMPIFA/compare-addresses", "PUSHR", "CALL", "GOTO", "ALIAS",
"BEGIN", "select-input-2", "JUMP", "FALSE", "LABEL", "MAP", "PUSHI", "LINE",
/*   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _ */
"PLANT", "DIVIDE", "RETURN", "ASSVAL", "TRUE", "NEGATE", "RESULT", "SJUMP",
"IEXP", "DEFAULT", "ASSREF", "LSH", "NOT", "RSH", "PROC", "SLABEL",
/*   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o */
"<'`'>", "ACCESS", "BOUNDS", "MCODE", "DIM", "EVENT", "FOR", "<'g'>",
"ALTBEG", "INDEX", "JAM", "bf", "LANG", "MONITOR", "SELECT", "ON",
/*   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ DEL */
"ASSPAR", "ALTEND", "RESOLVE", "STOP", "bt", "ADDA", "MOD", "SUBA",
"REXP", "DIAG", "CONTROL", "START", "ALT", "FINISH", "pending", "<127>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   SP  !   "   #   $   %   &   '   (   )   *   +   ,   -   .   / */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ? */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _ */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
/*   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ DEL */
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
"<128-255>", "<128-255>", "<128-255>", "<128-255>",
};
#endif

int get_icode(FILE *f)
{
  int c = fgetc(f);
  if (c != EOF) {
    if (debug_input) {
      if ('!' <= c && c <= '~') {
        fprintf(stdout, "%c:\n", c);
      } else {
        fprintf(stdout, "%d:\n", c);
      }
    }
  }
  return(c);
}

static char *getname(void) /* A Hack */
{
  static char local[256];
  int c;
  char *s = local;

  for (;;) {
    c = get_icode(icode_file);
    assert(c != EOF);
    if ((isalpha(c) && isupper(c)) || isdigit(c)) { } else break;
    *s++ = c;
  }
  *s = '\0';
  ungetc(c, icode_file);
  return(local);
}

static char *getwordconst(void)
{
  static char local[12];
  int i, c;
  int word = 0;
  for (i = 0; i < 4; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  sprintf(local, "#0x%08x", word);
  return(local);
}

static char *getbyte(void)
{
  static char local[4];
  int c;
  c = get_icode(icode_file); assert(c != EOF);
  sprintf(local, "%02x", c&255);
  return(local);
}

static char *getcond(void)
{
  static char local[3];
  int c;
  c = get_icode(icode_file); assert(c != EOF); c &= 255;
  if (c == '(') {
    sprintf(local, "<=");
  } else if (c == ')') {
    sprintf(local, ">=");
  } else {
    sprintf(local, "%c", c&255);
  }
  return(local);
}

static char *getimpstring(void)
{
  static char local[256*2+3];
  int i, c, len;
  char *s = local;
  len = get_icode(icode_file); assert(c != EOF);
  *s++ = '"';
  for (i = 0; i < len; i++) {
    c = get_icode(icode_file); assert(c != EOF);
    if (c == '"') {
      *s++ = '\\';
      *s++ = c;
    } else if (c == '\n') {
      *s++ = '\\';
      *s++ = 'n';
    } else *s++ = c;
  }
  *s++ = '"';
  *s++ = '\0';
  return(local);
}

static char *getmcstring(int *cp)
{
  static char local[256*2+3];
  int i, c, len;
  char *s = local;
  for (;;) {
    c = get_icode(icode_file); assert(c != EOF);
    if ((c == ' ') || (c == ';')) {
      *s++ = '\0';
      *cp = c;
      return(local);
    }
    *s++ = c;
  }
}

static char *getshort(void)
{
  static char local[5];
  int i, c;
  int word = 0;
  for (i = 0; i < 2; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  sprintf(local, "%04x", word);
  return(local);
}

static char *getshortdecimal(int *d)
{
  static char local[7];
  int i, c;
  int word = 0;
  for (i = 0; i < 2; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  if (d != NULL) *d = word;
  sprintf(local, "%0d", word);
  return(local);
}

static char *getlab(void)
{
  static char local[7];
  int i, c;
  int word = 0;
  for (i = 0; i < 2; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  sprintf(local, "L_%04x", word);
  return(local);
}

static char *getvar(void)
{
  static char local[7];
  int i, c;
  int word = 0;
  for (i = 0; i < 2; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  sprintf(local, "V_%04x", word);
  return(local);
}

int main(int argc, char **argv)
{
  char *p1, *p2;
  int opcode, d;
  char icode_filename[256], source_filename[256];

  if (argc >= 2 && strcmp(argv[1], "-d") == 0) {
    debug_input = TRUE;
    argc -= 1; argv += 1;
  }

  if (argc != 2) {
    fprintf(stderr, "syntax: idec basename\n"); exit(1);
  }
  sprintf(icode_filename, "%s.icode", argv[1]);
  icode_file = fopen(icode_filename, "rb");
  if (icode_file == NULL) {
    fprintf(stderr, "idec: cannot open '%s' - %s\n",
                    icode_filename, strerror(errno));
    exit(2);
  }

  sprintf(source_filename, "%s.i", argv[1]);
  source_file = fopen(source_filename, "r");
  if (source_file != NULL) {
    fprintf(stdout, "\n             %s\n\n\n",
                    "Edinburgh IMP77 Compiler - Version 8.4");
  }

  for (;;) {
    opcode = get_icode(icode_file);
    if (opcode == EOF) break;
    switch(opcode) {

    case '\n': break;

    case '$': /* DEF TAG TEXT TYPE FORM SIZE SPEC PREFIX */
              p1 = getvar(); p2 = getname();
              fprintf(stdout, "%s%s %s %s\n",
                indent, icode_name[opcode], p1, p2); break;
    case ',': fprintf(stdout, "%s%s 0x%s\n", /* TEMP HACK */
                indent, icode_name[opcode], getshort()); break;

    case '"': /* JUMPIFD cond label */
    case 'C': /* JUMPIFA cond label */
    case '?': /* JUMPIF cond label */
              p1 = getcond(); p2 = getlab();
              fprintf(stdout, "%s%s %s %s\n",
                indent, icode_name[opcode], p1, p2);
              break;

    case 'k': /* BF label */
    case 't': /* BT label */
              p1 = getlab();
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], p1);
              break;

    case '\'': /* PUSHS sconst */
               fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getimpstring()); break;

    case 'D': /* PUSHR rconst */
              fprintf(stdout, "%s/* TODO */ %s %s\n",
                indent, icode_name[opcode], getwordconst());
              break;

    case 'N': /* PUSHI iconst */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getwordconst()); break;

    case ':': /* LOCATE label */
    case 'B': /* REPEAT label */
    case 'F': /* GOTO label */
    case 'J': /* JUMP label */
    case 'L': /* LABEL label */
    case 'f': /* FOR label (label was missing from thesis description) */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getlab());
              break;

    case 'O': /* LINE decimal */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getshortdecimal(&d));
              /* output source file up to line %d */

              if (source_file != NULL) {
                while (source_line < d/*+1*/) {
                  if (fgets(line, MAX_LINE, source_file) == NULL) break;
                  source_line += 1;
                  fprintf(stdout, "%6d  ", source_line);
                  line[MAX_LINE] = '\0'; fputs(line, stdout);
		}
	      }
              break;

    case '@': /* PUSH tag */
    case '^': /* PROC tag */
    case 'n': /* SELECT tag */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getvar()); break;

    case 'W': /* SJUMP sd */
    case '_': /* SLABEL sd */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getshortdecimal(NULL)); break;
              break;

    case 'd': /* DIM short,short */
              p1 = strdup(getshort()); assert(get_icode(icode_file) == ','); p2 = getshort();
              fprintf(stdout, "%s%s %s %s\n",
                indent, icode_name[opcode], p1, p2);
              free(p1);
              break;

    case 'Y': /* DEFAULT short */
    case 'A': /* INIT short */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getshortdecimal(NULL)); break;

    case 'y': /* DIAG short */
    case 'z': /* CONTROL short */
    case 'e': /* EVENT short */
    case 'l': /* LANG short */
              fprintf(stdout, "%s%s %s\n",
                indent, icode_name[opcode], getshort()); break;

    case 'o': /* ON short label */
              fprintf(stdout, "%s%s %s %s\n",
                indent, icode_name[opcode], getshort(), getlab()); break;

    case 'r': /* RESOLVE m */
              fprintf(stdout, "%s/* TODO */ %s %s\n",
                indent, icode_name[opcode], getbyte()); break;
              break;

    case 'w': /* SUBA - documentation failure??? file uses 'w'  */
              opcode = 'c';
    case 'c': /* MCODE */
              {
              int i1;
              short int h1;
                printf("%s%s ", indent, icode_name[opcode]);
                for (;;) {
                  p1 = getmcstring(&i1);
                  printf(" %s", p1);
                  if (i1 == ';') break;
                  p2 = getshort();
                  printf(" tag_%s", p2);
                }
                printf("\n");
              }
              break;
    case '!': /* OR */
    case '#': /* BNE */
    case '%': /* XOR */
    case '&': /* AND */
    case '*': /* MUL */
    case '+': /* ADD */
    case '-': /* SUB */
    case '.': /* CONCAT */
    case '/': /* QUOT */
    case ';': /* END */
    case 'E': /* CALL */
    case 'G': /* ALIAS */
    case 'H': /* BEGIN */
    case 'K': /* FALSE */
    case 'M': /* MAP */
    case 'P': /* PLANT */
    case 'Q': /* DIVIDE */
    case 'R': /* RETURN */
    case 'S': /* ASSVAL */
    case 'T': /* TRUE */
    case 'U': /* NEGATE */
    case 'V': /* RESULT */
    case 'X': /* IEXP */
    case 'Z': /* ASSREF */
    case '[': /* LSH */
    case '\\': /* NOT */
    case ']': /* RSH */
    case 'a': /* ACCESS */
    case 'b': /* BOUNDS */
    case 'h': /* ALTBEG */
    case 'i': /* INDEX */
    case 'j': /* JAM */
    case 'm': /* MONITOR */
    case 'p': /* ASSPAR */
    case 'q': /* ALTEND */
    case 's': /* STOP */
    case 'u': /* ADDA */
    case 'v': /* MOD */
    case 'x': /* REXP */
    case '{': /* START */
    case '|': /* ALT */
    case '}': /* FINISH */
              fprintf(stdout, "%s%s\n", indent, icode_name[opcode]); break;

    default:  fprintf(stdout, "?%s%s\n", indent, icode_name[opcode]); break;
    }
  }
  exit(0);
  return(0);
}
