/*
 *  smoke16/src/as.c - The SMOKE-16 assembler.
 *  $Revision: 1.23 $
 *  Copyright 1997-2002, Benjamin Sittler
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* #define DEBUG_RELOCS */
/* #define DEBUG_SYMS */
/* #define DEBUG_SECTS */

#include <ctype.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <smoke16/a_out.h> /* object file format */
#include <smoke16/libObj.h>

#if defined(__GNUC__) && (__GNUC__ > 1)
#define unused __attribute__((unused))
#else
#define unused
#endif

static char unused RCSID[] = "$Id: as.c,v 1.23 2002/07/28 23:26:33 bsittler Exp $";

#define YYERROR_VERBOSE
#define MAX_PARSE_ERRORS 10

/* execution character set */
enum {
  ENCODING_NATIVE,
  ENCODING_PORTABLE
} xencoding = ENCODING_NATIVE, encoding = ENCODING_NATIVE;

/* forward declaration */
struct reloc_t;

/* assembler sections */
#define NSECTS 5
#define SECT_TEXT 0
#define SECT_RDATA 1
#define SECT_DATA 2
#define SECT_BSS 3
#define SECT_ABS 4
int sect = SECT_TEXT;

/* per-section data */
struct {
  unsigned long offset;
  unsigned long align;
  const char *align_sym;
  const char *name;
  u8 n_type;
  TmpFile *file;
  struct reloc_t *relocs;
} info[NSECTS] = {
  {0, 0,
   SMOKE16__ALIGN_TEXT,
   ".text",
   SMOKE16_N_TEXT,
   0, 0},
  {0, 0,
   0,
   ".rdata",
   SMOKE16_N_DATA,
   0, 0},
  {0, 0,
   SMOKE16__ALIGN_DATA,
   ".data",
   SMOKE16_N_DATA,
   0, 0},
  {0, 0,
   SMOKE16__ALIGN_BSS,
   ".bss",
   SMOKE16_N_BSS,
   0, 0},
  {0, 0,
   0,
   ".abs",
   SMOKE16_N_ABS,
   0, 0}
};

/* translate native character to execution encoding */
int xchar(int c);

int allow_r7 = 0; /* allow use of assembler-reserved register %r7/%t6/%7 */
int ignore_overflow = 0; /* ignore overflow */
int lineno = 1;
char *infile = 0;

int parse_errors = 0; /* number of errors detected in the input */
int yyerror(char *);
int yylex();

/* FIXME: these should use symbols in .abs */
void mkconst(char *sym, unsigned long value);
int isconst(char *sym);
unsigned long getconst(char *sym);

void cleanup(void);

/* make a low-byte symbolic reference */
void mklo(char *sym, unsigned long addr, unsigned long value);

/* make a high-byte symbolic reference */
void mkhi(char *sym, unsigned long addr, unsigned long value);

/* make a displacement symbolic reference */
void mkdisp(char *sym, unsigned long addr, unsigned long value);

/* make a symbol global */
void mkglobal(char *sym);

/* make a common symbol */
void mkcomm(char *sym, unsigned long size);

/* make a local common symbol */
void mklcomm(char *sym, unsigned long size);

/* bind a symbol to an address */
void define(char *sym, unsigned long addr);

void mkbyte(unsigned long byte); /* write a byte to the current section */
void mkword(unsigned long word); /* write a word to the current section */

u8 to_u8(unsigned long value); /* catches overflow */
u16 to_u16(unsigned long value); /* catches overflow */

#include "as_tab.c" /* parser */
#include "as_yy.c" /* scanner */

#include <smoke16/hashtbl.h> /* symbol table */
#include <smoke16/hashpjw.h> /* hashing function */
#include "getopt.h" /* argument parser */

/* translate native character to execution encoding */
int xchar(int c) {
  int i;

  if (encoding == ENCODING_NATIVE)
    return c;
  if ((i = htonc(c)) == -1) {
    char buf[128], *s;
    
    if ((s = strchr("\a\b\f\n\r\t\v\"\\\?\'\0abfnrtv\"\\\?\'", c)))
      sprintf(buf, "couldn't convert character %#o ('\\%c') to portable encoding", c, s[12]);
    else if (isprint(c))
      sprintf(buf, "couldn't convert character %#o ('%c') to portable encoding", c, c);
    else
      sprintf(buf, "couldn't convert character %#o to portable encoding", c);
    yyerror(buf);
    i = 0;
  }
  return i;
}

/* The memory representation of a relocation. Kept in one doubly-linked list per section. */
typedef struct reloc_t {
  struct reloc_t *next, *prev;
  struct Node *sym;
  struct a_out16_reloc reloc;
} reloc_t;

reloc_t *mkreloc(int sect) { /* allocate and link a new relocation */
  reloc_t *reloc = (reloc_t *) malloc(sizeof(reloc_t));
  
  if(!reloc) {
    perror("malloc");
    cleanup(), exit(1);
  }
  reloc->prev = 0;
  if((reloc->next = info[sect].relocs))
    reloc->next->prev = reloc;
  return (info[sect].relocs = reloc);
}

void rmreloc(int sect, reloc_t *reloc) { /* unlink and de-allocate a relocation */
  if(info[sect].relocs == reloc)
    info[sect].relocs = reloc->next;
  else
    if((reloc->prev->next = reloc->next))
      reloc->next->prev = reloc->prev;
  free(reloc);
}

static struct Node *consttab[HASHSIZE];

