/*  Small compiler - Binary code generation (the "assembler")
 *
 *  Copyright (c) ITB CompuPhase, 1997-2003
 *
 *  This software is provided "as-is", without any express or implied warranty.
 *  In no event will the authors be held liable for any damages arising from
 *  the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1.  The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software in
 *      a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  2.  Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  3.  This notice may not be removed or altered from any source distribution.
 *
 *  Version: $Id$
 */


#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>		/* for macro max() */
#include <string.h>
#include <ctype.h>

#include <Eina.h>

#include "embryo_cc_sc.h"

typedef             cell(*OPCODE_PROC) (FILE * fbin, char *params, cell opcode);

typedef struct
{
   cell                opcode;
   char               *name;
   int                 segment;	/* sIN_CSEG=parse in cseg, sIN_DSEG=parse in dseg */
   OPCODE_PROC         func;
} OPCODE;

static cell         codeindex;	/* similar to "code_idx" */
static cell        *lbltab;	/* label table */
static int          writeerror;
static int          bytes_in, bytes_out;

/* apparently, strtol() does not work correctly on very large (unsigned)
 * hexadecimal values */
static ucell
hex2long(char *s, char **n)
{
   unsigned long       result = 0L;
   int                 negate = FALSE;
   int                 digit;

   /* ignore leading whitespace */
   while (*s == ' ' || *s == '\t')
      s++;

   /* allow a negation sign to create the two's complement of numbers */
   if (*s == '-')
     {
	negate = TRUE;
	s++;
     }				/* if */

   assert((*s >= '0' && *s <= '9') || (*s >= 'a' && *s <= 'f')
	  || (*s >= 'A' && *s <= 'F'));
   for (;;)
     {
	if (*s >= '0' && *s <= '9')
	   digit = *s - '0';
	else if (*s >= 'a' && *s <= 'f')
	   digit = *s - 'a' + 10;
	else if (*s >= 'A' && *s <= 'F')
	   digit = *s - 'A' + 10;
	else
	   break;		/* probably whitespace */
	result = (result << 4) | digit;
	s++;
     }				/* for */
   if (n)
      *n = s;
   if (negate)
      result = (~result) + 1;	/* take two's complement of the result */
   return (ucell) result;
}

#ifdef WORDS_BIGENDIAN
static short       *
align16(short *v)
{
   unsigned char      *s = (unsigned char *)v;
   unsigned char       t;

   /* swap two bytes */
   t = s[0];
   s[0] = s[1];
   s[1] = t;
   return v;
}

static long        *
align32(long *v)
{
   unsigned char      *s = (unsigned char *)v;
   unsigned char       t;

   /* swap outer two bytes */
   t = s[0];
   s[0] = s[3];
   s[3] = t;
   /* swap inner two bytes */
   t = s[1];
   s[1] = s[2];
   s[2] = t;
   return v;
}
#if defined BIT16
#define aligncell(v)  align16(v)
#else
#define aligncell(v)  align32(v)
#endif
#else
#define align16(v)    (v)
#define align32(v)    (v)
#define aligncell(v)  (v)
#endif

static char        *
skipwhitespace(char *str)
{
   while (sc_isspace(*str))
      str++;
   return str;
}

static char        *
stripcomment(char *str)
{
   char               *ptr = strchr(str, ';');

   if (ptr)
     {
	*ptr++ = '\n';		/* terminate the line, but leave the '\n' */
	*ptr = '\0';
     }				/* if */
   return str;
}

static void
write_encoded(FILE * fbin, ucell * c, int num)
{
   assert(sizeof(cell) <= 4);	/* code must be adjusted for larger cells */
   assert(fbin != NULL);
   while (num-- > 0)
     {
	if (sc_compress)
	  {
	     ucell               p = (ucell) * c;
	     unsigned char       t[5];	/* a 32-bit cell is encoded in max. 5 bytes (3 bytes for a 16-bit cell) */
	     unsigned char       code;
	     int                 idx;

	     for (idx = 0; idx < 5; idx++)
	       {
		  t[idx] = (unsigned char)(p & 0x7f);	/* store 7 bits */
		  p >>= 7;
	       }		/* for */
	     /* skip leading zeros */
	     while (idx > 1 && t[idx - 1] == 0
		    && (t[idx - 2] & 0x40) == 0)
		idx--;
	     /* skip leading -1s *//* ??? for BIT16, check for idx==3 && t[idx-1]==0x03 */
	     if (idx == 5 && t[idx - 1] == 0x0f
		 && (t[idx - 2] & 0x40) != 0)
		idx--;
	     while (idx > 1 && t[idx - 1] == 0x7f
		    && (t[idx - 2] & 0x40) != 0)
		idx--;
	     /* write high byte first, write continuation bits */
	     assert(idx > 0);
	     while (idx-- > 0)
	       {
		  code =
		     (unsigned char)((idx == 0) ? t[idx]
				     : (t[idx] | 0x80));
		  writeerror |= !sc_writebin(fbin, &code, 1);
		  bytes_out++;
	       }		/* while */
	     bytes_in += sizeof *c;
	     assert(AMX_EXPANDMARGIN > 2);
	     if (bytes_out - bytes_in >= AMX_EXPANDMARGIN - 2)
		error(106);	/* compression buffer overflow */
	  }
	else
	  {
	     assert((sc_lengthbin(fbin) % sizeof(cell)) == 0);
	     writeerror |= !sc_writebin(fbin, aligncell(c), sizeof *c);
	  }			/* if */
	c++;
     }				/* while */
}

