/* Copyright (C) 3L Ltd. 1987-1991 */

/* I-code decoder.   ADC, 1-Jan-89  */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

/* Headers for idc.c */
extern void setmap(void);
extern int main(int argc, char **argv);
extern void list(int upto);
// extern void error(char *fmt, int a, int b, int c, int d, int e);
extern void error(char *fmt, ...);
extern void decode(int code);
extern void hex_bytes(size_t b);
extern void handle(int argument);
extern int getword(void);
extern int gettag(void);
extern int getbyte(void);
extern char *getstr(char *buf);
extern char *getstring(char *buf, int terminator);
/* End of headers for idc.c */

#include <limits.h>
//#define MAXINT  ((int)(((unsigned)(-1)<<1) >> 1))) /* most positive 2's complement int */
#define MAXLINE 132 /* longest acceptable source line size */

static long last_start = 0L;

struct iop {
  char code;
  char *name;
  char *args; /* args string lists argument types, or 0 for 'none'.
                        Valid types are:
                           'w':  32-bit value
                           'p':  32-bit hex pattern
                           't':  16-bit tag
                           'b':  8-bit value
                           's':  string, preceded by length count byte
                           ',':  string, terminated by ','
                           ';':  string, terminated by ';'
                           'z':  sizeof operand: handled specially
                           'D':  special for complicated 'define-var' args
                           '@':  real-constant arg
                           'x':  hex bytes, preceded by length count byte
                           'X':  hex bytes, preceded by length 32-bit word
                           'i':  an I-code (normally one of beq,bgt,bne etc.)
                           '?':  peculiar argument type handled specially
                    */
} icodes[] = {

    '$',
    "define-var",
    "t,D",
    'b',
    "constant-bounds",
    0,
    'd',
    "dimension",
    "tt",
    '@',
    "stack-var",
    "t",
    ';',
    "end",
    0,
    '{',
    "{",
    0,
    '}',
    "}",
    0,
    '~',
    "~",
    "b",
    128 + ',',
    "128+','",
    0,
    '\'',
    "string",
    "s",
    'D',
    "real-constant",
    "@",
    '!',
    "or",
    0,
    '%',
    "xor",
    0,
    '&',
    "and",
    0,
    '+',
    "add",
    0,
    '-',
    "sub",
    0,
    '*',
    "mul",
    0,
    'Q',
    "rdiv",
    0,
    '/',
    "div",
    0,
    '[',
    "lsh",
    0,
    ']',
    "rsh",
    0,
    '.',
    "conc",
    0,
    'X',
    "exp",
    0,
    'x',
    "rexp",
    0,
    128 + '|',
    "urem",
    0,
    128 + 'N',
    "rem",
    0,
    128 + '/',
    "udiv",
    0,
    'U',
    "neg",
    0,
    '\\',
    "not",
    0,
    'v',
    "abs",
    0,
    128 + 'X',
    "float",
    0,
    128 + 'T',
    "trunc",
    0,
    128 + '?',
    "unsigned-compare-values",
    0,
    '?',
    "compare-values",
    0,
    'C',
    "compare-addresses",
    0,
    '"',
    "compare-double",
    0,
    128 + '"',
    "unsigned-stack-condition",
    "i",
    128 + 'C',
    "stack-condition",
    "i",
    '=',
    "beq",
    "t",
    '#',
    "bne",
    "t",
    '>',
    "bgt",
    "t",
    '<',
    "blt",
    "t",
    '(',
    "ble",
    "t",
    ')',
    "bge",
    "t",
    ':',
    "label",
    "t",
    'B',
    "branch-back",
    "t",
    'F',
    "branch-forward",
    "t",
    'J',
    "user-jump",
    "t",
    'L',
    "user-label",
    "t",
    '_',
    "switch-label",
    "t",
    'W',
    "switch-jump",
    "t",
    'f',
    "for",
    "t",
    'e',
    "signal",
    "t",
    'o',
    "event-trap",
    "t",
    'm',
    "monitor",
    0,
    'K',
    "return-false",
    0,
    'M',
    "return-address",
    0,
    'R',
    "return",
    0,
    'T',
    "return-true",
    0,
    'V',
    "return-value",
    0,
    'a',
    "array-access",
    0,
    'i',
    "array-index",
    0,
    'S',
    "assign-value",
    0,
    'Z',
    "assign-address",
    0,
    'j',
    "jam-transfer",
    0,
    'p',
    "assign-parameter",
    0,
    'A',
    "init",
    "t",
    'E',
    "call",
    0,
    'G',
    "alias",
    "s",
    'H',
    "begin",
    0,
    'I',
    "icode-stream-select",
    0,
    'N',
    "lit",
    "w",
    128 + 'b',
    "byte",
    "b",
    'O',
    "line",
    "t",
    'l',
    "language-flags",
    "t",
    'n',
    "select",
    "t",
    'u',
    "adjust",
    0,
    'w',
    "machine-code",
    ";",
    'q',
    "include",
    "s",
    'y',
    "diagnose",
    "t",
    128 + 'o',
    "owntype",
    "t",
    128 + 'A',
    "amap",
    0,
    128 + 'V',
    "vmap",
    "t",
    128 + 'p',
    "fmap",
    "t",
    128 + 'F',
    "format",
    "t",
    128 + 'E',
    "eval",
    0,
    128 + 'e',
    "eval-address",
    0,
    128 + 'z',
    "sizeof",
    "z",
    128 + 'S',
    "swap",
    0,
    128 + 'D',
    "dup",
    0,
    128 + 'P',
    "pop",
    0,
    128 + 'g',
    "C-call",
    0,
    't',
    "bt",
    "t",
    'k',
    "bf",
    "t",
    'r',
    "resolve",
    "t",

    /* Pascal-specific ones */

    'g',
    "test-nil",
    0,
    '^',
    "set-format",
    0,
    128 + '3',
    "set-variant-count",
    "t",
    128 + '4',
    "test-variant-count",
    "t",
    128 + '5',
    "check-not-long-new",
    0,
    128 + '6',
    "check-dynamic-bounds",
    0,
    128 + 'R',
    "define-range",
    "t",
    128 + 'f',
    "for-range",
    "t",
    128 + 'r',
    "test-range",
    "t",
    128 + 's',
    "size-of",
    0,
    128 + 't',
    "type-of",
    0,
    128 + '{',
    "alternate",
    0,
    128 + '}',
    "end-alternate",
    0,
    128 + '[',
    "claim-with",
    "t",
    128 + ']',
    "release-with",
    "t",
    128 + 'U',
    "use-with",
    "t",
    128 + 'B',
    "no-op",
    0,
    128 + 'c',
    "general-compare",
    0,
    128 + 'm',
    "general-move",
    0,
    128 + 'L',
    "localise",
    0,
    128 + 'O',
    "round",
    0,
    128 + 'M',
    "mod",
    0,
    128 + 'Z',
    "push-[]",
    0,
    128 + '1',
    "add-member",
    0,
    128 + '2',
    "add-range",
    0,
    128 + 'i',
    "in-set?",
    0,
    128 + 'I',
    "in-set",
    0,

    /* Fortran-specific ones */

    128 + '$',
    "define-at",
    "t,Dtw",
    128 + 'a',
    "area-size",
    "tw",
    128 + '^',
    "position",
    "twt",
    128 + 'u',
    "fixup",
    "t",
    128 + 'x',
    "convert",
    "t",
    128 + '~',
    "promote",
    "t",
    128 + '=',
    "entry",
    "s",
    '0',
    "mark-stack-signed",
    0,
    '1',
    "mark-stack-unsigned",
    0,
    '2',
    "mark-desc-signed",
    0,
    '3',
    "mark-desc-unsigned",
    0,
    '4',
    "debug-v",
    "tpsb",
    '5',
    "debug-t",
    "tp",
    '6',
    "debug-x",
    "x",

    '7',
    "constant-block",
    "tX",
    '8',
    "inline-call",
    "t",
    '9',
    "bss-alloc-next",
    0,
};