u16 to_u16(unsigned long value) {
  if ((value != (value & 0xffffUL)) &&
      ((value & ~0x7fffUL) != ~0x7fffUL) &&
      ! ignore_overflow) {
    yyerror("overflow while converting to 16-bit value -- override with assembler option -J");
  }
  return value & 0xffffUL;
}

u8 to_u8(unsigned long value) {
  if ((value != (value & 0xffUL)) &&
      ((value & ~0x7fUL) != ~0x7fUL) &&
      ! ignore_overflow) {
    yyerror("overflow while converting to 8-bit value -- override with assembler option -J");
  }
  return value & 0xffUL;
}

void mkconst(char *sym, unsigned long value) {
  struct Node *string, *found;

  if(! (string = (struct Node *) malloc(sizeof (struct Node)))) {
    perror("malloc");
    cleanup(), exit(1);
  }
  if(! (string->word = strdup(sym))) {
    perror("strdup");
    cleanup(), exit(1);
  }
  if((found = lookup(consttab, string, wordhash, wordcompare))) {
    free(string->word);
    free(string);
  } else {
    found = enter(consttab, string, wordhash);
  }
  found->nlist.n_value = to_u16(value);
}

int isconst(char *sym) {
  struct Node *string, *found;
  
  if(! (string = (struct Node *) malloc(sizeof (struct Node)))) {
    perror("malloc");
    cleanup(), exit(1);
  }
  if(! (string->word = strdup(sym))) {
    perror("strdup");
    cleanup(), exit(1);
  }
  found = lookup(consttab, string, wordhash, wordcompare);
  free(string->word);
  free(string);
  if(found)
    return 1;
  return 0;
}

unsigned long getconst(char *sym) {
  struct Node *string, *found;
  
  if(! (string = (struct Node *) malloc(sizeof (struct Node)))) {
    perror("malloc");
    cleanup(), exit(1);
  }
  if(! (string->word = strdup(sym))) {
    perror("strdup");
    cleanup(), exit(1);
  }
  found = lookup(consttab, string, wordhash, wordcompare);
  free(string->word);
  free(string);
  return found->nlist.n_value;
}

static struct Node *symtab[HASHSIZE];

struct Node *mksym(char *sym) {
  struct Node *string, *found;
  
  if(! (string = (struct Node *) malloc(sizeof (struct Node)))) {
    perror("malloc");
    cleanup(), exit(1);
  }
  string->nlist.n_strx = 0; /* this will be properly initialized later */
  string->nlist.n_type = SMOKE16_N_UNDF;
  string->nlist.n_other = 0; /* stab "other" field */
  string->nlist.n_desc = 0; /* stab "desc" field */
  string->nlist.n_value = 0;
  string->sect = 0; /* assembler section number */
  string->refcnt = 0; /* refcount */
  string->word = sym;
  string->sect = -1; 
  if((found = lookup(symtab, string, wordhash, wordcompare))) {
    free(string->word);
    free(string);
  } else {
    found = enter(symtab, string, wordhash);
  }
  return found;
}

void mklo(char *sym, unsigned long addr, unsigned long value) {
  struct Node *node;
  reloc_t *reloc;

  if(! strcmp(sym, ".")) {
    free(sym);
    if(! (sym = strdup(info[sect].name))) {
      perror("strdup");
      cleanup(), exit(1);
    }
    value += info[sect].offset;
  }
  node = mksym(sym);
  addr = to_u16(addr);
  value = to_u16(value);
  if((sect != SECT_TEXT) && (sect != SECT_DATA) && (sect != SECT_RDATA)) {
    yyerror("relocations are not allowed in this section");
    return;
  }
#ifdef DEBUG_SYMS
  fprintf(stderr, "mklo(%s, %s+%#lx, %#lx)\n",
          node->word,
          info[sect].name, addr, value);
#endif
  if(info[sect].relocs &&
     (info[sect].relocs->sym == node) &&
     (info[sect].relocs->reloc.r_value == value) &&
     (SMOKE16_R_TYPE(info[sect].relocs->reloc) == SMOKE16_RELOC_ABSOLUTE) &&
     !SMOKE16_R_LOW(info[sect].relocs->reloc))
    reloc=info[sect].relocs;
  else {
    if(!(reloc = mkreloc(sect))) {
      yyerror("cannot make relocation");
      return;
    }
    node->refcnt++; /* increment refcount */
    reloc->sym = node;
    SMOKE16_SET_R_TYPE(reloc->reloc, SMOKE16_RELOC_ABSOLUTE);
    SMOKE16_SET_R_HIGH(reloc->reloc, 0);
    SMOKE16_SET_R_EXTERN(reloc->reloc, 1);
    reloc->reloc.r_value = value;
    info[sect].relocs = reloc;
  }
  SMOKE16_SET_R_LOW(reloc->reloc, 1);
  reloc->reloc.r_addr_low = addr;
}