#if defined __BORLANDC__ || defined __WATCOMC__
#pragma argsused
#endif

static cell
noop(FILE * fbin EINA_UNUSED, char *params EINA_UNUSED, cell opcode EINA_UNUSED)
{
   return 0;
}

#if defined __BORLANDC__ || defined __WATCOMC__
#pragma argsused
#endif

static cell
parm0(FILE * fbin, char *params EINA_UNUSED, cell opcode)
{
   if (fbin)
      write_encoded(fbin, (ucell *) & opcode, 1);
   return opcodes(1);
}

static cell
parm1(FILE * fbin, char *params, cell opcode)
{
   ucell               p = hex2long(params, NULL);

   if (fbin)
     {
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &p, 1);
     }				/* if */
   return opcodes(1) + opargs(1);
}

static cell
parm2(FILE * fbin, char *params, cell opcode)
{
   ucell               p[2];

   p[0] = hex2long(params, &params);
   p[1] = hex2long(params, NULL);
   if (fbin)
     {
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, p, 2);
     }				/* if */
   return opcodes(1) + opargs(2);
}

#if defined __BORLANDC__ || defined __WATCOMC__
#pragma argsused
#endif

static cell
do_dump(FILE * fbin, char *params, cell opcode EINA_UNUSED)
{
   ucell               p;
   int                 num = 0;

   while (*params != '\0')
     {
	p = hex2long(params, &params);
	if (fbin)
	   write_encoded(fbin, &p, 1);
	num++;
	while (sc_isspace(*params))
	   params++;
     }				/* while */
   return num * sizeof(cell);
}

static cell
do_call(FILE * fbin, char *params, cell opcode)
{
   char                name[sNAMEMAX + 1];
   int                 i;
   symbol             *sym;
   ucell               p;

   for (i = 0; !sc_isspace(*params); i++, params++)
     {
	assert(*params != '\0');
	assert(i < sNAMEMAX);
	name[i] = *params;
     }				/* for */
   name[i] = '\0';

   /* look up the function address; note that the correct file number must
    * already have been set (in order for static globals to be found).
    */
   sym = findglb(name);
   assert(sym != NULL);
   assert(sym->ident == iFUNCTN || sym->ident == iREFFUNC);
   assert(sym->vclass == sGLOBAL);

   p = sym->addr;
   if (fbin)
     {
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &p, 1);
     }				/* if */
   return opcodes(1) + opargs(1);
}

static cell
do_jump(FILE * fbin, char *params, cell opcode)
{
   int                 i;
   ucell               p;

   i = (int)hex2long(params, NULL);
   assert(i >= 0 && i < labnum);

   if (fbin)
     {
	assert(lbltab != NULL);
	p = lbltab[i];
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &p, 1);
     }				/* if */
   return opcodes(1) + opargs(1);
}

static cell
do_file(FILE * fbin, char *params, cell opcode)
{
   ucell               p, clen;
   int                 len;

   p = hex2long(params, &params);

   /* remove leading and trailing white space from the filename */
   while (sc_isspace(*params))
      params++;
   len = strlen(params);
   while (len > 0 && sc_isspace(params[len - 1]))
      len--;
   params[len++] = '\0';	/* zero-terminate */
   while (len % sizeof(cell) != 0)
      params[len++] = '\0';	/* pad with zeros up to full cell */
   assert(len > 0 && len < 256);
   clen = len + sizeof(cell);	/* add size of file ordinal */

   if (fbin)
     {
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &clen, 1);
	write_encoded(fbin, &p, 1);
	write_encoded(fbin, (ucell *) params, len / sizeof(cell));
     }				/* if */
   return opcodes(1) + opargs(1) + clen;	/* other argument is in clen */
}

