static char *rcs_version = "icode.c V$Revision: 1.18 $";
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
extern char *strdup(const char *s); // should have been in string.h ???
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>

#include "flex.h"
//#include "ast.h"
#include "i2c.h"
//#include "icode.h"
extern FILE *icode_file;
extern int PARM_DEBUG;

const 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",
/* (    )      *      +      ,      -      .         /                 */
"ble", "bge", "MUL", "ADD", "+  ", "SUB", "CONCAT", "QUOT",
/*  0     1        2        3        4        5        6        7      */
"<'0'>", "<'1'>", "<'2'>", "<'3'>", "<'4'>", "<'5'>", "<'6'>", "<'7'>",
/*  8     9        :         ;      <      =      >      ?             */
"<'8'>", "<'9'>", "LOCATE", "END", "blt", "beq", "bgt", "JUMPIF",
/*  @    A       B         C                      D        E       F      G */
"PUSH", "INIT", "REPEAT", "JUMPIFA", "PUSHR", "CALL", "GOTO", "ALIAS",
/*  H     I                 J       K        L        M      N       O */
"BEGIN", "select-input-2", "JUMP", "FALSE", "LABEL", "MAP", "PUSHI", "LINE",
/*  P     Q         R         S         T       U         V         W  */
"PLANT", "DIVIDE", "RETURN", "ASSVAL", "TRUE", "NEGATE", "RESULT", "SJUMP",
/*  X    Y          Z         [      \      ]      ^       _           */
"IEXP", "DEFAULT", "ASSREF", "LSH", "NOT", "RSH", "PROC", "SLABEL",
/*  `     a         b         c        d      e        f      g        */
"DEFSWLABEL", "ACCESS", "BOUNDS", "MCODE", "DIM", "EVENT", "FOR", "REDEF", /* 'g' was unused, now REDEF (to help with procedure parameters) */
/*   h     i        j      k     l       m          n         o        */
"ALTBEG", "INDEX", "JAM", "BF", "LANG", "MONITOR", "SELECT", "ON",
/*   p     q         r          s       t     u      v       w         */
"ASSPAR", "ALTEND", "RESOLVE", "STOP", "BT", "ADDA", "MOD", "MCODE",
/*  x    y       z          {        |      }         ~          DEL   */
"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>",
};

// having separate arrays here for compiler labels and user labels may
// not be needed.  I'll re-examine this *after* I have user labels working...

int label_id[MAX_LABEL_IDS];        /* init to 0 means not used */
int user_label_id[MAX_LABEL_IDS];   /* init to 0 means not used */
static int next_label_id = 0;           // start at L_0001
static int next_user_label_id = 0x0FFF; // start at U_1000

int get_next_user_label(void) {
  next_user_label_id += 1;
  if (next_user_label_id >= MAX_LABEL_IDS) {
    fprintf(stderr, "* Ran out of label IDs!!!\n");
    exit(1);
  }
  //fprintf(stderr, "next user label: %08x\n", next_user_label_id);
  return next_user_label_id;
}



int get_next_label_id(void) {
  next_label_id += 1;
  if (next_label_id >= MAX_LABEL_IDS) {
    fprintf(stderr, "* Ran out of label IDs!!!\n");
    exit(1);
  }
  //fprintf(stderr, "next lab: %08x\n", next_label_id);
  return next_label_id;
}

int get_icode(FILE *f) {
  int c = fgetc(f);
  if (c != EOF) {
    c &= 0xFF;
#ifdef NEVER
    if (PARM_DEBUG) {
      if ('!' <= c && c <= '~') {
        fprintf(stderr, "'%c':\n", c);
      } else if ((('!'|128) <= (c|128)) && ((c|128) <= ('~'|128))) {
        fprintf(stderr, "128+'%c':\n", c&127);
      } else {
        fprintf(stderr, "%d:\n", c);
      }
    }
#endif
  }
  return(c);
}

int getbyte(void) {
  int c;
  c = get_icode(icode_file); assert(c != EOF);
  return c&255;
}

int getshort(void) {
  int i, c;
  int word = 0;
  for (i = 0; i < 2; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  return word;
}

int gettag(void) { // alias of getshort just to emphasise type of the object is a tag. May later include range checking.
  //int i = get_icode(icode_file); // force byte order
  //return (i << 8) + get_icode(icode_file);
  return getshort();
}

int getwordconst(void) {
  int i, c;
  int word = 0;
  for (i = 0; i < 4; i++) {
    word = word << 8 | (c = get_icode(icode_file));
    assert(c != EOF);
  }
  return word;
}

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);
}

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