void mkhi(char *sym, unsigned long addr, unsigned long value) {
  struct Node *node;
  reloc_t *reloc;

  if(! strcmp(sym, ".")) {
    free(sym);
    if(! (sym = strdup(info[sect].name))) {
      perror("strdup");
      cleanup(), exit(1);
    }
    value += info[sect].offset;
  }
  node = mksym(sym);
  addr = to_u16(addr);
  value = to_u16(value);
  if((sect != SECT_TEXT) && (sect != SECT_DATA) && (sect != SECT_RDATA)) {
    yyerror("relocations are not allowed in this section.");
    return;
  }
#ifdef DEBUG_SYMS
  fprintf(stderr, "mkhi(%s, %s+%#lx, %#lx)\n",
          node->word,
          info[sect].name, addr, value);
#endif
  if(info[sect].relocs &&
     (info[sect].relocs->sym == node) &&
     (info[sect].relocs->reloc.r_value == value) &&
     (SMOKE16_R_TYPE(info[sect].relocs->reloc) == SMOKE16_RELOC_ABSOLUTE) &&
     !SMOKE16_R_HIGH(info[sect].relocs->reloc))
    reloc=info[sect].relocs;
  else {
    if(!(reloc = mkreloc(sect))) {
      yyerror("cannot make relocation");
      return;
    }
    node->refcnt++; /* increment refcount */
    reloc->sym = node;
    SMOKE16_SET_R_TYPE(reloc->reloc, SMOKE16_RELOC_ABSOLUTE);
    SMOKE16_SET_R_LOW(reloc->reloc, 0);
    SMOKE16_SET_R_EXTERN(reloc->reloc, 1);
    reloc->reloc.r_value = value;
    info[sect].relocs = reloc;
  }
  SMOKE16_SET_R_HIGH(reloc->reloc, 1);
  reloc->reloc.r_addr_high = addr;
}

void mkdisp(char *sym, unsigned long addr, unsigned long value) {
  struct Node *node;
  reloc_t *reloc;

  if(! strcmp(sym, ".")) {
    free(sym);
    if(! (sym = strdup(info[sect].name))) {
      perror("strdup");
      cleanup(), exit(1);
    }
    value += info[sect].offset;
  }
  node = mksym(sym);
  addr = to_u16(addr);
  value = to_u16(value);
  if((sect != SECT_TEXT) && (sect != SECT_DATA) && (sect != SECT_RDATA)) {
    yyerror("relocations are not allowed in this section.");
    return;
  }
#ifdef DEBUG_SYMS
  fprintf(stderr, "mkdisp(%s, %s+%#lx, %#lx)\n",
          node->word,
          info[sect].name, addr, value);
#endif
  if(!(reloc = mkreloc(sect))) {
    yyerror("cannot make relocation");
    return;
  }
  node->refcnt++; /* increment refcount */
  reloc->sym = node;
  SMOKE16_SET_R_TYPE(reloc->reloc, SMOKE16_RELOC_DISP8);
  SMOKE16_SET_R_HIGH(reloc->reloc, 0);
  SMOKE16_SET_R_LOW(reloc->reloc, 1);
  SMOKE16_SET_R_EXTERN(reloc->reloc, 1);
  reloc->reloc.r_addr_high = 0;
  reloc->reloc.r_addr_low = addr;
  reloc->reloc.r_value = value;
  info[sect].relocs = reloc;
}

void define(char *sym, unsigned long addr) {
  struct Node *node;
  char *foosym;

  addr = to_u16(addr);
  if(!(foosym = strdup(sym))) {
    perror("strdup");
    cleanup(), exit(1);
  }
  node = mksym(sym);
  if((((node->nlist.n_type & SMOKE16_N_TYPE) != SMOKE16_N_UNDF) &&
      ((node->nlist.n_type & SMOKE16_N_TYPE) != SMOKE16__N_COMM)) ||
     (node->nlist.n_value != 0))
    if(((node->nlist.n_type & SMOKE16_N_TYPE) != info[sect].n_type) ||
       (node->sect != sect) ||
       (node->nlist.n_value != addr)) {
      char *foo = yytext;

      yytext = foosym;
      yyerror("incompatible symbol redefinition");
      yytext = foo;
      return;
    }
  node->nlist.n_type = (node->nlist.n_type & ~SMOKE16_N_TYPE) | info[sect].n_type;
  node->sect = sect;
  node->nlist.n_value = addr;
#ifdef DEBUG_SYMS
  fprintf(stderr, "define(%s, %s+%#" FMT_x16 "), n_type: %#" FMT_X8 ", sect: %s\n",
          node->word,
          info[sect].name, node->nlist.n_value, node->nlist.n_type, info[node->sect].name);
#endif
}

void mkglobal(char *sym) {
  struct Node *node = mksym(sym);

#ifdef DEBUG_SYMS
  fprintf(stderr, "mkglobal(%s)\n",
          node->word);
#endif
  node->nlist.n_type |= SMOKE16_N_EXT;
}

void mkcomm(char *sym, unsigned long size) {
  struct Node *node = mksym(sym);

#ifdef DEBUG_SYMS
  fprintf(stderr, "mkcomm(%s, %lu)\n",
          node->word, size);
#endif
  if (! size)
    yyerror ("common symbol requires a non-zero size");
  node->nlist.n_type |= SMOKE16_N_EXT;
  if((node->nlist.n_type & SMOKE16_N_TYPE) == SMOKE16_N_UNDF)
    node->nlist.n_type =
      (node->nlist.n_type & ~SMOKE16_N_TYPE)
      | SMOKE16__N_COMM;
  if(((node->nlist.n_type & SMOKE16_N_TYPE) == SMOKE16__N_COMM) &&
     (node->nlist.n_value < size))
    node->nlist.n_value = to_u16(size);
}