static cell
do_symbol(FILE * fbin, char *params, cell opcode)
{
   char               *endptr;
   ucell               offset, clen, flags;
   int                 len;
   unsigned char       mclass, type;

   for (endptr = params; (!sc_isspace(*endptr)) && (*endptr != '\0'); endptr++)
      /* nothing */ ;
   assert(*endptr == ' ');

   len = (int)(endptr - params);
   assert(len > 0 && len < sNAMEMAX);
   /* first get the other parameters from the line */
   offset = hex2long(endptr, &endptr);
   mclass = (unsigned char)hex2long(endptr, &endptr);
   type = (unsigned char)hex2long(endptr, NULL);
   flags = type + 256 * mclass;
   /* now finish up the name (overwriting the input line) */
   params[len++] = '\0';	/* zero-terminate */
   while (len % sizeof(cell) != 0)
      params[len++] = '\0';	/* pad with zeros up to full cell */
   clen = len + 2 * sizeof(cell);	/* add size of symbol address and flags */

   if (fbin)
     {
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &clen, 1);
	write_encoded(fbin, &offset, 1);
	write_encoded(fbin, &flags, 1);
	write_encoded(fbin, (ucell *) params, len / sizeof(cell));
     }				/* if */

#if !defined NDEBUG
   /* function should start right after the symbolic information */
   if (!fbin && mclass == 0 && type == iFUNCTN)
      assert(offset == codeindex + opcodes(1) + opargs(1) + clen);
#endif

   return opcodes(1) + opargs(1) + clen;	/* other 2 arguments are in clen */
}

static cell
do_switch(FILE * fbin, char *params, cell opcode)
{
   int                 i;
   ucell               p;

   i = (int)hex2long(params, NULL);
   assert(i >= 0 && i < labnum);

   if (fbin)
     {
	assert(lbltab != NULL);
	p = lbltab[i];
	write_encoded(fbin, (ucell *) & opcode, 1);
	write_encoded(fbin, &p, 1);
     }				/* if */
   return opcodes(1) + opargs(1);
}

#if defined __BORLANDC__ || defined __WATCOMC__
#pragma argsused
#endif

static cell
do_case(FILE * fbin, char *params, cell opcode EINA_UNUSED)
{
   int                 i;
   ucell               p, v;

   v = hex2long(params, &params);
   i = (int)hex2long(params, NULL);
   assert(i >= 0 && i < labnum);

   if (fbin)
     {
	assert(lbltab != NULL);
	p = lbltab[i];
	write_encoded(fbin, &v, 1);
	write_encoded(fbin, &p, 1);
     }				/* if */
   return opcodes(0) + opargs(2);
}

#if defined __BORLANDC__ || defined __WATCOMC__
#pragma argsused
#endif

static cell
curfile(FILE * fbin EINA_UNUSED, char *params, cell opcode EINA_UNUSED)
{
   fcurrent = (int)hex2long(params, NULL);
   return 0;
}