/* Table below maps every character code to a pointer to relevant entry in 'iop'
   table above, or 0 for undefined I-codes */

struct iop *imap[256]; /* all zeros (null pointers) by default */

/* the mapping is set up at run-time, by the 'setmap' */

void setmap(void) {
  int i;
  struct iop *p;
  for (i = 0; i < sizeof(icodes) / sizeof(icodes[0]); i++) {
    p = &icodes[i];
    if (imap[p->code]) error("setmap: duplicate entry %d", p->code);
    imap[p->code] = p;
  }
}

FILE *ifile, *srcfile = NULL;

void skip_optional(char opt) {
  int c;
  long before;
  before = ftell(ifile);
  c = fgetc(ifile);
  if (c != opt) {
    fseek(ifile, SEEK_SET, before);
  }
}

int main(int argc, char **argv) {
  int c;

  if (argc < 2 || argc > 3) {
    if (argc == 1) fprintf(stderr, "I-code decoder V1.1\n");
    fprintf(stderr, "usage:  idc icodefile\n");
    fprintf(stderr, "   or:  idc icodefile sourcefile\n");
    exit(1);
  }

  ifile = fopen(argv[1], "rb");
  if (ifile == NULL) error("can't open I-code file %s", argv[1]);

  if (argc > 2) {
    srcfile = fopen(argv[2], "r");
    if (srcfile == NULL) error("can't open source file %s", argv[2]);
  }

  setmap();

  while ((c = getc(ifile)) != EOF) decode(c);

  list(INT_MAX); /* flush out rest of source file listing */
}