void mklcomm(char *sym, unsigned long size) {
  struct Node *node = mksym(sym);

#ifdef DEBUG_SYMS
  fprintf(stderr, "mklcomm(%s, %lu)\n",
          node->word, size);
#endif
  if (! size)
    yyerror ("local common symbol requires a non-zero size");
  if((node->nlist.n_type & SMOKE16_N_TYPE) == SMOKE16_N_UNDF)
    node->nlist.n_type =
      (node->nlist.n_type & ~SMOKE16_N_TYPE)
      | SMOKE16__N_COMM;
  if(((node->nlist.n_type & SMOKE16_N_TYPE) == SMOKE16__N_COMM) &&
     (node->nlist.n_value < size))
    node->nlist.n_value = to_u16(size);
}

int fixup(u16 toolversion,
	  u16 Ttext,
	  int Tdata_p, u16 Tdata,
	  int Tbss_p, u16 Tbss,
          int Entry_p, u16 *Entry) {
  reloc_t *reloc;
  struct Node *sym;
  struct SearchContext search;

  /* .comm -> external undefined, .lcomm -> bss */
  for(sym=start_search(symtab, &search, common_node); sym; sym = next_node(&search)) {
    if(sym->nlist.n_type & SMOKE16_N_EXT) {
      sym->nlist.n_type =
	(sym->nlist.n_type & ~SMOKE16_N_TYPE)
	| SMOKE16_N_UNDF;
    } else {
      u16 bytes = sym->nlist.n_value;

      sect = SECT_BSS;
      sym->nlist.n_type =
	(sym->nlist.n_type & ~SMOKE16_N_TYPE)
	| SMOKE16_N_BSS;
      sym->nlist.n_value = info[sect].offset;
      while (bytes--) {
	mkbyte(0);
	info[sect].offset++;
      }
    }
  }

  /* define special symbols for each section */
  for(sect = 0; sect < NSECTS; sect++) {
    char *s;

    if (! (s = strdup(info[sect].name))) {
      perror("strdup");
      cleanup(), exit(1);
    }
    define(s, 0);
  }

  /* fix the alignment where .rdata is squeezed in */
  sect = (info[SECT_RDATA].n_type == SMOKE16_N_TEXT)
    ? SECT_TEXT
    : SECT_RDATA;
  if(info[sect + 1].align > info[sect].align)
    info[sect].align = info[sect + 1].align;
  while(info[sect].offset !=
	((info[sect].offset >> info[sect + 1].align) << info[sect + 1].align)) {
    mkbyte(0);
    info[sect].offset++;
  }
  if(info[sect].align > info[sect + 1].align)
    info[sect + 1].align = info[sect].align;

  /* adjust symbol values to compensate for .rdata */
  for(sym=start_search(symtab, &search, extern_node); sym; sym = next_node(&search)) {
    if(info[SECT_RDATA].n_type == SMOKE16_N_DATA) {
      if(sym->sect == SECT_DATA)
	sym->nlist.n_value += info[SECT_RDATA].offset;
    } else {
      if(sym->sect == SECT_RDATA)
	sym->nlist.n_value += info[SECT_TEXT].offset;
    }
  }

  /* Calculate tentative load addresses */
  /* (Ttext and JMAGIC Tdata were already computed by our caller */
  if (! Tdata_p)
    Tdata = Ttext + info[SECT_TEXT].offset;
  if (! Tbss_p)
    Tbss = Tdata + info[SECT_DATA].offset;

  /* Choose an entry point, if one hasn't been chosen already */
  if (! Entry_p)
    *Entry = Ttext;

  /* Make resolved relocations local, and patch the sections */
  for(sect=0; sect<NSECTS; sect++)
    for(reloc = info[sect].relocs; reloc; reloc = reloc->next)
      if(SMOKE16_R_EXTERN(reloc->reloc) &&
         (reloc->sym->nlist.n_type & SMOKE16_N_TYPE)) {
	u16 value;
        u8 patched[2];

#ifdef DEBUG_RELOCS
        fprintf(stderr,
                "Converting Type: %" FMT_u16 " External: \"%s\"+%#" FMT_x16,
                SMOKE16_R_TYPE(reloc->reloc),
                reloc->sym->word,
                reloc->reloc.r_value
            );
#endif

        value = (reloc->reloc.r_value += reloc->sym->nlist.n_value);
        SMOKE16_SET_R_EXTERN(reloc->reloc, 0);
        reloc->reloc.r_index = reloc->sym->nlist.n_type;
        reloc->sym->refcnt--; /* decrease refcount */

	/* find a naive approximation of the relocated address */
	switch(reloc->reloc.r_index & SMOKE16_N_TYPE) {
	case SMOKE16_N_TEXT:
	  value += Ttext;
	  break;
	case SMOKE16_N_DATA:
	  value += Tdata;
	  break;
	case SMOKE16_N_BSS:
	  value += Tbss;
	  break;
	}

	/* adjust value for relative relocations */
	if((SMOKE16_R_TYPE(reloc->reloc) == SMOKE16_RELOC_DISP8) &&
	   SMOKE16_R_LOW(reloc->reloc)) {
	  u16 adjust = 0;

	  /* address adjustment to compensate for load address */
	  switch (sect) {
	  case SECT_TEXT:
	    adjust = Ttext;
	    break;
	  case SECT_RDATA:
	    if (info[SECT_RDATA].n_type == SMOKE16_N_TEXT)
	      adjust = Ttext + info[SECT_TEXT].offset;
	    else
	      adjust = Tdata;
	    break;
	  case SECT_DATA:
	    if (info[SECT_RDATA].n_type == SMOKE16_N_TEXT)
	      adjust = Tdata;
	    else
	      adjust = Tdata + info[SECT_RDATA].offset;
	    break;
	  case SECT_BSS:
	    adjust = Tbss;
	    break;
	  }

	  value -= reloc->reloc.r_addr_low + adjust;
	  value --;
	  value /= 2;
	}

#ifdef DEBUG_RELOCS
        fprintf(stderr,
                " -> Internal: n_type %#" FMT_X16
                " Value: %#" FMT_x16 " [0x",
                reloc->reloc.r_index,
                reloc->reloc.r_value
            );
#endif

	/* patch the section file */
        /* FIXME: this should not use fseek directly (perhaps fgetpos/fsetpos?) */
        patched[0] = (value >> 8) & 0xffU;
        patched[1] = value & 0xffU;
	if (SMOKE16_R_HIGH(reloc->reloc)) {
#ifdef DEBUG_RELOCS
          fprintf(stderr,
                  "%2.2" FMT_x8,
                  patched[0]
              );
#endif
          if (! fseek(info[sect].file -> file, (long)reloc->reloc.r_addr_high, SEEK_SET)) {
            if (! fput_u8(patched, 1, info[sect].file -> file)) {
              fprintf(stderr, "%s(%s) ", info[sect].file->name, info[sect].name);
              perror("cannot write");
              yyerror("cannot write byte");
            }
          } else {
            fprintf(stderr, "%s: (%s) ", info[sect].file->name, info[sect].name);
            perror("cannot seek");
            yyerror("cannot seek");
          }
        } else {
#ifdef DEBUG_RELOCS
          fprintf(stderr,
                  ".."
              );
#endif
        }
	if (SMOKE16_R_LOW(reloc->reloc)) {
#ifdef DEBUG_RELOCS
          fprintf(stderr,
                  "%2.2" FMT_x8,
                  patched[1]
              );
#endif
          if (! fseek(info[sect].file -> file, (long)reloc->reloc.r_addr_low, SEEK_SET)) {
            if (! fput_u8(patched + 1, 1, info[sect].file -> file)) {
              fprintf(stderr, "%s(%s) ", info[sect].file->name, info[sect].name);
              perror("cannot write");
              yyerror("cannot write byte");
            }
          } else {
            fprintf(stderr, "%s: (%s) ", info[sect].file->name, info[sect].name);
            perror("cannot seek");
            yyerror("cannot seek");
          }
        } else {
#ifdef DEBUG_RELOCS
          fprintf(stderr,
                  ".."
              );
#endif
        }
#ifdef DEBUG_RELOCS
        fprintf(stderr,
                "]\n"
            );
#endif

      }
#ifdef DEBUG_RELOCS
  for(sect=0; sect<NSECTS; sect++)
    if(info[sect].relocs)
      {
        fprintf(stderr, "Relocations: %s\n", info[sect].name);
        for(reloc=info[sect].relocs; reloc; reloc=reloc->next) {
          if(SMOKE16_R_EXTERN(reloc->reloc))
            fprintf(stderr,
                    "Type: %" FMT_u16 " External: \"%s\"+%#" FMT_x16 "\n",
		    SMOKE16_R_TYPE(reloc->reloc),
		    reloc->sym->word,
		    reloc->reloc.r_value
                   );
          else
            fprintf(stderr,
                    "Type: %" FMT_u16 " Internal: n_type %#" FMT_X16
                    " Value: %#" FMT_x16 "\n",
		    SMOKE16_R_TYPE(reloc->reloc),
		    reloc->reloc.r_index,
		    reloc->reloc.r_value
                   );
          if(SMOKE16_R_HIGH(reloc->reloc))
            fprintf(stderr, "\tHigh byte at: %#" FMT_x16 "\n", reloc->reloc.r_addr_high);
          if(SMOKE16_R_LOW(reloc->reloc))
            fprintf(stderr, "\tLow byte at: %#" FMT_x16 "\n", reloc->reloc.r_addr_low);
        }
      }
#endif /* DEBUG_RELOCS */
  return 0;
}