static OPCODE       opcodelist[] = {
   /* node for "invalid instruction" */
   {0, NULL, 0, noop},
   /* opcodes in sorted order */
   {78, "add", sIN_CSEG, parm0},
   {87, "add.c", sIN_CSEG, parm1},
   {14, "addr.alt", sIN_CSEG, parm1},
   {13, "addr.pri", sIN_CSEG, parm1},
   {30, "align.alt", sIN_CSEG, parm1},
   {29, "align.pri", sIN_CSEG, parm1},
   {81, "and", sIN_CSEG, parm0},
   {121, "bounds", sIN_CSEG, parm1},
   {49, "call", sIN_CSEG, do_call},
   {50, "call.pri", sIN_CSEG, parm0},
   {0, "case", sIN_CSEG, do_case},
   {130, "casetbl", sIN_CSEG, parm0},	/* version 1 */
   {118, "cmps", sIN_CSEG, parm1},
   {0, "code", 0, noop},
   {12, "const.alt", sIN_CSEG, parm1},
   {11, "const.pri", sIN_CSEG, parm1},
   {0, "curfile", sIN_CSEG, curfile},
   {0, "data", 0, noop},
   {114, "dec", sIN_CSEG, parm1},
   {113, "dec.alt", sIN_CSEG, parm0},
   {116, "dec.i", sIN_CSEG, parm0},
   {112, "dec.pri", sIN_CSEG, parm0},
   {115, "dec.s", sIN_CSEG, parm1},
   {0, "dump", sIN_DSEG, do_dump},
   {95, "eq", sIN_CSEG, parm0},
   {106, "eq.c.alt", sIN_CSEG, parm1},
   {105, "eq.c.pri", sIN_CSEG, parm1},
   {124, "file", sIN_CSEG, do_file},
   {119, "fill", sIN_CSEG, parm1},
   {100, "geq", sIN_CSEG, parm0},
   {99, "grtr", sIN_CSEG, parm0},
   {120, "halt", sIN_CSEG, parm1},
   {45, "heap", sIN_CSEG, parm1},
   {27, "idxaddr", sIN_CSEG, parm0},
   {28, "idxaddr.b", sIN_CSEG, parm1},
   {109, "inc", sIN_CSEG, parm1},
   {108, "inc.alt", sIN_CSEG, parm0},
   {111, "inc.i", sIN_CSEG, parm0},
   {107, "inc.pri", sIN_CSEG, parm0},
   {110, "inc.s", sIN_CSEG, parm1},
   {86, "invert", sIN_CSEG, parm0},
   {55, "jeq", sIN_CSEG, do_jump},
   {60, "jgeq", sIN_CSEG, do_jump},
   {59, "jgrtr", sIN_CSEG, do_jump},
   {58, "jleq", sIN_CSEG, do_jump},
   {57, "jless", sIN_CSEG, do_jump},
   {56, "jneq", sIN_CSEG, do_jump},
   {54, "jnz", sIN_CSEG, do_jump},
   {52, "jrel", sIN_CSEG, parm1},	/* always a number */
   {64, "jsgeq", sIN_CSEG, do_jump},
   {63, "jsgrtr", sIN_CSEG, do_jump},
   {62, "jsleq", sIN_CSEG, do_jump},
   {61, "jsless", sIN_CSEG, do_jump},
   {51, "jump", sIN_CSEG, do_jump},
   {128, "jump.pri", sIN_CSEG, parm0},	/* version 1 */
   {53, "jzer", sIN_CSEG, do_jump},
   {31, "lctrl", sIN_CSEG, parm1},
   {98, "leq", sIN_CSEG, parm0},
   {97, "less", sIN_CSEG, parm0},
   {25, "lidx", sIN_CSEG, parm0},
   {26, "lidx.b", sIN_CSEG, parm1},
   {125, "line", sIN_CSEG, parm2},
   {2, "load.alt", sIN_CSEG, parm1},
   {9, "load.i", sIN_CSEG, parm0},
   {1, "load.pri", sIN_CSEG, parm1},
   {4, "load.s.alt", sIN_CSEG, parm1},
   {3, "load.s.pri", sIN_CSEG, parm1},
   {10, "lodb.i", sIN_CSEG, parm1},
   {6, "lref.alt", sIN_CSEG, parm1},
   {5, "lref.pri", sIN_CSEG, parm1},
   {8, "lref.s.alt", sIN_CSEG, parm1},
   {7, "lref.s.pri", sIN_CSEG, parm1},
   {34, "move.alt", sIN_CSEG, parm0},
   {33, "move.pri", sIN_CSEG, parm0},
   {117, "movs", sIN_CSEG, parm1},
   {85, "neg", sIN_CSEG, parm0},
   {96, "neq", sIN_CSEG, parm0},
   {134, "nop", sIN_CSEG, parm0},	/* version 6 */
   {84, "not", sIN_CSEG, parm0},
   {82, "or", sIN_CSEG, parm0},
   {43, "pop.alt", sIN_CSEG, parm0},
   {42, "pop.pri", sIN_CSEG, parm0},
   {46, "proc", sIN_CSEG, parm0},
   {40, "push", sIN_CSEG, parm1},
   {37, "push.alt", sIN_CSEG, parm0},
   {39, "push.c", sIN_CSEG, parm1},
   {36, "push.pri", sIN_CSEG, parm0},
   {38, "push.r", sIN_CSEG, parm1},
   {41, "push.s", sIN_CSEG, parm1},
   {133, "pushaddr", sIN_CSEG, parm1},	/* version 4 */
   {47, "ret", sIN_CSEG, parm0},
   {48, "retn", sIN_CSEG, parm0},
   {32, "sctrl", sIN_CSEG, parm1},
   {73, "sdiv", sIN_CSEG, parm0},
   {74, "sdiv.alt", sIN_CSEG, parm0},
   {104, "sgeq", sIN_CSEG, parm0},
   {103, "sgrtr", sIN_CSEG, parm0},
   {65, "shl", sIN_CSEG, parm0},
   {69, "shl.c.alt", sIN_CSEG, parm1},
   {68, "shl.c.pri", sIN_CSEG, parm1},
   {66, "shr", sIN_CSEG, parm0},
   {71, "shr.c.alt", sIN_CSEG, parm1},
   {70, "shr.c.pri", sIN_CSEG, parm1},
   {94, "sign.alt", sIN_CSEG, parm0},
   {93, "sign.pri", sIN_CSEG, parm0},
   {102, "sleq", sIN_CSEG, parm0},
   {101, "sless", sIN_CSEG, parm0},
   {72, "smul", sIN_CSEG, parm0},
   {88, "smul.c", sIN_CSEG, parm1},
   {127, "srange", sIN_CSEG, parm2},	/* version 1 */
   {20, "sref.alt", sIN_CSEG, parm1},
   {19, "sref.pri", sIN_CSEG, parm1},
   {22, "sref.s.alt", sIN_CSEG, parm1},
   {21, "sref.s.pri", sIN_CSEG, parm1},
   {67, "sshr", sIN_CSEG, parm0},
   {44, "stack", sIN_CSEG, parm1},
   {0, "stksize", 0, noop},
   {16, "stor.alt", sIN_CSEG, parm1},
   {23, "stor.i", sIN_CSEG, parm0},
   {15, "stor.pri", sIN_CSEG, parm1},
   {18, "stor.s.alt", sIN_CSEG, parm1},
   {17, "stor.s.pri", sIN_CSEG, parm1},
   {24, "strb.i", sIN_CSEG, parm1},
   {79, "sub", sIN_CSEG, parm0},
   {80, "sub.alt", sIN_CSEG, parm0},
   {132, "swap.alt", sIN_CSEG, parm0},	/* version 4 */
   {131, "swap.pri", sIN_CSEG, parm0},	/* version 4 */
   {129, "switch", sIN_CSEG, do_switch},	/* version 1 */
   {126, "symbol", sIN_CSEG, do_symbol},
   {136, "symtag", sIN_CSEG, parm1},	/* version 7 */
   {123, "sysreq.c", sIN_CSEG, parm1},
   {135, "sysreq.d", sIN_CSEG, parm1},	/* version 7, not generated directly */
   {122, "sysreq.pri", sIN_CSEG, parm0},
   {76, "udiv", sIN_CSEG, parm0},
   {77, "udiv.alt", sIN_CSEG, parm0},
   {75, "umul", sIN_CSEG, parm0},
   {35, "xchg", sIN_CSEG, parm0},
   {83, "xor", sIN_CSEG, parm0},
   {91, "zero", sIN_CSEG, parm1},
   {90, "zero.alt", sIN_CSEG, parm0},
   {89, "zero.pri", sIN_CSEG, parm0},
   {92, "zero.s", sIN_CSEG, parm1},
};