char *getimpstring(void) {
  static char local[256*2+3];
  int i, c, len;
  char *s = local;
  len = get_icode(icode_file); assert(len != EOF);
  //*s++ = '"';
  for (i = 0; i < len; i++) {
    c = get_icode(icode_file); assert(c != EOF);
    // Working on this at the moment: dump_imp_to_c_string in ast.h
    /*if (c == '"') {
      *s++ = '\\';
      *s++ = c;
    } else if (c == '\n') {
      *s++ = '\\';
      *s++ = 'n';
      } else */ *s++ = c;
  }
  //*s++ = '"';
  *s++ = '\0';
  return(local);
}

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

short int getforwardlab_from_tag(short int *tag) {
   /* target for an if (..) goto skip; inst(); skip: */
   int i, c;
   short int lab;
   int word = 0;

   for (i = 0; i < 2; i++) {
      word = word << 8 | (c = get_icode (icode_file));
      assert (c != EOF);
   }
   if (word < 0 || word >= MAX_LABEL_IDS) {
     fprintf(stderr, "word = %d (%08x)\n", word, word);
     assert((word >= 0) && (word+2 < MAX_LABEL_IDS));
   }
   *tag = word;

   lab = label_id[word];
   /* there may be several jumps to this later label */
   if (lab == 0) {
     lab = label_id[word]   = get_next_label_id();
     //           label_id[word+1] = get_next_label_id();
     //           label_id[word+2] = get_next_label_id();
   }
   return lab;
}

// #####################################################################################


char *source_indent = "                                                                // ";
char *comment_indent = "                        // ";
//static char *code_indent    = "        ";

void dump(char *s, ...) {
  va_list ap;
  //  if (suppress_perms) return;
  va_start(ap, s);
  vfprintf(output_file, s, ap);
  va_end(ap);
  fflush(output_file);
}

// just to make the source call intentions clearer.  They all do the same,
// apart from a different indentation level for each.
static char buffer[4096] = {'\0'};

// ***WARNING*** I think this code is still slightly broken in that it is not
// consistently and fully re-entrant as sometimes it wrongly prints the same
// parameter twice when in fact it was given two different parameters as inputs.
// For instance "%N %N" or "%s %s".
// That's not to say however that the problem may be elsewhere, such as in pooltostr for example,
// which I'll be checking first... right now... *TO DO* *doing!*

int suppress_perms = 0;

void dump_code(char *format, ...) {
  va_list ap;
  if (suppress_perms) return;
  char *s = format;
  char item_format[1024];
  //fprintf(stderr, "[%s]\n", format);
  va_start(ap, format);
  for (;;) {
    int c = *s++;
    if (c == 0) break;
    if (c == '%') {
      int f = *s++;
      if (f == 0) break;
      if (f == '%') {
        fprintf(output_file, "%%");
      } else {
        void *arg = va_arg(ap, void *);
        // Beware: some of these clash with little-used format codes from C library (eg %A)
        if (f == 'N') { // Node
          codegen((int)((intptr_t)arg));
        } else if (f == 'V') { // Variable
          if (REQUIRES_AUTO_DEREF((int)((intptr_t)arg))) arg = (void *)mktuple(AST_INDIRECT_THROUGH, (int)((intptr_t)arg)); 
          codegen/*_data*/((int)((intptr_t)arg));
        } else if (f == 'L') { // L-value
          codegen/*_data*/((int)((intptr_t)arg));
        } else if (f == 'R') { // R-value
          codegen/*_data*/((int)((intptr_t)arg));
        } else if (f == 'A') { // Address-of
          codegen/*_data*/(mktuple(AST_ADDRESS_OF, (int)((intptr_t)arg)) /*, WANT_ADDRESS_OF */);
        } else if (f == 'P') { // Pointer-fetch
          codegen/*_data*/(mktuple(AST_INDIRECT_THROUGH, (int)((intptr_t)arg)) /*, WANT_INDIRECT_THROUGH */);
        } else {
          char *p = item_format;
          *p++ = '%';
          for (;;) {
            *p++ = f;
            if (isalpha(f)) break;
            f = *s++;
          }
          *p = '\0';
          //fprintf(stderr, "  [%s]\n", item_format);
          fprintf(output_file, item_format, arg);
        }
      }
    } else
      fprintf(output_file, "%c", c);
  }
  va_end(ap);
}

