static char *rcs_version = "idec.c V$Revision: 1.20 $";
/*
    Read, potentially modify, and re-output an icode file as generated by the modified
    pass1-flat.c used in i2c.
    TO DO: Does not yet output merged source code correctly for %include'd files.
           Does not handle perms.h (which does not record line numbers in the icode file)
           Does not yet implement reordering that will be necessary for handling
           procedures passed as parameters to other procedures.
           Lines with 2 statements (eg line 147 in hal70.c) don't print in the listing
           correctly following the source line.
    Note: we may want to suppress the decoded ICODE output of the perms to the screen
          for human viewing, but we *must* pass through the ICODE of the perms to the
          rewritten ICODE file for the updated output file to be compatible with the
          input file.  Currently standard icode files with perms display wrongly.
 */
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>

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

// for selecting non-standard icode format
static int opt_extra_commas = 0;
static int opt_extended_line = 0;
static int opt_suppress_perms = 1; // implemented by not outputting anything before the first 'O' (line) code.
static int opt_icode_style = 0; // 0x77 0x12C 0x2026
static int opt_recode_wanted = 0;
static int opt_verbose = 0;
static char implicit_single_source_file[512];

static int next_icode = 0;
static int icode[65536]; // pointers to the next icode instruction in mem[].  May take up several bytes.
static int icode_length[65536]; // pointers to the next icode instruction in mem[].  May take up several bytes.
static char *icode_needs_replacing[65536]; // a DEF FN without the details.  Look up the fn with get_fn and output full version with params!
static unsigned char mem[MAX_ICODE]; // copy of icode file in memory.
static unsigned char replacement[MAX_ICODE]; // when outputting modified icode, if replacement[addr] != 0, use that value instead of mem[addr]

int flagged_for_replacement(char *s) {
  int i;
  for (i = 0; i < 65536; i++) { // ***VERY VERY*** inefficient.  Placeholder code.
    if ((icode_needs_replacing[i] != NULL) && (strcmp(icode_needs_replacing[i], s) == 0)) return TRUE;
  }
  return FALSE;
}

static int inpos = 0;

static char *indent = "        ";
static FILE *icode_file;
static FILE *source_file = NULL;
#define MAX_LINE 1024
static char line[MAX_LINE+1];

// A chunk corresponds roughly to a source statement, and the biggest
// imp source I have here is under 11K lines.
#define MAX_CHUNKS (16*1024)
static int next_free_spec = 0;
static int specs[MAX_CHUNKS];

void push_spec(int spec) { specs[next_free_spec++] = spec; }
int pop_spec(void) { if (next_free_spec > 0) return specs[--next_free_spec]; else return specs[0]; }

static int next_free_form = 0;
static int forms[MAX_CHUNKS];

void push_form(int form) { forms[next_free_form++] = form; }
int pop_form(void) { if (next_free_form > 0) return forms[--next_free_form]; else return forms[0]; }

#define MAX_FNS 1024
static char *saved_fn[MAX_FNS];
static int saved_icode_idx[MAX_FNS]; // MUST be initialised to all zeroes
static int next_free_saved = 0;
void save_fn(int icode_idx, char *fname) {
  if (next_free_saved == MAX_FNS) {
    fprintf(stderr, "* Must increase MAX_FNS\n");
    exit(EXIT_FAILURE);
  }
  saved_fn[next_free_saved] = strdup(fname);
  saved_icode_idx[next_free_saved] = icode_idx;
  //fprintf(stdout, "/* SAVED ICODE INDEX %d FOR %s */", icode_idx, fname);
  next_free_saved += 1;
}

int get_fn(char *fname) {
  int idx;
  for (idx = 0; idx < next_free_saved; idx++) {
    if (strcmp(saved_fn[idx], fname)==0) return saved_icode_idx[idx];
  }
  return -1;
}

static int unique_block_no = 0;
static char current_blockname[1024] = { '\0' };

char *next_blockname(int lev) {
  static char tmp[128];
  sprintf(tmp, "lev%did%d", lev, unique_block_no);
  unique_block_no += 1;
  return tmp;
}

void push_blockname(char *s) {
  sprintf(current_blockname+strlen(current_blockname), "_%s", s);
}

void pop_blockname(void) {
  char *p = strrchr(current_blockname, '_');
  if (p) *p = '\0';
}

typedef struct CHUNK CHUNK;
struct CHUNK {
  char *filename;  // everything in a chunk must have come from the same file.

  // care has to be taken for pathological situations such as:  %integer a; %include "file.inc"; %integer b
  // where the line number has to be output twice: once before the included code and again after it.

  // assigned on first pass:
  int first_line;
  int first_icode_idx;

  // assigned on second pass:
  int last_line;
};

static int next_free_chunk = 0;
static CHUNK chunks[MAX_CHUNKS];

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>",
"<32>", "OR    ", "JUMPIFD", "BNE   ", "DEF   ", "XOR   ", "AND   ", "PUSHS ",
"ble   ", "bge   ", "MUL   ", "ADD   ", "+     ", "SUB   ", "CONCAT", "QUOT  ",
"<'0'>", "<'1'>", "<'2'>", "<'3'>", "<'4'>", "<'5'>", "<'6'>", "<'7'>",
"<'8'>", "<'9'>", "LOCATE", "END   ", "blt   ", "beq   ", "bgt   ", "JUMPIF",
"PUSH  ", "INIT  ", "REPEAT", "JUMPIFA", "PUSHR ", "CALL  ", "GOTO  ", "ALIAS ",
"BEGIN ", "select-input-2", "JUMP  ", "FALSE ", "LABEL ", "MAP   ", "PUSHI ", "LINE  ",
"PLANT ", "DIVIDE", "RETURN", "ASSVAL", "TRUE  ", "NEGATE", "RESULT", "SJUMP ",
"IEXP  ", "DEFAULT", "ASSREF", "LSH   ", "NOT   ", "RSH   ", "PROC  ", "SLABEL",
// DEFSW is an added I-Code (actually a restored one, as this code existed in older compilers)
// for the default switch label.  Imp77, by the time this pass1 was in use, had abandoned
// an explicit code for this and was outputting all the missing labels explicitly, but
// that scheme will not work for i2c.