#define MAX_INSTR_LEN   30
static int
findopcode(char *instr, int maxlen)
{
   int                 low, high, mid, cmp;
   char                str[MAX_INSTR_LEN];

   if (maxlen >= MAX_INSTR_LEN)
      return 0;
   strncpy(str, instr, maxlen);
   str[maxlen] = '\0';		/* make sure the string is zero terminated */
   /* look up the instruction with a binary search
    * the assembler is case insensitive to instructions (but case sensitive
    * to symbols)
    */
   low = 1;			/* entry 0 is reserved (for "not found") */
   high = (sizeof opcodelist / sizeof opcodelist[0]) - 1;
   while (low < high)
     {
	mid = (low + high) / 2;
	assert(opcodelist[mid].name != NULL);
	cmp = strcasecmp(str, opcodelist[mid].name);
	if (cmp > 0)
	   low = mid + 1;
	else
	   high = mid;
     }				/* while */

   assert(low == high);
   if (strcasecmp(str, opcodelist[low].name) == 0)
      return low;		/* found */
   return 0;			/* not found, return special index */
}

void
assemble(FILE * fout, FILE * fin)
{
   typedef struct tagFUNCSTUB
   {
      unsigned int            address, nameofs;
   } FUNCSTUB;
   AMX_HEADER          hdr;
   FUNCSTUB            func;
   int                 numpublics, numnatives, numlibraries, numpubvars,
      numtags, padding;
   long                nametablesize, nameofs;
   char                line[256], *instr, *params;
   int                 i, pass;
   short               count;
   symbol             *sym, **nativelist;
   constvalue         *constptr;
   cell                mainaddr;
   int                 nametable, tags, libraries, publics, natives, pubvars;
   int                 cod;

#if !defined NDEBUG
   /* verify that the opcode list is sorted (skip entry 1; it is reserved
    * for a non-existent opcode)
    */
   assert(opcodelist[1].name != NULL);
   for (i = 2; i < (int)(sizeof(opcodelist) / sizeof(opcodelist[0])); i++)
     {
	assert(opcodelist[i].name != NULL);
	assert(strcasecmp(opcodelist[i].name, opcodelist[i - 1].name) > 0);
     }				/* for */
#endif

   writeerror = FALSE;
   nametablesize = sizeof(short);
   numpublics = 0;
   numnatives = 0;
   numpubvars = 0;
   mainaddr = -1;
   /* count number of public and native functions and public variables */
   for (sym = glbtab.next; sym; sym = sym->next)
     {
	char                alias[sNAMEMAX + 1] = "";
	int                 match = 0;

	if (sym->ident == iFUNCTN)
	  {
	     assert(strlen(sym->name) <= sNAMEMAX);
	     if ((sym->usage & uNATIVE) != 0 && (sym->usage & uREAD) != 0
		 && sym->addr >= 0)
	       {
		  match = ++numnatives;
		  if (!lookup_alias(alias, sym->name))
		     strcpy(alias, sym->name);
	       }		/* if */
	     if ((sym->usage & uPUBLIC) != 0 && (sym->usage & uDEFINE) != 0)
	       {
		  match = ++numpublics;
		  strcpy(alias, sym->name);
	       }		/* if */
	     if (strcmp(sym->name, uMAINFUNC) == 0)
	       {
		  assert(sym->vclass == sGLOBAL);
		  mainaddr = sym->addr;
	       }		/* if */
	  }
	else if (sym->ident == iVARIABLE)
	  {
	     if ((sym->usage & uPUBLIC) != 0)
	       {
		  match = ++numpubvars;
		  strcpy(alias, sym->name);
	       }		/* if */
	  }			/* if */
	if (match)
	  {
	     assert(alias[0] != '\0');
	     nametablesize += strlen(alias) + 1;
	  }			/* if */
     }				/* for */
   assert(numnatives == ntv_funcid);

   /* count number of libraries */
   numlibraries = 0;
   for (constptr = libname_tab.next; constptr;
	constptr = constptr->next)
     {
	if (constptr->value > 0)
	  {
	     assert(constptr->name[0] != '\0');
	     numlibraries++;
	     nametablesize += strlen(constptr->name) + 1;
	  }			/* if */
     }				/* for */

   /* count number of public tags */
   numtags = 0;
   for (constptr = tagname_tab.next; constptr;
	constptr = constptr->next)
     {
	if ((constptr->value & PUBLICTAG) != 0)
	  {
	     assert(constptr->name[0] != '\0');
	     numtags++;
	     nametablesize += strlen(constptr->name) + 1;
	  }			/* if */
     }				/* for */

   /* pad the header to sc_dataalign
    * => thereby the code segment is aligned
    * => since the code segment is padded to a sc_dataalign boundary, the data segment is aligned
    * => and thereby the stack top is aligned too
    */
   assert(sc_dataalign != 0);
   padding = sc_dataalign - (sizeof hdr + nametablesize) % sc_dataalign;
   if (padding == sc_dataalign)
      padding = 0;

   /* write the abstract machine header */
   memset(&hdr, 0, sizeof hdr);
   hdr.magic = (unsigned short)0xF1E0;
   hdr.file_version = CUR_FILE_VERSION;
   hdr.amx_version = MIN_AMX_VERSION;
   hdr.flags = (short)(sc_debug & sSYMBOLIC);
   if (charbits == 16)
      hdr.flags |= AMX_FLAG_CHAR16;
   if (sc_compress)
      hdr.flags |= AMX_FLAG_COMPACT;
   if (sc_debug == 0)
      hdr.flags |= AMX_FLAG_NOCHECKS;
//  #ifdef WORDS_BIGENDIAN
//    hdr.flags|=AMX_FLAG_BIGENDIAN;
//  #endif
   hdr.defsize = sizeof(FUNCSTUB);
   assert((hdr.defsize % sizeof(cell)) == 0);
   publics = hdr.publics = sizeof hdr;	/* public table starts right after the header */
   natives = hdr.natives = hdr.publics + numpublics * sizeof(FUNCSTUB);
   libraries = hdr.libraries = hdr.natives + numnatives * sizeof(FUNCSTUB);
   pubvars = hdr.pubvars = hdr.libraries + numlibraries * sizeof(FUNCSTUB);
   tags = hdr.tags = hdr.pubvars + numpubvars * sizeof(FUNCSTUB);
   nametable = hdr.nametable = hdr.tags + numtags * sizeof(FUNCSTUB);
   cod = hdr.cod = hdr.nametable + nametablesize + padding;
   hdr.dat = hdr.cod + code_idx;
   hdr.hea = hdr.dat + glb_declared * sizeof(cell);
   hdr.stp = hdr.hea + sc_stksize * sizeof(cell);
   hdr.cip = mainaddr;
   hdr.size = hdr.hea;	/* preset, this is incorrect in case of compressed output */
#ifdef WORDS_BIGENDIAN
   align32(&hdr.size);
   align16(&hdr.magic);
   align16(&hdr.flags);
   align16(&hdr.defsize);
   align32(&hdr.cod);
   align32(&hdr.dat);
   align32(&hdr.hea);
   align32(&hdr.stp);
   align32(&hdr.cip);
   align32(&hdr.publics);
   align32(&hdr.natives);
   align32(&hdr.libraries);
   align32(&hdr.pubvars);
   align32(&hdr.tags);
   align32(&hdr.nametable);
#endif
   sc_writebin(fout, &hdr, sizeof hdr);

   /* dump zeros up to the rest of the header, so that we can easily "seek" */
   for (nameofs = sizeof hdr; nameofs < cod; nameofs++)
      putc(0, fout);
   nameofs = nametable + sizeof(short);

   /* write the public functions table */
   count = 0;
   for (sym = glbtab.next; sym; sym = sym->next)
     {
	if (sym->ident == iFUNCTN
	    && (sym->usage & uPUBLIC) != 0 && (sym->usage & uDEFINE) != 0)
	  {
	     assert(sym->vclass == sGLOBAL);
	     func.address = sym->addr;
	     func.nameofs = nameofs;
#ifdef WORDS_BIGENDIAN
	     align32(&func.address);
	     align32(&func.nameofs);
#endif
	     if (fseek(fout, publics + count * sizeof(FUNCSTUB), SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, &func, sizeof func);
	     if (fseek(fout, nameofs, SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, sym->name, strlen(sym->name) + 1);
	     nameofs += strlen(sym->name) + 1;
	     count++;
	  }			/* if */
     }				/* for */

   /* write the natives table */
   /* The native functions must be written in sorted order. (They are
    * sorted on their "id", not on their name). A nested loop to find
    * each successive function would be an O(n^2) operation. But we
    * do not really need to sort, because the native function id's
    * are sequential and there are no duplicates. So we first walk
    * through the complete symbol list and store a pointer to every
    * native function of interest in a temporary table, where its id
    * serves as the index in the table. Now we can walk the table and
    * have all native functions in sorted order.
    */
   if (numnatives > 0)
     {
	nativelist = (symbol **) malloc(numnatives * sizeof(symbol *));
	if (!nativelist)
	   error(103);		/* insufficient memory */
#if !defined NDEBUG
	memset(nativelist, 0, numnatives * sizeof(symbol *));	/* for NULL checking */
#endif
	for (sym = glbtab.next; sym; sym = sym->next)
	  {
	     if (sym->ident == iFUNCTN && (sym->usage & uNATIVE) != 0
		 && (sym->usage & uREAD) != 0 && sym->addr >= 0)
	       {
		  assert(sym->addr < numnatives);
		  nativelist[(int)sym->addr] = sym;
	       }		/* if */
	  }			/* for */
	count = 0;
	for (i = 0; i < numnatives; i++)
	  {
	     char                alias[sNAMEMAX + 1];

	     sym = nativelist[i];
	     assert(sym != NULL);
	     if (!lookup_alias(alias, sym->name))
	       {
		  assert(strlen(sym->name) <= sNAMEMAX);
		  strcpy(alias, sym->name);
	       }		/* if */
	     assert(sym->vclass == sGLOBAL);
	     func.address = 0;
	     func.nameofs = nameofs;
#ifdef WORDS_BIGENDIAN
	     align32(&func.address);
	     align32(&func.nameofs);
#endif
	     if (fseek(fout, natives + count * sizeof(FUNCSTUB), SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, &func, sizeof func);
	     if (fseek(fout, nameofs, SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, alias, strlen(alias) + 1);
	     nameofs += strlen(alias) + 1;
	     count++;
	  }			/* for */
	free(nativelist);
     }				/* if */

   /* write the libraries table */
   count = 0;
   for (constptr = libname_tab.next; constptr;
	constptr = constptr->next)
     {
	if (constptr->value > 0)
	  {
	     assert(constptr->name[0] != '\0');
	     func.address = 0;
	     func.nameofs = nameofs;
#ifdef WORDS_BIGENDIAN
	     align32(&func.address);
	     align32(&func.nameofs);
#endif
	     if (fseek(fout, libraries + count * sizeof(FUNCSTUB), SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, &func, sizeof func);
	     if (fseek(fout, nameofs, SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, constptr->name, strlen(constptr->name) + 1);
	     nameofs += strlen(constptr->name) + 1;
	     count++;
	  }			/* if */
     }				/* for */

   /* write the public variables table */
   count = 0;
   for (sym = glbtab.next; sym; sym = sym->next)
     {
	if (sym->ident == iVARIABLE && (sym->usage & uPUBLIC) != 0)
	  {
	     assert((sym->usage & uDEFINE) != 0);
	     assert(sym->vclass == sGLOBAL);
	     func.address = sym->addr;
	     func.nameofs = nameofs;
#ifdef WORDS_BIGENDIAN
	     align32(&func.address);
	     align32(&func.nameofs);
#endif
	     if (fseek(fout, pubvars + count * sizeof(FUNCSTUB), SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, &func, sizeof func);
	     if (fseek(fout, nameofs, SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, sym->name, strlen(sym->name) + 1);
	     nameofs += strlen(sym->name) + 1;
	     count++;
	  }			/* if */
     }				/* for */

   /* write the public tagnames table */
   count = 0;
   for (constptr = tagname_tab.next; constptr;
	constptr = constptr->next)
     {
	if ((constptr->value & PUBLICTAG) != 0)
	  {
	     assert(constptr->name[0] != '\0');
	     func.address = constptr->value & TAGMASK;
	     func.nameofs = nameofs;
#ifdef WORDS_BIGENDIAN
	     align32(&func.address);
	     align32(&func.nameofs);
#endif
	     if (fseek(fout, tags + count * sizeof(FUNCSTUB), SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, &func, sizeof func);
	     if (fseek(fout, nameofs, SEEK_SET) < 0)
               fprintf(stderr, "Error seeking\n");
	     sc_writebin(fout, constptr->name, strlen(constptr->name) + 1);
	     nameofs += strlen(constptr->name) + 1;
	     count++;
	  }			/* if */
     }				/* for */

   /* write the "maximum name length" field in the name table */
   assert(nameofs == nametable + nametablesize);
   if (fseek(fout, nametable, SEEK_SET) < 0)
     fprintf(stderr, "Error seeking\n");
   count = sNAMEMAX;
#ifdef WORDS_BIGENDIAN
   align16(&count);
#endif
   sc_writebin(fout, &count, sizeof count);
   if (fseek(fout, cod, SEEK_SET) < 0)
     fprintf(stderr, "Error seeking\n");

   /* First pass: relocate all labels */
   /* This pass is necessary because the code addresses of labels is only known
    * after the peephole optimization flag. Labels can occur inside expressions
    * (e.g. the conditional operator), which are optimized.
    */
   lbltab = NULL;
   if (labnum > 0)
     {
	/* only very short programs have zero labels; no first pass is needed
	 * if there are no labels */
	lbltab = (cell *) malloc(labnum * sizeof(cell));
	if (!lbltab)
	   error(103);		/* insufficient memory */
	codeindex = 0;
	sc_resetasm(fin);
	while (sc_readasm(fin, line, sizeof line))
	  {
	     stripcomment(line);
	     instr = skipwhitespace(line);
	     /* ignore empty lines */
	     if (*instr == '\0')
		continue;
	     if (tolower(*instr) == 'l' && *(instr + 1) == '.')
	       {
		  int                 lindex = (int)hex2long(instr + 2, NULL);

		  assert(lindex < labnum);
		  lbltab[lindex] = codeindex;
	       }
	     else
	       {
		  /* get to the end of the instruction (make use of the '\n' that fgets()
		   * added at the end of the line; this way we will *always* drop on a
		   * whitespace character) */
		  for (params = instr; *params != '\0' && !sc_isspace(*params);
		       params++)
		     /* nothing */ ;
		  assert(params > instr);
		  i = findopcode(instr, (int)(params - instr));
		  if (!opcodelist[i].name)
		    {
		       *params = '\0';
		       error(104, instr);	/* invalid assembler instruction */
		    }		/* if */
		  if (opcodelist[i].segment == sIN_CSEG)
		     codeindex +=
			opcodelist[i].func(NULL, skipwhitespace(params),
					   opcodelist[i].opcode);
	       }		/* if */
	  }			/* while */
     }				/* if */

   /* Second pass (actually 2 more passes, one for all code and one for all data) */
   bytes_in = 0;
   bytes_out = 0;
   for (pass = sIN_CSEG; pass <= sIN_DSEG; pass++)
     {
	sc_resetasm(fin);
	while (sc_readasm(fin, line, sizeof line))
	  {
	     stripcomment(line);
	     instr = skipwhitespace(line);
	     /* ignore empty lines and labels (labels have a special syntax, so these
	      * must be parsed separately) */
	     if (*instr == '\0' || (tolower(*instr) == 'l'
		 && *(instr + 1) == '.'))
		continue;
	     /* get to the end of the instruction (make use of the '\n' that fgets()
	      * added at the end of the line; this way we will *always* drop on a
	      * whitespace character) */
	     for (params = instr; *params != '\0' && !sc_isspace(*params);
		  params++)
		/* nothing */ ;
	     assert(params > instr);
	     i = findopcode(instr, (int)(params - instr));
	     assert(opcodelist[i].name != NULL);
	     if (opcodelist[i].segment == pass)
		opcodelist[i].func(fout, skipwhitespace(params),
				   opcodelist[i].opcode);
	  }			/* while */
     }				/* for */
   if (bytes_out - bytes_in > 0)
      error(106);		/* compression buffer overflow */

   if (lbltab)
     {
	free(lbltab);
#if !defined NDEBUG
	lbltab = NULL;
#endif
     }				/* if */

   if (writeerror)
      error(101, "disk full");

   /* adjust the header */
   if (sc_compress)
     {
	hdr.size = sc_lengthbin(fout);
#ifdef WORDS_BIGENDIAN
	align32(&hdr.size);
#endif
	sc_resetbin(fout);	/* "size" is the very first field */
	sc_writebin(fout, &hdr.size, sizeof hdr.size);
     }				/* if */
}