static int curline = 0;
static int depth /* of include file nesting */ = 0;

void list(int upto) {
  char linebuf[MAXLINE];
  if (srcfile)
    while (curline < upto && fgets(linebuf, MAXLINE, srcfile)) printf("%6d: %s", ++curline, linebuf);
}

void error(char *s, ...) {
  static char buff[256];
  va_list ap;
  fprintf(stderr, "idc: ");
  va_start(ap, s);
  vfprintf(stderr, s, ap);
  va_end(ap);
  fprintf(stderr, "\n\n");

  fprintf(stderr, "Recent icode (at %ld):\n", last_start);
  fseek(ifile, last_start, SEEK_SET);

  for (int i = 0; i < 32; i++) {
    int c = fgetc(ifile);
    if (c == EOF) break;
    fprintf(stderr, "%04x: %02x %c%c\n",
            i,
            c,
            c&128?'!':' ',
            ((' '+1 <= (c&127)) && ((c&127) < 127)) ? c&127 : '?');
    if ((i&4) == 3) fprintf(stderr, "\n");
  }
  fprintf(stderr, "\n");
  
  exit(EXIT_FAILURE);
}

void decode(int code) {
  struct iop *p;
  char *args;
  int line;
  
  if (code == '\n' || code == '\r') return;
  if (code < 0 || code > 255) error("code out of range");

  if (code == 'd') {
    int p1 = gettag(); skip_optional(',');
    int p2 = gettag();
    printf("\t\tCODE 'd' %d %d\n", p1, p2);
    return;
  }
  
  if (code == 'o') {
    int p1 = gettag(); skip_optional(',');
    int p2 = gettag();
    printf("\t\tevent-trap %d %d (bug-fixed)\n", p1, p2);
    return;
  }
  
  if (code == 's') {
    printf("\t\t%%stop\n");
    return;
  }
  
  p = imap[code];
  if (p == NULL) error("unknown I-code %d (%s'%c')", code, (code&128?"128+":""), code&127);

  last_start = ftell(ifile)-1L;
  
  //printf("Code %s'%c': ", (code&128?"128+":""), code&127);

  if (code == 'O') { /* line <n> */
    line = gettag();
    if (depth == 0) /* don't try and list contents of include files */
      list(line);
    printf("\t\t%s %d\n", p->name, line);

  } else if (code == 'q') { /* include */
    char *file = getstr(NULL);
    printf("\t\t%s %s\n", p->name, file);
    depth += (*file == '\0' ? -1 : 1);

  } else { /* normal codes */

    printf("\t\t%s", p->name);
    args = p->args;
    if (args) {
      while (*args) handle(*args++);
    }
    printf("\n");
  }
}

void hex_bytes(size_t b) {
  printf(" [");
  while (b--) printf(" %02x", getbyte());
  printf("]");
}