// New code 'REDEF' is identcal to 'DEF' except that it is only for internal use by i2c
// and should not cause any C to be output.
"DEFSW ", "ACCESS", "BOUNDS", "MCODE ", "DIM   ", "EVENT ", "FOR   ", "REDEF ", /* 'g' */
"ALTBEG", "INDEX ", "JAM   ", "bf    ", "LANG  ", "MONITOR", "SELECT", "ON    ",
"ASSPAR", "SUBA  ", "RESOLVE", "STOP  ", "bt    ", "ADDA  ", "MOD   ", "MCODE ",
"REXP  ", "DIAG  ", "CONTROL", "START  (", "ALT |", "FINISH )", "ALTALT", "<127>",
// ALTALT is an escape code which is followed by alternative encoding of START/ALT/FINISH
// (this is in the original pass1 - not sure why the standard encoding was changed unless
//  it was because PSR was running out of printable opcodes and needed some more for the
//  extensions to I-Code needed when adding new languages?)
#define OP_UNDEF 128 // replacement for DEF in rewritten icode so that it is not output as C
                     // This is for the procedure parameter procedure which is repeated inside
                     // the procedure after the abbreviated version was in the FP list.
                     // This rewrite puts the full version inside the parameter list as well
                     // as following it: the first instance is for being output in C and
                     // the second instance is for i2c to use when calling the passed procedure
                     // inside the enclosing procedure.  So 'UNDEF' is an invisible 'DEF' -
                     // handled exactly as DEF was before, but not printed out.
"UNDEF ", "<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>",
};

static char *specialdesc[] = {
  /*  0: */ "<special:0>",
  /*  1: */ "byte-integer",
  /*  2: */ "short-integer",
  /*  3: */ "long-real",
};

static char *typedesc[16] = {
  /*  0: */ "<type:0>",
  /*  1: */ "integer",
  /*  2: */ "real",
  /*  3: */ "string",
  /*  4: */ "record",
  /*  5: */ "Boolean",
  /*  6: */ "set",
  /*  7: */ "byte-enumerated",
  /*  8: */ "short-enumerated",
  /*  9: */ "pointer",
  /* 10: */ "char",
  /* 11: */ "<type:11>",
  /* 12: */ "<type:12>",
  /* 13: */ "<type:13>",
  /* 14: */ "<type:14>",
  /* 15: */ "general-area"
};

static char *formdesc[16] = {
  /*  0: */ "<form:0>",
  /*  1: */ "simple",
  /*  2: */ "name",
  /*  3: */ "label",
  /*  4: */ "record-format",
  /*  5: */ "<form:5>",
  /*  6: */ "switch",
  /*  7: */ "routine",
  /*  8: */ "function",
  /*  9: */ "map",
  /* 10: */ "predicate",
  /* 11: */ "array",
  /* 12: */ "<form:12>",
  /* 13: */ "name-array",
  /* 14: */ "name-array-name",
  /* 15: */ "<form:15>",
};