void mkbyte(unsigned long byte) {
  u8 c;

  c = to_u8(byte);
  if(sect == SECT_ABS) {
    yyerror("can't write to ABS");
  } else if(sect == SECT_BSS) {
    if(byte)
      yyerror("non-zero value not allowed in BSS");
  } else {
    if(!info[sect].file)
      info[sect].file = newTmpFile();
    if(!info[sect].file) {
      fprintf(stderr, "%s: cannot create temporary file\n", info[sect].name);
      yyerror("cannot write byte to section");
      return;
    }
    if(!fput_u8(&c, 1, info[sect].file->file)) {
      fprintf(stderr, "%s: (%s) ", info[sect].file->name, info[sect].name);
      perror("cannot write");
      yyerror("cannot write byte");
      return;
    }
#ifdef DEBUG_SECTS
    else
      fprintf(stderr,
	      "mkbyte(%s+%#lx): %#lx\n",
	      info[sect].name, info[sect].offset,
	      byte);
#endif
  }
}

void mkword(unsigned long word) {
  u16 nword;

  nword = to_u16(word);
  if(sect == SECT_ABS) {
    yyerror("can't write to ABS");
  } else if(sect == SECT_BSS) {
    if(word)
      yyerror("non-zero value not allowed in BSS");
  } else {
    if(!info[sect].file)
      info[sect].file = newTmpFile();
    if(!info[sect].file) {
      fprintf(stderr, "%s: cannot create temporary file\n", info[sect].name);
      yyerror("cannot write word to section");
      return;
    }
    if(!fput_u16(&nword, 1, info[sect].file->file)) {
      fprintf(stderr, "%s(%s) ", info[sect].file->name, info[sect].name);
      perror("cannot write");
      yyerror("cannot write word");
      return;
    }
#ifdef DEBUG_SECTS
    else
      fprintf(stderr,
	      "mkword: (%s+%#lx): %#lx\n",
	      info[sect].name, info[sect].offset,
	      word);
#endif
  }
}