char commentbuff[1024 * 1024];
char *cbp = commentbuff;

void dump_comment(char *s, ...) {
  va_list ap;
  if (suppress_icode) return;

  // Doesn't make sense to save these in the stack, but maybe saving them to an
  // array indexed by source line would work, which could be output along with
  // the actual source line.

  cbp += sprintf(cbp, "%s", comment_indent);
  va_start(ap, s);
  cbp += vsprintf(cbp, s, ap);
  va_end(ap);
  //cbp += sprintf(cbp, "%s", hashline);
}

void dump_imp_source(char *s, ...) {
  // insert the #line directive before every generated line.
  static int lastc = '\n';
  va_list ap;
  //  if (suppress_perms) return;
  fprintf(output_file, "%s", source_indent);
  va_start(ap, s);
  vsprintf(buffer, s, ap);
  va_end(ap);
  char *p = buffer;
  for (;;) {
    int c = *p++;
    if (c == '\0') break;
    if ((c == '\n') && (lastc == '\\')) fputc('&', output_file); // avoid problem with comment lined where \ is the last character...
    fputc(c, output_file);
    lastc = c;
    //if (c == '\n') fprintf(output_file, "%s", hashline);
  }
  fflush(output_file);
  buffer[0] = '\0';
}

void dump_imp_to_c_string(char *impstr) {
  fputc('"', output_file);
  for (int i = 0; i < strlen(impstr); i++) {
    int c = impstr[i];
    if (c == '"') {
      fputc('\\', output_file);
      fputc('"', output_file);
    } else if (c == '\n') {
      fputc('\\', output_file);
      fputc('n', output_file);
    } else if (c == '\b') {
      fputc('\\', output_file);
      fputc('b', output_file);
    } else if (c == '\f') {
      fputc('\\', output_file);
      fputc('f', output_file);
    } else if (c == '\r') {
      fputc('\\', output_file);
      fputc('r', output_file);
    } else if (c == '\t') {
      fputc('\\', output_file);
      fputc('t', output_file);
    } else if (c == '\v') {
      fputc('\\', output_file);
      fputc('v', output_file);
    } else if (c < 32 || c >= 127) {
      fprintf(output_file, "\\x%02X", ((unsigned char)c&255U));
    } else if (c == '\\') {
      fputc(c, output_file);
      fputc(c, output_file);
    } else {
      fputc(c, output_file);
    }
  }
  fputc('"', output_file);
}

void show_source_code(int target_line, char *fname) {
  static char *source_file_name[128];
  static FILE *source_file[128];
  static int next_free_source_fileno = 0;
  static int source_line[128];
  int this_file = -1;

  for (int i = 0; i < next_free_source_fileno; i++) {
    if (strcmp(fname, source_file_name[i]) == 0) {
      this_file = i;
      break;
    }
  }
  if (this_file < 0) {  // Could also be detected on LINE 0 (d == 0)?
    // New file just now included?

    this_file = next_free_source_fileno++;

    source_file_name[this_file] = strdup(fname);
    source_file[this_file] = fopen(fname, "r");
    if (source_file[this_file] == NULL) {
      fprintf(stderr, "Warning: cannot reopen listing file \"%s\" = %s\n", fname, strerror(errno));
    }
    source_line[this_file] = 0;  // start of new file.
  }

  if (source_file[this_file] != NULL) {
    /* Now output the source file lines up to line %d (first line is line 1.) */
    for (;;) {
#define MAX_LINE 1024
      extern char line[MAX_LINE + 1];
      int end;
      if (source_line[this_file] >= target_line) break;
      if (fgets(line, MAX_LINE, source_file[this_file]) == NULL) break;

      source_line[this_file] += 1;
      dump_imp_source("%6d  ", source_line[this_file]);

      line[MAX_LINE] = '\0';
      end = strlen(line);
      while (--end >= 0) {
        if (line[end] == '\r' || line[end] == '\n')
          line[end] = '\0';
        else
          break;
      }

      dump("%s", line);
      if (*line != '\0' && line[strlen(line)-1] == '\\') {
        dump("&\n");
      } else {
        dump("\n");
      }
    }
  }
}