static char *linkagedesc[] = {
  /*  0: */ "auto",
  /*  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",
//    32, "-unass"
//};

#define T_INTEGER 1
#define T_REAL 2
#define T_STRING 3
#define T_RECORD 4
#define T_BOOLEAN 5
#define T_SET 6
#define T_BYTE_ENUMERATED 7
#define T_SHORT_ENUMERATED 8
#define T_POINTER 9
#define T_CHAR 10
#define T_GENERAL 15

#define SPECIAL_DEFAULT 0
#define SPECIAL_BYTE_INT 1
#define SPECIAL_SHORT_INT 2
#define SPECIAL_LONG_REAL 3

#define F_RECORDFORMAT 4
#define F_ROUTINE 7
#define F_FN 8
#define F_MAP 9
#define F_PREDICATE 10

#define FORM (tf & 0xF)
#define IS_PROCEDURE ((FORM == F_ROUTINE) || (FORM == F_FN) || (FORM == F_MAP) || (FORM == F_PREDICATE))
#define IN_ENCLOSING_PROCEDURE (forms[next_free_form-1] != F_RECORDFORMAT)
#define IS_SPEC_ONLY (((ostate) & 8) != 0)
#define IS_INDIRECT (((ostate) & 16) != 0)
#define IS_UNASS (((ostate) & 32) != 0)

static char *p1, *p2, *p3;
static int opcode, d;
static char icode_filename[256], source_filename[256];
static int IN_START_FINISH_BLOCK = 0;

static int blocklevel = 0;

int skipbyte(void) {
  int c = fgetc(icode_file);
  if (c == EOF) return c;
  c &= 0xFF;
  mem[inpos++] = c;
  return(c);
}

int skipshort(void) {
  int i = skipbyte();
  return (i << 8) + skipbyte();
}

void debug_chunk(char *mess, int idx) {
  CHUNK *chunk = &chunks[idx];
  fprintf(stdout, "\n\n%s %d: filename=%s from %d (%d):\n",
          mess, idx,
          chunk->filename,
          chunk->first_line, chunk->first_icode_idx);
}

void pass1(void) {
  // This phase is *only* to ensure that the decoded icodes (and later
  // decoded assembly code) are output in sync with the source listing.
  // The CHUNK structure corresponds roughly to source lines.

  next_free_chunk = 0;
  
  CHUNK *chunk;
  chunk = &chunks[next_free_chunk];
  chunk->filename = "perms.inc";
  chunk->first_line = 1;
  chunk->first_icode_idx = next_icode;
  chunk->last_line = 999999;

  //if (next_free_chunk <= 3) debug_chunk("makechunk", next_free_chunk);
  next_free_chunk  += 1;
  for (;;) {
    
    icode[next_icode++] = inpos;
    opcode = skipbyte();
    if ((opcode == EOF) || (opcode == '\n')) break;
    
    switch(opcode) {
    case '~': (void)skipbyte();
              break;
      
    case '|': blocklevel -= 1;
    case '{': IN_START_FINISH_BLOCK = 1;
    case 'H': blocklevel += 1;
              break;
    case '}': IN_START_FINISH_BLOCK = 0;
    case ';': blocklevel -= 1;
              break;
    case '$': case 'g':
              {
              (void)skipshort();
              int c;
              for (;;) {
                c = skipbyte();
                if ((isalpha(c) && isupper(c)) || isdigit(c)) { } else break;
              }
              inpos -= 1; ungetc(c, icode_file);

              int tf, type, form, format, ostate, special, i;
              skipbyte(); // ,
              tf = skipshort();
              skipbyte(); // ,
              format = skipshort();
              skipbyte(); // ,
              ostate = skipshort();
              type = (tf >> 4) & 0xF;
              form = tf & 0xF;
              special = SPECIAL_DEFAULT;
              if (type == T_INTEGER) {
                if (format == 2)
                  special = SPECIAL_BYTE_INT;
                else if (format == 3)
                  special = SPECIAL_SHORT_INT;
              } else if (type == T_STRING && format == 4) {
                special = SPECIAL_LONG_REAL;
              }
              if (!IN_START_FINISH_BLOCK && IS_PROCEDURE && !IS_SPEC_ONLY) blocklevel += 1;
              }
              break;
    case '"': case 'C': case '?':
              (void)skipbyte(); (void)skipshort();
              break;

    case '\'': case 'G':
              {
                int i, c, len;
                len = skipbyte();
                for (i = 0; i < len; i++) {
                  c = skipbyte();
                }
              }
              break;

    case 'D':
              {
                int i, c, byte, len;
                byte  = skipbyte();
                len   = skipbyte();
                c     = skipbyte();
                for (i = 1; i <= len; i++) {
                  c = skipbyte();
                  if (c == '@') break;
                }
                if (c == '@') (void)skipshort();
              }
              break;
              
    case 'N':
              for (int i = 0; i < 4; i++) (void)skipbyte();
              break;

    case 'd': case 'o':
              (void)skipshort();
              skipbyte(); // ,
    case ':': case 'B': case 'F': case 'J': case 'L': case 'f': case 'k': case 't':
    case '@': case '^': case 'n': case ',': case '`': case 'W': case '_': case 'Y':
    case 'A': case 'y': case 'z': case 'e': case 'l': case 'r':
              (void)skipshort();
              break;

    case 'O':
              {
                int this_icode = next_icode-1;
                d = skipshort();

                static char fname[256*2+3];
                int i, c, len;
                char *s = fname;
                if (opt_extended_line) {
                  len = skipbyte();
                  for (i = 0; i < len; i++) {
                    c = skipbyte();
                    *s++ = c;
                  }
                  *s++ = '\0';
                } else {
                  strcpy(fname, implicit_single_source_file);
                }

                if (next_free_chunk == 1) {
                  chunk = &chunks[next_free_chunk];
                  chunk->filename = strdup(fname);
                  chunk->first_line = 1;
                  chunk->first_icode_idx = this_icode;
                  chunk->last_line = 999999;
                } else {
                  chunk = &chunks[next_free_chunk];
                  chunk->filename = strdup(fname);
                  chunk->first_line = d;
                  chunk->first_icode_idx = this_icode;
                  chunk->last_line = 999999;
                }
//if (next_free_chunk <= 3) debug_chunk("makechunk", next_free_chunk);
                next_free_chunk += 1;
              }
              break;

    case 'w': case 'c':
              {
                int i;
                for (;;) {
                  int c;
                  for (;;) {
                    c = skipbyte();
                    if ((c == ' ') || (c == ';')) {
                      i = c;
                      break;
                    }
                  }
                  if (i == ';') break;
                  (void)skipshort();
                }
              }
              break;

    default:
              break;

    }
  }
  icode[next_icode] = inpos;


  chunk = &chunks[next_free_chunk++];
  chunk->filename = "";
  chunk->first_line = 0;
  chunk->first_icode_idx = next_icode;
  chunk->last_line = 999999;
}

//-------------------------------------------------------------------------------------------------------

static int memp;

static char *TagLookup[0x10000];

// too many variations of icode-reading procedures below.  This needs to be cleaned up
// and rationalised and simplified down to just the basics necessary...

unsigned char get_icode(void) {
  int c = mem[memp++]; // The *only* place where data is read from in the second pass over the icode.
  //fprintf(stdout, "> %02x\n", c);
  return(c);
}

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

  for (;;) {
    c = get_icode();
    assert(c != EOF);
    if ((isalpha(c) && isupper(c)) || isdigit(c)) { } else break;
    *s++ = c;
  }
  *s = '\0';
  memp -= 1; // ungetc(c, icode_file);
  //fprintf(stdout, "< %02x\n", c);
  return(local);
}

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

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

char *getimpstring(void) {
  static char local[256*2+3];
  int i, c, len;
  char *s = local;
  len = get_icode();
  *s++ = '"';
  for (i = 0; i < len; i++) {
    c = get_icode();
    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 i, c, len;
  char *s = local;
  for (;;) {
    c = get_icode();
    if ((c == ' ') || (c == ';')) {
      *s++ = '\0';
      *cp = c;
      return(local);
    }
    *s++ = c;
  }
}

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());
  }
  sprintf(local, "%04x", word);
  return(local);
}

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());
  }
  if (d != NULL) *d = word;
  sprintf(local, "%0d", word);
  return(local);
}

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());
  }
  sprintf(local, "L_%04x", word);
  return(local);
}

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

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());
  }
  if (TagLookup[word] == NULL) {
    sprintf(local, "V_%04x", word);
  } else if (*TagLookup[word] != '\0') {
    strcpy(local, TagLookup[word]);
  } else {
    sprintf(local, "V_%04x", word);
  }
  return(local);
}

int gettag(void) {
  int i = get_icode(); // force byte order
  return (i << 8) + get_icode();
}

void block(int lev) {
  while (lev --> 0) {
    fprintf(stdout, "   ");
  }
}

static int opcode_p;
static int POP_BLOCKNAME_ON_FINISH = FALSE;

void show_icode(int icode_idx, int display) {

    opcode_p = memp;
    opcode = get_icode();

    switch(opcode) {

    case '\n': break; // end of file

    case ';': /* END */
              {
              blocklevel -= 1;
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s {lev=%d --> %d}", indent, opcode, icode_name[opcode], current_blockname, blocklevel+1, blocklevel);
              pop_blockname();
              }
              break;

    case '~':
              {
                int pending = get_icode();
                if (display) block(blocklevel);
                if ((' ' + 1 <= pending) && (pending <= '~')) {
                  if (pending == 'A') {
                    if (display) fprintf(stdout, "%s%c %s ~A %s", indent, opcode, icode_name[opcode], "ALTBEG");
                  } else if (pending == 'B') {
                    if (display) fprintf(stdout, "%s%c %s ~B %s", indent, opcode, icode_name[opcode], "ALTEND");
                  } else if (pending == 'C') {
                    if (display) fprintf(stdout, "%s%c %s ~C %s", indent, opcode, icode_name[opcode], "ALT");
                  } else {
                    if (display) fprintf(stdout, "%s%c %s ??? %s", indent, opcode, icode_name[opcode], "ALTALT1");
                  }
                } else {
                  if (display) fprintf(stdout, "%s%c %s pending=%d %s", indent, opcode, icode_name[opcode], pending, "ALTALT2");
                }
              }
              break;
      
    case 'H': /* BEGIN */
              if (display) block(blocklevel);
              push_blockname(next_blockname(blocklevel));
              if (display) fprintf(stdout, "%s%c %s %s {lev=%d --> %d}", indent, opcode, icode_name[opcode], current_blockname, blocklevel, blocklevel+1);
              blocklevel += 1;
              break;

    case '{': /* START */
              {
              int form;
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s",
                                   indent, opcode, icode_name[opcode]);
                                   //blocklevel, forms[next_free_form-1]);
              
              push_form(form = pop_form()); // form was pushed by '$'
              if (display) {
                if (form == F_RECORDFORMAT) {
                  fprintf(stdout, " RECORDFORMAT");
                } else {
                  fprintf(stdout, " FORMAL PARAMETERS  {lev=%d --> %d, nested=%d --> %d}", blocklevel, blocklevel+1, IN_START_FINISH_BLOCK, IN_START_FINISH_BLOCK+1);
                }
              }
              IN_START_FINISH_BLOCK += 1;
              blocklevel += 1;
              }
              break;

    case '|': /* ALT */
              // Only relevant to record definitions, not parameter lists.
              blocklevel -= 1;
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s", indent, opcode, icode_name[opcode]);
              blocklevel += 1;
              break;

    case '}': /* FINISH */
              {
              int form;
              blocklevel -= 1;
              push_form(form = pop_form());
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s ", indent, opcode, icode_name[opcode]);
              if (form != F_RECORDFORMAT) if (display) fprintf(stdout, "{lev=%d}", blocklevel);
              IN_START_FINISH_BLOCK -= 1;
              int is_spec = pop_spec();
              push_form(form = pop_form());
              if (form == F_RECORDFORMAT) {
              } else {
                if (POP_BLOCKNAME_ON_FINISH) {
                  pop_blockname();
                  POP_BLOCKNAME_ON_FINISH = FALSE;
                }
                if (is_spec) {
                  blocklevel -= 1; // extra level due to spec                    DOESN'T QUITE WORK WITH REWRITTEN ICODE FILE YET **************************
                                                                                 if (blocklevel < 0) blocklevel = 0;
                  if (display) fprintf(stdout, " {lev=%d (due to %%spec)}", blocklevel);
                }
              }
              }
              break;

    case '$': case 'g': /* {RE}DEF TAG TEXT TYPE FORM SIZE SPEC PREFIX */
              {
/*

         Instruction:  Define <tag> [id] <a> <b> <c>

         Effect:       A new tag value is created.

                       <tag> defines the tag index which  will  be  used  to
                             select the value.

                       [id]  specifies the actual identifier associated with
                             the described object.  It is a sequence of zero
                             or more characters terminated by a comma.  This
                             identifier will be used for external linkage if
                             necessary  unless  overridden by an Alias. [id]
                             will  also  be  used  for  run-time  diagnostic
                             information.

                       <a>   A two-byte value: <a> = T<<4+F where:
                             T = 0 : void
                                 1 : integer            {qualified by <b>}
                                 2 : real               {qualified by <b>}
                                 3 : string             {maximum length <b>}
                                 4 : record             {format <b>}
                                 5 : boolean
                                 6 : set
                                 7 : 8-bit-enumerated   {format <b>}
                                 8 : 16-bit-enumerated  {format <b>}
                                 9 : pointer
                                10 : char
                             11-15 : undefined          {error}

                             F = 0 : void
                                 1 : simple                  {byte}
                                 2 : indirect                {bytename}
                                 3 : general label
                                 4 : recordformat
                                 5 : undefined               {error}
                                 6 : switch
                                 7 : routine
                                 8 : function
                                 9 : map
                                10 : predicate
                                11 : array                   {array}
                                12 : array indirect          {arrayname}
                                13 : indirect array          {namearray}
                                14 : indirect array indirect {namearrayname}
                                15 : undefined               {error}

                       <b>   If   T  is  INTEGER  <b>  takes  the  following
                             meanings:

                             <b> =       1, full range
                                         2, range 0..255
                                         3, range -32768..32767

                             If T is REAL <b> takes the following meanings:

                             <b> =       1, normal precision
                                         4, double precision

                             If T is STRING <b> gives the maximum length  of
                             the string.
                             If  T  is  RECORD  <b>  gives  the  tag  of the
                             corresponding recordformat.
                             If T is enumerated <b> gives  the  tag  of  the
                             dummy  format  used  to identify the enumerated
                             value identifiers.

                       <c>   is a two-byte value: U<<5+I<<4+S<<3+X where:
                                U is 1 check the object for unassigned
                                     0 otherwise
                                I is 1 if the object is an indirect object,
                                     0 otherwise
                                S is 1 if this is a spec,
                                     0 otherwise
                                X  = 0 :: automatic (stack) allocation
                                     1 :: own
                                     2 :: constant
                                     3 :: external
                                     4 :: system
                                     5 :: dynamic
                                     6 :: primitive
                                     7 :: permanent

                             An indirect object (I=1) differs  from  F=2  in
                             that F=2 implies that the actual object created
                             will  be  a  pointer  and  will be dereferenced
                             whenever used unless explicit action  is  taken
                             (e.g.  use  of  Assign-Reference).   If  I=1  a
                             pointer will be created (usually as an integer)
                             and will be treated as an integer (or  address)
                             with no automatic dereferencing taking place.

         Notes:        The  tag  values  within  a block should be dense and
                       preferably consecutive.   All  tag  values  within  a
                       block  must  have values greater than the maximum tag
                       value yet defined within the enclosing block.
                       Tag definitions remain valid until  the  end  of  the
                       enclosing block.
                       The  tag values used within a recordformat definition
                       must all be zero; the fields of a record are selected
                       by their position in the  format,  numbered  starting
                       from one.

                               x = illegal combination

                                                 1 1 1 1 1
                         F = 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4
                            *-----------------------------*
                    T = 0   | |x| | |x|x| | |x| | |x| |x| |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        1   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        2   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        3   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        4   |x| | |x| |x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        5   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        6   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        7   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        8   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                        9   |x| | |x|x|x|x|x| | |x| | | | |
                            |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
                       10   |x| | |x|x|x|x|x| | |x| | | | |
                            *-----------------------------*

*/
              int tag;
              p1 = getvartag(&tag); p2 = getname();
              TagLookup[tag] = strdup(p2);

              int tf, type, form, format, ostate, special, i, linkage;
              int comma;

              if (opt_extra_commas)  { comma = get_icode(); assert(comma == ','); } //skip_optional(','); // NOT SAFE.
              tf = gettag(); // a
              if (opt_extra_commas)  { comma = get_icode(); assert(comma == ','); } //skip_optional(','); // NOT SAFE.
              format = gettag(); // b
              if (opt_extra_commas)  { comma = get_icode(); assert(comma == ','); } //skip_optional(','); //  "    "
              ostate = gettag(); // c

              linkage = ostate&7;
              type = (tf >> 4) & 0xF;
              form = tf & 0xF;

/*
                       <b>   If   T  is  INTEGER  <b>  takes  the  following
                             meanings:

                             <b> =       1, full range
                                         2, range 0..255
                                         3, range -32768..32767

                             If T is REAL <b> takes the following meanings:

                             <b> =       1, normal precision
                                         4, double precision

                             If T is STRING <b> gives the maximum length  of
                             the string.
                             If  T  is  RECORD  <b>  gives  the  tag  of the
                             corresponding recordformat.
                             If T is enumerated <b> gives  the  tag  of  the
                             dummy  format  used  to identify the enumerated
                             value identifiers.
*/

              /* handle bizarre type encoding scheme!: */
              special = SPECIAL_DEFAULT;
              if (type == T_INTEGER) {
                if (format == 2)
                  special = SPECIAL_BYTE_INT;
                else if (format == 3)
                  special = SPECIAL_SHORT_INT;
              } else if (type == T_STRING && format == 4) {
                special = SPECIAL_LONG_REAL;
              }

              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c ", indent, opcode);

              if (IS_PROCEDURE || form == F_RECORDFORMAT) push_form(form); // things that will be followed by START/FINISH blocks

/*XXX*/       if (/*!*/ IN_START_FINISH_BLOCK && IN_ENCLOSING_PROCEDURE && IS_PROCEDURE /*&& !IS_SPEC_ONLY*/) {
                push_spec(IS_SPEC_ONLY);
                //blocklevel += 1;
              } else if (!IN_START_FINISH_BLOCK && IS_PROCEDURE && !IS_SPEC_ONLY) {
                blocklevel += 1;
              }
              if (IS_PROCEDURE /*&& !IS_SPEC_ONLY*/) push_blockname(p2);
              

              // DEF    FN p1=V_0080 a/tf=18 b/format=1 c/ostate=10  type=1 (integer) form=8 (function) special=0 (<special:0>) linkage=0 (auto) spec=0 indirect=1 unass=0
              char extra[1024];
              extra[0] = '\0';
              if (IN_START_FINISH_BLOCK && IN_ENCLOSING_PROCEDURE && IS_PROCEDURE) {
                sprintf(extra, "(PROCEDURE PARAMETER %s) ", current_blockname);
                if (opt_recode_wanted) strcat(extra, "***** REPLACEMENT CONTENDER ***** ");
                icode_needs_replacing[icode_idx] = strdup(current_blockname);
              } else if (IS_PROCEDURE) {
                sprintf(extra, "(%s) ", current_blockname);
                if (flagged_for_replacement(current_blockname)) {
                    save_fn(icode_idx, current_blockname);
                    replacement[icode[icode_idx]] = 'g'; // 'REDEF'
                    if (opt_recode_wanted) strcat(extra, "***** MASTER ****** ");
                }
              }

              if (display) fprintf(stdout,
                      "%s %s %sp1=%s a/tf=%x b/format=%x c/ostate=%x  type=%d (%s) form=%d (%s) special=%d (%s) linkage=%d (%s) spec=%d indirect=%d unass=%d",
                      icode_name[opcode],
                      //blocklevel,
                      p2, extra, p1,
                      tf, format, ostate,
                      type, typedesc[type],
                      form, formdesc[form],
                      special, specialdesc[special],
                      linkage, linkagedesc[ostate&7],
                      IS_SPEC_ONLY, IS_INDIRECT, IS_UNASS);
              //if (IN_START_FINISH_BLOCK && !IS_PROCEDURE) if (display) fprintf(stdout, " ]");

              if (IS_PROCEDURE && IS_SPEC_ONLY) {
                // pop_blockname();
                POP_BLOCKNAME_ON_FINISH = TRUE;
              } else if ((IN_START_FINISH_BLOCK && IN_ENCLOSING_PROCEDURE && IS_PROCEDURE)) {
                pop_blockname();
              }
              // The "&& IS_SPEC_ONLY" part means that the function name in the function parameter doesn't include the name of
              // the function that takes that parameter.  This means that when trying to match the incomplete spec with the
              // full spec that follows later, we can't match based on the fully-scoped function name.  So I'll have to remove
              // the "&& IS_SPEC_ONLY" and pop the block name at the FINISH of the START/FINISH block. (TO DO)
              
              extra[0] = '\0';
              
              }
              break;

    case ',': (void)getshort();//if (display) fprintf(stdout, "%s%c %s 0x%s   ???", /* TEMP HACK */ indent, opcode, icode_name[opcode], getshort());
              break;

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

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

    case '\'': /* PUSHS sconst */
              {
              char *sc = getimpstring();
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s",
                      indent, opcode, icode_name[opcode], sc);
              }
              break;

    case 'G': /* ALIAS */
              {
              char *alias = getimpstring();
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s",
                      indent, opcode, icode_name[opcode], alias);
              }
              break;

    case 'D': /* PUSHR rconst */
              {
                int i, c, byte, len;
                if (display) block(blocklevel);
                if (display) fprintf(stdout, "%s%c %s ", indent, opcode, icode_name[opcode]);
                byte  = get_icode();
                len   = get_icode();
                if (display) fprintf(stdout, "code=%d len=%d \"", byte, len);
                c = get_icode();
                for (i = 1; i <= len; i++) {
                  c = get_icode();
                  if (c == '@') break;
                  if (display) fprintf(stdout, "%c", c);
                }
                if (c == '@') {
                  char *rc = getshort();
                  if (display) fprintf(stdout, " @ (signed short)0x%s\"", rc);
                } else {
                  if (display) fprintf(stdout, "\"");
                }
              }
              break;

    case 'N': /* PUSHI iconst */
              {
              char *ic = getwordconst();
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s",
                      indent, opcode, icode_name[opcode], ic);
              }
              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) */
              {
              char *lab = getlab();
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s",
                      indent, opcode, icode_name[opcode], lab);
              }
              break;

    case 'O': /* LINE decimal */
              {
                if (display) block(blocklevel);
                //suppress_perms = FALSE;
                getshortdecimal(&d);
                // EXTENSION FOR C-PASS1:
                char *fname = "";
                if (opt_extended_line) fname = getimpstring();
                if (display) fprintf(stdout, "%s%c %s %d %s", indent, opcode, icode_name[opcode], d, fname);
              }
              break;

    case '@': /* PUSH tag */
    case '^': /* PROC tag */
              {
              char *tag = getvar();
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s", indent, opcode, icode_name[opcode], tag);
              }
              break;

    case 'n': /* SELECT tag */
              {
              int tag;
              char *tags = getvartag(&tag);
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %d", indent, opcode, icode_name[opcode], tag);
              }
              break;

    case '`': /* defsw */         // EXTENSION FOR C-PASS1: default switch label
    case 'W': /* SJUMP sd */
    case '_': /* SLABEL sd */
              {
              char *sd = getshortdecimal(NULL);
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s", indent, opcode, icode_name[opcode], sd);
              }
              break;

    case 'd': /* DIM short,short */
              {
              int comma;
              if (display) block(blocklevel);
              p1 = strdup(getshort()); comma = get_icode(); assert(comma == ','); p2 = getshort();
              if (display) fprintf(stdout, "%s%c %s %s %s", indent, opcode, icode_name[opcode], p1, p2);
              free(p1);
              }
              break;

    case 'Y': /* DEFAULT short */
    case 'A': /* INIT short */
              {
              char *sd = getshortdecimal(NULL);
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s %s", indent, opcode, icode_name[opcode], sd);
              }
              break;

    case 'y': /* DIAG short */
    case 'z': /* CONTROL short */
    case 'e': /* EVENT short */
    case 'l': /* LANG short */
              {
              char *sh = getshort();
              if (display) block(blocklevel);
              //if ((opcode == 'l') && no_perms) suppress_perms = TRUE;
              if (display) fprintf(stdout, "%s%c %s %s", indent, opcode, icode_name[opcode], sh);
              }
              break;

    case 'o': /* ON byte short label */ /* BUG! wrong data */
              {
                int comma;
                if (display) block(blocklevel);
                p1 = strdup(getshort());
                if (opt_extra_commas) {
                comma = get_icode();
                if (comma != ',') {
                  if (display) fprintf(stderr, "\n* expected ',' in %%on statement, got %d ('%c')\n", comma, comma);
                  if (display) fprintf(stdout, "\n* expected ',' in %%on statement, got %d ('%c')", comma, comma);
                }
                }
                p2 = getlab();
                if (display) fprintf(stdout, "%s%c %s MASK=%s %s", indent, opcode, icode_name[opcode], p1, p2);
              }
              break;

    case 'r': /* RESOLVE m */
              {
                if (display) block(blocklevel);
        /*
r flag   String Resolution.  Flag is a three bit number, the
         bits representing:
              4    - conditional resolution.
              2    - first destination present.
              1    - second destination present.

         The resolution sets the condition code to true for
         successful resolution, and false for failure.
         The various operands are on the top of the stack.

         e.g.      S -> A.(B).C        @s@a@b@c r3

                   S -> A.(B)          @s@a@b r1

                   stop if S -> (B).C  @s@b@c r5k4s:4

                 */
                // result is tested by BF or BT!
                int d;
                char *flags = getshortdecimal(&d);
                if (display) fprintf(stdout, "%s%c %s flags=%04x", indent, opcode, icode_name[opcode], d&0xFFFF);
              }
              break;

    case 'w': /* SUBA - documentation failure??? file uses 'w'  */
              opcode = 'c';

              // ? break;

    case 'c': /* MCODE */
              {
                if (display) block(blocklevel);
                int i1;
                short int h1;
                if (display) fprintf(stdout, "%s%c %s ", indent, opcode, icode_name[opcode]);
                  for (;;) {
                    p1 = getmcstring(&i1);
                    if (display) fprintf(stdout, " %s", p1);
                    if (i1 == ';') break;
                    p2 = getshort();
                    if (display) fprintf(stdout, " tag_%s", p2);
                  }
              }
              break;

    case '!': /* OR */
    case '#': /* BNE */
    case '%': /* XOR */
    case '&': /* AND */
    case '*': /* MUL */
    case '+': /* ADD */
    case '-': /* SUB */
    case '.': /* CONCAT */
    case '/': /* QUOT */
    case 'E': /* CALL */
    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': /* SUBA */
    case 's': /* STOP */
    case 'u': /* ADDA */
    case 'v': /* MOD */
    case 'x': /* REXP */
              if (display) block(blocklevel);
              if (display) fprintf(stdout, "%s%c %s", indent, opcode, icode_name[opcode]);
              break;

    default:
      if (display) block(blocklevel);
      if (opcode < 0 || opcode > 255 || icode_name[opcode] == NULL) {
        if (display) fprintf(stdout, "?%sOPCODE %d", indent+1, opcode); break;
      } else {
        if (display) fprintf(stdout, "?%s%c %s", indent+1, opcode, icode_name[opcode]); break;
      }
    }

}