void mkaout(char *objfilename, u16 toolversion, u16 Entry, u16 Magic) {
  FILE *objfile;
  struct a_out16_exec header;
  reloc_t *reloc;
  int ok = 1;

  if(objfilename[0] == '-' && objfilename[1] == '\0') {
    objfilename = "stdout";
    objfile = stdout;
  } else if(!(objfile = fopen(objfilename, "wb"))) {
    perror(objfilename);
    cleanup(), exit(1);
  }

  /* for aligned sections, write special @x alignment symbol */
  /* (not in "v0" files, though, because the older linker does not understand them) */
  if (toolversion > 0U)
      for(sect = 0; sect < NSECTS; sect++)
          if(((sect == SECT_TEXT) ||
              (sect == SECT_DATA) ||
              (sect == SECT_BSS)) &&
             info[sect].align) {
              char *s;
              struct Node *node;

              if(! (s = strdup(info[sect].align_sym))) {
                  perror("strdup");
                  cleanup(), exit(1);
              }
              if (! (node = mksym(s))) {
                  fprintf (stderr, "mksym(`%s') failed!\n", s);
                  cleanup(), exit(1);
              }
              node->nlist.n_type = SMOKE16_N_ALIGN | SMOKE16_N_EXT;
              node->nlist.n_value = info[sect].align;
          }

  for(sect = 0; sect < NSECTS; sect++)
    if(info[sect].file)
      rewind(info[sect].file->file);
  SMOKE16_SET_A_DYNAMIC(header, 0);
  SMOKE16_SET_A_TOOLVERSION(header, toolversion);
  SMOKE16_SET_A_MACHTYPE(header, M_SMOKE16);
  header.a_text = info[SECT_TEXT].offset +
    (info[SECT_RDATA].n_type == SMOKE16_N_TEXT ? info[SECT_RDATA].offset : 0UL);
  header.a_data = info[SECT_DATA].offset +
    (info[SECT_RDATA].n_type == SMOKE16_N_DATA ? info[SECT_RDATA].offset : 0UL);
  header.a_bss = info[SECT_BSS].offset;
  /* calculate symbol table length and indices */
  if(ok)
  {
    struct Node *sym;
    struct SearchContext search;
    int nsyms = 0;

    for(sym=start_search(symtab, &search, extern_node); sym; sym = next_node(&search)) {
      sym->index = nsyms++;
    }
    header.a_syms = nsyms * fsz16nlist(SMOKE16_A_TOOLVERSION(header));
#ifdef DEBUG_SYMS
    fprintf(stderr, "%" FMT_u16 "-byte symtab (%d syms)\n", header.a_syms, nsyms);
#endif
  }
  header.a_trsize = 0;
  header.a_drsize = 0;
  for(reloc = info[SECT_TEXT].relocs;
      reloc && ok;
      reloc = reloc->next)
    header.a_trsize += fsz16reloc(SMOKE16_A_TOOLVERSION(header));
  for(reloc = info[SECT_RDATA].relocs;
      reloc && ok;
      reloc = reloc->next)
    if (info[SECT_RDATA].n_type == SMOKE16_N_TEXT)
      header.a_trsize += fsz16reloc(SMOKE16_A_TOOLVERSION(header));
    else
      header.a_drsize += fsz16reloc(SMOKE16_A_TOOLVERSION(header));
  for(reloc = info[SECT_DATA].relocs;
      reloc && ok;
      reloc = reloc->next)
    header.a_drsize += fsz16reloc(SMOKE16_A_TOOLVERSION(header));
  header.a_magic = Magic;

  /* set the default entry point (not usually used) */
  header.a_entry = Entry;

  if(!fput16exec(&header, 1, objfile)) {
    perror(objfilename);
    ok = 0;
  }

  if(info[(sect = SECT_TEXT)].offset) {
    if(fcopy(info[sect].file->file, objfile, info[sect].offset) != info[sect].offset)
      {
	fprintf(stderr, "%s: (%s) can't copy to ", info[sect].file->name, info[sect].name);
	perror(objfilename);
	ok = 0;
      }
  }
  if(info[(sect = SECT_RDATA)].offset) {
    if(fcopy(info[sect].file->file, objfile, info[sect].offset) != info[sect].offset)
      {
	fprintf(stderr, "%s: (%s) can't copy to ", info[sect].file->name, info[sect].name);
	perror(objfilename);
	ok = 0;
      }
  }
  if(info[(sect = SECT_DATA)].offset) {
    if(fcopy(info[sect].file->file, objfile, info[sect].offset) != info[sect].offset)
      {
	fprintf(stderr, "%s: (%s) can't copy to ", info[sect].file->name, info[sect].name);
	perror(objfilename);
	ok = 0;
      }
  }
  for(reloc = info[SECT_TEXT].relocs;
      reloc && ok;
      reloc = reloc->next) {
    if(SMOKE16_R_EXTERN(reloc->reloc))
      reloc->reloc.r_index = reloc->sym->index;
    if(!fput16reloc(&(reloc->reloc), 1, objfile)) {
      perror(objfilename);
      ok = 0;
    }
  }
  for(reloc = info[SECT_RDATA].relocs;
      reloc && ok;
      reloc = reloc->next) {
    if(SMOKE16_R_EXTERN(reloc->reloc))
      reloc->reloc.r_index = reloc->sym->index;
    if(info[SECT_RDATA].n_type == SMOKE16_N_TEXT) {
      if(SMOKE16_R_LOW(reloc->reloc))
	reloc->reloc.r_addr_low += info[SECT_TEXT].offset;
      if(SMOKE16_R_HIGH(reloc->reloc))
	reloc->reloc.r_addr_high += info[SECT_TEXT].offset;
    }
    if(!fput16reloc(&(reloc->reloc), 1, objfile)) {
      perror(objfilename);
      ok = 0;
    }
  }
  for(reloc = info[SECT_DATA].relocs;
      reloc && ok;
      reloc = reloc->next) {
    if(SMOKE16_R_EXTERN(reloc->reloc))
      reloc->reloc.r_index = reloc->sym->index;
    if(info[SECT_RDATA].n_type == SMOKE16_N_DATA) {
      if(SMOKE16_R_LOW(reloc->reloc))
	reloc->reloc.r_addr_low += info[SECT_RDATA].offset;
      if(SMOKE16_R_HIGH(reloc->reloc))
	reloc->reloc.r_addr_high += info[SECT_RDATA].offset;
    }
    if(!fput16reloc(&(reloc->reloc), 1, objfile)) {
      perror(objfilename);
      ok = 0;
    }
  }
  if(ok) {
    u16 stroff = 2, strsize;
    struct Node *sym;
    struct SearchContext search;

    /* write symbols and calculate string table size */
    for(sym=start_search(symtab, &search, extern_node); sym && ok; sym = next_node(&search)) {
      sym->nlist.n_strx = stroff;
      stroff += strlen(sym->word) + 1;
      if(!fput16nlist(&(sym->nlist), 1, objfile)) {
        perror(objfilename);
	ok = 0;
      }
    }

    /* write string table */
    if(ok) {
      strsize = stroff;
      if(!fput_u16(&strsize, 1, objfile)) {
	perror(objfilename);
	ok = 0;
      }
      for(sym=start_search(symtab, &search, extern_node); sym && ok; sym = next_node(&search))
	if(fput_as(sym->word, strlen(sym->word) + 1, objfile)
	   != (strlen(sym->word) + 1)) {
	  if (ferror(objfile))
	    perror(objfilename);
	  else
	    fprintf(stderr, "%s: couldn't convert symbol name to portable encoding: `%s'\n",
		    objfilename, sym -> word);
	  ok = 0;
	}
    }
  }
  fclose(objfile);
  if(!ok) {
    if(unlink(objfilename))
      fputs(objfilename, stderr),
      perror(": cannot remove file");
    cleanup(), exit(1);
  }
}