void handle(int argument) {
  switch (argument) {
    case 'w':
      printf(" %d", getword());
      break;

    case 'p':
      printf(" %08x", getword());
      break;

    case 't':
      printf(" %d", gettag());
      break;

    case 'b':
      printf(" %d", getbyte());
      break;

    case 's':
      printf(" \"%s\"", getstr(NULL));
      break;

    case ',':
    case ';':
      printf(" %s", getstring(NULL, argument));
      break;

    case 'z': {
      int b;
      printf(" %d", b = getbyte());
      if (b == 0) /* followed by <tag> (format) */
        printf(" %d", gettag());
      break;
    }

    case '@': {
      int j, c, count = gettag(), exp;
      putchar(' ');
      for (j = 0; j < count; j++) {
        c = getbyte();
        putchar(c);
        if (c == '@') {
          exp = gettag();
          if (exp & 0xFFFF8000) /* sign extend to 32 bits */
            exp |= 0xFFFF8000;
          printf("%d", exp);
          break;
        }
      }
      break;
    }

    case 'D': /* Special for 'define-var' args:
                    'type+form', 'format' and 'ostate' */
    {
      static char *translate[] = {
          /* translate 'special' codes below */
          /*  0: */ "",
          /*  1: */ "byte-integer",
          /*  2: */ "short-integer",
          /*  3: */ "long-real",
      };

      static char *types[16] = {/*  0: */ 0,
                                /*  1: */ "integer",
                                /*  2: */ "real",
                                /*  3: */ "string",
                                /*  4: */ "record",
                                /*  5: */ "Boolean",
                                /*  6: */ "set",
                                /*  7: */ "byte-enumerated",
                                /*  8: */ "short-enumerated",
                                /*  9: */ "pointer",
                                /* 10: */ "char",
                                /* 11: */ 0,
                                /* 12: */ 0,
                                /* 13: */ 0,
                                /* 14: */ 0,
                                /* 15: */ "general-area"};

      static char *forms[16] = {
          /*  0: */ "0",
          /*  1: */ "simple",
          /*  2: */ "name",
          /*  3: */ "label",
          /*  4: */ "record-format",
          /*  5: */ "5",
          /*  6: */ "switch",
          /*  7: */ "routine",
          /*  8: */ "function",
          /*  9: */ "map",
          /* 10: */ "predicate",
          /* 11: */ "array",
          /* 12: */ "12",
          /* 13: */ "name-array",
          /* 14: */ "name-array-name",
          /* 15: */ "15",
      };

      static char *ostates[] = {/*  0: */ "0",
                                /*  1: */ "own",
                                /*  2: */ "constant",
                                /*  3: */ "external",
                                /*  4: */ "system",
                                /*  5: */ "dynamic",
                                /*  6: */ "prim",
                                /*  7: */ "perm"};

      static struct {
        int mask;
        char *name;
      } oflags[] = {
          8,
          "-spec",
          16,
          "-indirect",
      };

      int tf, type, form, format, ostate, special, i;

      tf = gettag();     skip_optional(',');
      format = gettag(); skip_optional(',');
      ostate = gettag();

      type = (tf >> 4) & 0xF;
      form = tf & 0xF;

      special = 0;     /* handle bizarre type encoding scheme */
      if (type == 1) { /* integer-oid */
        if (format == 2)
          special = 1;
        else if (format == 3)
          special = 2;
      } else if (type == 2 && format == 4)
        special = 3;

      printf(" ");
      if (special)
        printf("%s", translate[special]);
      else if (types[type])
        printf("%s", types[type]);
      else
        printf("type=%d", type);

      printf(", %s, format=%d", forms[form], format);

      printf(", %s", ostates[ostate & 7]);

      for (i = 0; i < sizeof(oflags) / sizeof(oflags[0]); i++)
        if (ostate & oflags[i].mask) printf("%s", oflags[i].name);

      break;
    }

    case 'x':
      hex_bytes(getbyte());
      break;

    case 'X':
      hex_bytes(getword());
      break;

    case 'i':
      printf(" [%s]", imap[getbyte()]->name);
      break;

    case '?':
    default:
      error("unknown argument type");
  }
}

int getword(void) {
  int i = 0;
#define DOBYTE i = (i << 8) + getc(ifile)
  DOBYTE;
  DOBYTE;
  DOBYTE;
  DOBYTE;
  return (i);
}

int gettag(void) {
  int i = 0;
  DOBYTE;
  DOBYTE;
  return i;
}

int getbyte(void) {
  unsigned char c;
  c = getc(ifile);
  return c;
}

char *getstr(char *buf) {
  static char sbuf[256];
  int i, len;
  if (buf == NULL) buf = sbuf;
  for (i = 0, len = getbyte(); i < len; i++) buf[i] = getc(ifile);
  buf[len] = '\0';
  return buf;
}

char *getstring(char *buf, int terminator) {
  char *p;
  int c;
  static char sbuf[256];
  if (buf == NULL) buf = sbuf;
  p = buf;
  for (;;) {
    c = getc(ifile);
    if (c == terminator) break;
    *p++ = c;
  }
  *p = '\0';
  return buf;
}