//------------------------------------------------------------------------------------------------------------

char *progname = "idec";

int main(int argc, char **argv) {
  *icode_filename = '\0';

  if (strrchr(argv[0], '/') != 0) {
    progname = strrchr(argv[0], '/')-1;
  } else {
    progname = argv[0];
  }

  while (argc > 1) {
    if (*argv[1] == '-') {
      if (strcmp(argv[1], "--i2c") == 0) {
        opt_extra_commas = 1; opt_extended_line = 1; opt_icode_style = 0x12C;
      } else if (strcmp(argv[1], "--imp77") == 0) {
        opt_extra_commas = 0; opt_extended_line = 0; opt_icode_style = 0x77;
      } else if (strcmp(argv[1], "--imp2026") == 0) {
        opt_extra_commas = 1; opt_extended_line = 0; opt_icode_style = 0x2026;
      } else if (strcmp(argv[1], "--commas") == 0) {
        opt_extra_commas = 1;
      } else if (strcmp(argv[1], "--no-commas") == 0) {
        opt_extra_commas = 0;
      } else if (strcmp(argv[1], "--perms") == 0) {
        opt_suppress_perms = 0;
      } else if (strcmp(argv[1], "--no-perms") == 0) {
        opt_suppress_perms = 1;
      } else if (strcmp(argv[1], "--line") == 0) {
        opt_extended_line = 1;
      } else if (strcmp(argv[1], "--no-line") == 0) {
        opt_extended_line = 0;
      } else if (strcmp(argv[1], "--recode") == 0) {
        opt_recode_wanted = 1; // modify icode file instead of decoding the icode
      } else if (strcmp(argv[1], "--no-recode") == 0) {
        opt_recode_wanted = 0;
      } else if (strcmp(argv[1], "-v") == 0) {
        opt_verbose = TRUE;
      } else if (strcmp(argv[1], "-h") == 0) {
        fprintf(stderr, "syntax: %s [options] file\n", progname);
        fprintf(stderr, "\n");
        fprintf(stderr, "options:\n");
        fprintf(stderr, "          --i2c     icode file was created by i2c\n");
        fprintf(stderr, "          --imp2026 icode file was created by imp2026\n");
        fprintf(stderr, "          --imp77   Original style icode file\n");
        fprintf(stderr, "          --perms   Decode the initial perms as well\n");
        fprintf(stderr, "          --commas  accept extra commas on $DEF\n");
        //fprintf(stderr, "          --line    accept extended $LINE code\n");
        fprintf(stderr, "          --recode  modify the .icd file to support i2c\n");
        fprintf(stderr, "          -v        Slightly more verbose output\n");
        fprintf(stderr, "          -h        This help\n");
        fprintf(stderr, "\n");
        exit(EXIT_SUCCESS);
      } else {
        fprintf(stderr, "%s: unknown option %s\n", progname, argv[1]);
        exit(EXIT_FAILURE);
      }
    } else {
      if (*icode_filename != '\0') {
        fprintf(stderr, "%s: only one filename parameter allowed. (%s and %s)\n", progname, icode_filename, argv[1]);
        exit(EXIT_FAILURE);
      }
      strcpy(icode_filename, argv[1]);
    }
    argv += 1; argc -= 1;
  }

  if (*icode_filename == '\0') {
    fprintf(stderr, "syntax: %s [options] file - no file was given\n", progname); exit(1);
  }

  if (opt_icode_style == 0) {
    fprintf(stderr, "%s: you must supply one of these options: --imp77 --imp2026 --i2c\n", progname); exit(1);
  }
  
  {
    // Ensure a .icd suffix.  Remove any existing suffix first.
    char *s = icode_filename;
    char *p = strrchr(s, '/');
    if (p == NULL) p = s;
    char *x = strrchr(p, '.');
    if (x) {
      strcpy(x, ".icd");
    } else {
      strcat(icode_filename, ".icd");
    }
  }

  if (!opt_extended_line) {
    // standard imp77 doesn't embed filenames in the icode so we need to guess it.
    // (could also add a --src parameter but won't for now)
    strcpy(implicit_single_source_file, icode_filename);
    char *s = strrchr(implicit_single_source_file, '.');
    strcpy(s, ".imp");
  }

  icode_file = fopen(icode_filename, "rb");
  if (icode_file == NULL) {
    fprintf(stderr, "%s: cannot open '%s' - %s\n", progname, icode_filename, strerror(errno));
    exit(2);
  }

  pass1();

//-------------------------------------------------------------------------------------------------------

  // pass2: now patch up the line numbers embedded in the chunks.

  int this_chunk;
  CHUNK *chunk, *peek;
  
  // First block will always be perms and perms will always be only one block.
  blocklevel = 0;
  
  for (this_chunk = 0; this_chunk <= next_free_chunk; this_chunk++) {
    
    chunk = &chunks[this_chunk];
    peek = &chunks[this_chunk+1];

    while (chunk->filename && peek->filename && strcmp(chunk->filename, peek->filename) == 0 && chunk->first_line == peek->first_line) {
      // two consecutive chunks are on the same line (statements separated by semi-colons)
      // so merge them into a single chunk.  Update the current chunk and delete the following one.
      chunk->last_line = peek->last_line;
      // eliminate peek:
      for (int compact = this_chunk+1; compact < next_free_chunk; compact++) chunks[compact] = chunks[compact+1];
      peek = &chunks[this_chunk+1];
      next_free_chunk -= 1;
    }
    
    for (int lookahead = this_chunk+1; lookahead < next_free_chunk; lookahead++) {
      peek = &chunks[lookahead];
      // find the next chunk in this file, and get our first line from its last line:
      if (strcmp(chunk->filename, peek->filename) == 0) {
        chunk->last_line = peek->first_line;
        break;
      }
    }
  }

  // There has been no output before this point.  We now have the icode in an
  // array and could potentially re-order parts of it to make the conversion
  // to C easier - specifically the output of forward references to procedures
  // which are passed as parameters to other procedures, which is handled in
  // ICODE in an unfortunate way which precludes outputting the forward declaration
  // correctly with the procedure parameter properly specified with its own arguments.

  // For the moment this code merely outputs the merged Imp77 source and decoded ICODE
  // for diagnostic purposes.

  blocklevel = 0;
  IN_START_FINISH_BLOCK = 0;
  char *prev_file = "";

  // the first few lines of regression-compile/nrimp10s-77.imp which are comments
  // and the initial %BEGIN are not being output.  Need to turn the debug_chunk back
  // on to determine why.

  for (int chunk_idx = 0; chunk_idx < next_free_chunk-1; chunk_idx++) {
    int lineno;
    
    chunk = &chunks[chunk_idx];
    peek = &chunks[chunk_idx+1];

    if (0) fprintf(stdout, "CHUNK %d of %d: [lev=%d] from icode: %d (incl) to icode: %d (excl),  lines from %d (incl) to %d (excl),  file = %s\n",
            chunk_idx, next_free_chunk,
            blocklevel,
            chunk->first_icode_idx, peek->first_icode_idx,
            chunk->first_line, chunk->last_line, chunk->filename);

    if (strcmp(chunk->filename, prev_file) != 0) {
      // We are now changing to a different include file.
      if (source_file != NULL) fclose(source_file);
      source_file = fopen(chunk->filename, "r");
      prev_file = chunk->filename;
      if (source_file != NULL) {
        lineno = 1;
        for (;;) {
          if (lineno >= chunk->first_line) break;
          // skip lines until we get to the desired one.
          if (fgets(line, MAX_LINE, source_file) == NULL) break;
          lineno += 1;
        }
        // lineno is now the number of the line that is about to be read.
      }
      // At this point either we are positioned ready for the expected line *or* source_file is NULL...
    }
      
    if (source_file != NULL) {

      for (;;) {
        if (lineno >= chunk->first_line) break;
        // skip lines that have been output already
        if (fgets(line, MAX_LINE, source_file) == NULL) break;
        lineno += 1;
      }
      
      lineno = chunks[chunk_idx].first_line;
      for (;;) {
        if (fgets(line, MAX_LINE, source_file) == NULL) break;
        // Need to skip this newline if no icode was output between the previous
        // line and this one...
        line[MAX_LINE] = '\0';
        if (chunk_idx != 0) {
          fprintf(stdout, "%6d  ", lineno);
          fputs(line, stdout);
        }
        lineno += 1;
        if (lineno >= chunk->last_line) break;
      }
    }

    // Turns out using the first_icode_idx from the following chunk is more reliable than
    // using the last_icode_idx from the current chunk.  That field has now been removed.
    // (same does not apply to line numbers however)
  
    for (int icode_idx = chunks[chunk_idx].first_icode_idx; icode_idx < chunks[chunk_idx+1].first_icode_idx; icode_idx++) {
      if (chunk_idx != 0 && opt_verbose) fprintf(stdout, "%d @ %d: ", icode_idx, memp/*, current_blockname*/);
      opcode_p = memp;

      if (chunk_idx == 0) {
        show_icode(icode_idx, !opt_suppress_perms);
        if (!opt_suppress_perms) fprintf(stdout, "\n");
      } else {
        show_icode(icode_idx, 1); fprintf(stdout, "\n"); // also updates memp by number of bytes used in the icode...
      }
      
      icode_length[icode_idx] = memp - icode[icode_idx];
    }
    if (chunks[chunk_idx].first_icode_idx != chunks[chunk_idx+1].first_icode_idx) fprintf(stdout, "\n");

  }

    FILE *new_icode_file;

    // Now make the changes that are needed to support passing procedures as parameters.
    // 1) in the param list - which is effectively lost at the end of the ")" - we need
    //    the full spec of the passed procedure, not just the procedure's name as at present
    // 2) within the body of the procedure, we need the passed procedure's parameters
    //    so that we can call it properly, which is indeed what currently happens with the
    //    second '$'def of the procedure, *but* we do *not* want to output that second copy
    //    of the procedure definition to the C code, which is what would happen unless I
    //    add a way to suppress it.
    // 3) be careful about forward definitions of procedures that take other procedures
    //    as parameters.

    // Only functional thing remaining to be done is to output REDEF ('g') in place of '$'
    // for the hidden internal spec in the recoded icode.

    // (there's still an indenting blocklevel issue in the text but I can live with that
    // for now.)
    
    // finally output the modified icode!

  if (opt_recode_wanted) {
    new_icode_file = fopen("ICODE-OUT.icd", "wb"); // TO DO: specific output file name...
    if (new_icode_file != 0) {
      for (int idx = 0; idx < next_icode; idx++) {
        int len = icode_length[idx];
        int memp = icode[idx];
        int replacement_fn;
        if ((icode_needs_replacing[idx] != NULL) && (get_fn(icode_needs_replacing[idx]) != -1)) {

          opcode_p = memp;
          fprintf(stdout, "REPLACING %s from %d @ %d:\n", icode_needs_replacing[idx], idx, memp);
          //show_icode(idx, TRUE); // also updates memp by number of bytes used in the icode...
          
          // Get offset for this procedure's proper definition and output DEF and START/.../FINISH
          replacement_fn = get_fn(icode_needs_replacing[idx]);
          fprintf(stdout, "WITH: %d\n", replacement_fn);

          int icode_idx, icode_p, icode_len, op;
          icode_idx = replacement_fn;
          icode_p = icode[icode_idx];
          icode_len = icode_length[icode_idx];
          op = mem[icode_p];

          if (mem[icode_p] != '$') {
            fprintf(stdout, "* Did not find $DEF at %d\n", icode_p);
            continue;
          } else {
            fprintf(stdout, "# Found $DEF at %d\n", icode_p);
            for (int i = icode_p; i < icode_p+icode_len; i++) fputc(mem[i], new_icode_file);
          }

          icode_p += icode_len; icode_idx += 1;
          icode_len = icode_length[icode_idx];
          op = mem[icode_p];

          // Followed by '{' (START)

          if (mem[icode_p] != '{') {
            fprintf(stdout, "* Did not find {START at %d\n", icode_p);
            continue;
          } else {
            fprintf(stdout, "# Found {START at %d\n", icode_p);
            for (int i = icode_p; i < icode_p+icode_len; i++) fputc(mem[i], new_icode_file);
          }

          for (;;) {
            // Followed by 0 or more '$' (DEF)
            icode_p += icode_len; icode_idx += 1;
            icode_len = icode_length[icode_idx];
            op = mem[icode_p];
            
            if (op != '$') break;
            
            fprintf(stdout, "# Found $DEF at %d\n", icode_p);
            for (int i = icode_p; i < icode_p+icode_len; i++) fputc(mem[i], new_icode_file);
          }
          
          // Followed by '}' (FINISH)
          if (mem[icode_p] != '}') {
            fprintf(stdout, "* Did not find }FINISH at %d\n", icode_p);
            continue;
          } else {
            fprintf(stdout, "# Found }FINISH at %d\n", icode_p);
            for (int i = icode_p; i < icode_p+icode_len; i++) fputc(mem[i], new_icode_file);
          }

          
        } else {
          for (int i = memp; i < memp+len; i++) {
            if (replacement[i] != 0) {
              fputc(replacement[i], new_icode_file);
            } else {
              fputc(mem[i], new_icode_file);
            }
          }
        }
      }
      //fputc('\n', new_icode_file);
      fclose(new_icode_file);
    } else {
      fprintf(stderr, "%s: could not write to ICODE-OUT.icd - %s\n", progname, strerror(errno));
    }
  }
  exit(0);
  return(0);
}