int yyerror(char *s)
{
  fprintf(stderr, "%s:%d: near `%s': %s (%s+%4.4lX)\n",
	  infile,
	  lineno,
	  (yytext ? ((yytext[0] == '\n') ? "<newline>" : yytext) : "(null)"),
	  s,
	  info[sect].name,
	  info[sect].offset);
  if(parse_errors++ > MAX_PARSE_ERRORS) {
    fputs(infile, stderr);
    fprintf(stderr, ": too many errors, exiting.\n");
    cleanup(), exit(1);
  }
  return 0;
}

#define SUMMARY \
   "Summary: %s [-help] [-version] [options] asm-file...\n"

#define USAGE "\
Usage: %s [options] asm-file...\n\
Options:\n\
-A vNUM                 assemble for SMOKE-16 toolset vNUM\n\
-h or -help             show this message and exit\n\
-J                      ignore overflow\n\
-o OBJ or\n\
-output-file OBJ        name generated object file (default: a.out)\n\
-portable               use portable execution character encoding\n\
-R                      place read-only data (.rdata) in the text section\n\
                        (incompatible with split I&D)\n\
-7 or -r7 or -t6        allow use of reserved register %%7/%%r7/%%t6\n\
-E [0x]HEX              override the entry point\n\
-Tbss [0x]HEX           set the load address of the BSS section\n\
-Tdata [0x]HEX          set the load address of the text section\n\
-T[text] [0x]HEX        set the load address of the text section\n\
-N or -omagic           generate object or impure executable (OMAGIC)\n\
-n or -nmagic           generate pure executable (NMAGIC)\n\
-j or -jmagic           generate split I&D executable (JMAGIC)\n\
-V or -version          print assembler version and exit\n\
"

#define VERSION \
   "SMOKE-16 assembler $Revision: 1.23 $\n" \
   "         " YACC_VERSION \
   "         " LEX_VERSION \
   SMOKE16_WARRANTY

void cleanup(void)
{
  for(sect=0; sect<NSECTS; sect++)
    if(info[sect].file)
      freeTmpFile(info[sect].file);
}

int main(int argc, char *argv[])
{
  int Tbss_p = 0, Tdata_p = 0, Ttext_p = 0, Entry_p = 0;
  u16 Tbss = 0, Tdata = 0, Ttext = 0, Entry = 0;
  u16 Magic = SMOKE16_OMAGIC;
  char *objfile = "a.out";
  int i, option_index;
  u16 toolversion = SMOKE16_TOOLVERSION;
  static struct option long_options[] = {
    {"portable", 0, 0, 0},
    {"help", 0, 0, 'h'},
    {"output-file", 1, 0, 'o'},
    {"r7", 0, 0, '7'},
    {"t6", 0, 0, '7'},
    {"version", 0, 0, 'V'},
    {"Tbss", 1, 0, 0},
    {"Tdata", 1, 0, 0},
    {"Ttext", 1, 0, 'T'},
    {"omagic", 0, 0, 'N'},
    {"nmagic", 0, 0, 'n'},
    {"jmagic", 0, 0, 'j'},
    {0, 0, 0, 0}
  };

  if (! (infile = strdup("stdin"))) {
    perror("strdup");
    cleanup(), exit(1);
  }
  while((i = getopt_long_only(argc, argv, "A:hJo:R7E:T:NnjV", long_options, &option_index)) !=
	-1)
    switch(i) {
    case 0:
      switch(option_index) {
      case 0:
	xencoding = ENCODING_PORTABLE;
	break;
      case 6:
	Tbss_p = 1;
	if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	  optarg += 2;
	if (sscanf(optarg, "%" FMT_x16 "%s", &Tbss, optarg) != 1)
	  fprintf(stderr,
		  "%s: %s: not a valid hexadecimal number\n",
		  argv[0],
		  optarg),
	  exit(1);
	break;
      case 7:
	Tdata_p = 1;
	if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	  optarg += 2;
	if (sscanf(optarg, "%" FMT_x16 "%s", &Tdata, optarg) != 1)
	  fprintf(stderr,
		  "%s: %s: not a valid hexadecimal number\n",
		  argv[0],
		  optarg),
	  exit(1);
	break;
      default:
        fprintf(stderr, SUMMARY, argv[0]);
        cleanup(), exit(2);
      }
      break;
    case 'A':
      if (*optarg != 'v')
	fprintf(stderr,
		"%s: %s: not a valid version\n",
		argv[0],
		optarg),
	  cleanup(), exit(1);
      optarg++;
      if (sscanf(optarg, "%" FMT_u16 "%s", &toolversion, optarg) != 1)
	fprintf(stderr,
		"%s: %s: not a valid number\n",
		argv[0],
		optarg),
	  cleanup(), exit(1);
      if (toolversion > SMOKE16_TOOLVERSION)
	fprintf(stderr,
		"%s: toolset version must be between 0 and %" FMT_u16 "\n",
		argv[0],
		SMOKE16_TOOLVERSION),
	  cleanup(), exit(1);
      break;
    case 'J':
      ignore_overflow = 1;
      break;
    case 'h':
      printf(USAGE, argv[0]);
      cleanup(), exit(0);
      break;
    case 'R':
      info[SECT_RDATA].n_type = SMOKE16_N_TEXT;
      break;
    case 'o':
      objfile = optarg;
      break;
    case '7':
      allow_r7 = 1;
      break;
    case 'T':
      Ttext_p = 1;
      if (optarg[0] == '0' && ((optarg[1] == 'x') || (optarg[1] == 'X')))
	optarg += 2;
      if (sscanf(optarg, "%" FMT_x16 "%s", &Ttext, optarg) != 1)
	fprintf(stderr,
		"%s: %s: not a valid hexadecimal number\n",
		argv[0],
		optarg),
	  exit(1);
      break;
    case 'V':
      printf(VERSION, SMOKE16_TOOLVERSION);
      cleanup(), exit(0);
      break;
    case 'N':
      Magic = SMOKE16_OMAGIC;
      break;
    case 'n':
      Magic = SMOKE16_NMAGIC;
      break;
    case 'j':
      Magic = SMOKE16_JMAGIC;
      break;
    default:
      fprintf(stderr, SUMMARY, argv[0]);
      cleanup(), exit(2);
    }
  if(optind < argc) {
    while (optind < argc) {
      if(argv[optind] &&
         (argv[optind][0] == '-') &&
         (argv[optind][1] == '\0')) {
        yyin = stdin;
	if(infile)
	  free(infile);
	lineno = 1;
        if (! (infile = strdup("stdin"))) {
	  perror("strdup");
	  cleanup(), exit(1);
	}
        yyparse();
      } else {
        if(!(yyin = fopen(argv[optind], "r")))
          perror(argv[optind]), cleanup(), exit(1);
	if(infile)
	  free(infile);
	lineno = 1;
        if (! (infile = strdup(argv[optind]))) {
	  perror("strdup");
	  cleanup(), exit(1);
	}
        yyparse();
        fclose(yyin);
      }
      optind++;
    }
  } else {
    fprintf(stderr, "%s: need at least one non-option argument.\n", argv[0]);
    fprintf(stderr, SUMMARY, argv[0]);
    cleanup(), exit(2);
  }
  if (! Ttext_p) {
    Ttext_p = 1;
    Ttext = (toolversion == 0U)
      ? 0x0000U
      : 0x0400U;
  }
  if ((Magic == SMOKE16_JMAGIC) &&
      ! Tdata_p) {
    Tdata_p = 1;
    Tdata = (toolversion == 0U)
      ? 0x0000U
      : 0x0400U;
  }
  fixup(toolversion, Ttext, Tdata_p, Tdata, Tbss_p, Tbss, Entry_p, &Entry);
  if(parse_errors) {
    fputs(infile, stderr);
    fprintf(stderr,
	    ": %d error%s.\n",
	    parse_errors,
	    ((parse_errors == 1) ? "" : "s"));
    cleanup();
    exit(1);
  }
  mkaout(objfile, toolversion, Entry, Magic);
  cleanup();
  return 0;
}
