/* xa_link.c

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 3, 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, see <http://www.gnu.org/licenses/>. */

/* WORK IN PROGRESS: do not watch this if you don't have the legal
   age in your country to watch this.
*/

/* This is a cheap hack. The xa51 has a couple of ways to scramble
   relocation info into it's opcode that the standard linker can't
   handle, not to mention word allignment. 

   No hash or qsort yet.

   The relocatable format looks like the known one, BUT ISN'T.

   The only things that are handled now are:

   "SDCCXA rel, version %f" must be the first line, sort of MAGIC word
   "H %d areas %d global symbols" defines the # of areas and globals
   "S <symbol> [Ref0000 | DefXXXX | AbsXXXX]" Def's are supposed to be
     defined in their own area/segment
   "A <seg> size %d flags %d" switch to another segment. this can happen
     multiple times and should be equal. flags is ignored for now
   "T xxxx <how> <symbol> 0"
   "R xxxx <how> <symbol> <pc+>" the relocation info. xxxx is the address
     within relative code space. How is something like REL_FF, REL_FFFF, 
     ABS_70FF. Symbol is the referenced symbol and pc+ is the program 
     counter that will be used to calculate the relative address (that is
     the address of the following instruction).

   So, this is not a standalone linker. It will only link files generated
   by xa_rasm, which will only process files generated by the xa51 sdcc
   port.
*/

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include "xa_version.h"

enum {
  // these are all concatenated into the code image
  GSINIT=1,
  CSEG,
  XINIT,

  // here goes the final output and should be used by the assembler
  GSFINAL,

  // these are only for storage
  BSEG,
  DSEG,
  XSEG,
  XISEG,

  // that's all
  MAX_SEGMENTS
};

enum {
  REL_FF=1,
  REL_FFFF,
  BIT_03FF,
  DIR_07FF,
  DIR_70FF,
  DIR_0700FF,
  ABS_0F,
  ABS_FF,
  ABS_FFFF,
  ABS_PC,
  MAX_REFS
};

char *refModes[]={
  "???",
  "REL_FF",
  "REL_FFFF",
  "BIT_03FF",
  "DIR_07FF",
  "DIR_70FF",
  "DIR_0700FF",
  "ABS_0F",
  "ABS_FF",
  "ABS_FFFF",
  "ABS_PC"
};

#define CODESIZE 0x10000

int fatalErrors=0;

unsigned char gsinitImage[CODESIZE];
unsigned char csegImage[CODESIZE];
unsigned char xinitImage[CODESIZE];
unsigned char gsfinalImage[CODESIZE];

struct SEGMENT {
  short id;
  char *name;
  int hasSymbols;
  int _size;
  int start;
  int current;
  unsigned char *image;
} segments[MAX_SEGMENTS]={
  {0,       "???",    0, 0, 0, 0, NULL},

  {GSINIT,  "GSINIT",  0, 0, 0, 0, gsinitImage},
  {CSEG,    "CSEG",    0, 0, 0, 0, csegImage},
  {XINIT,   "XINIT",   0, 0, 0, 0, xinitImage},
  {GSFINAL, "GSFINAL", 0, 0, 0, 0, gsfinalImage},

  {BSEG,    "BSEG",    0, 0, 0, 0, NULL},
  {DSEG,    "DSEG",    0, 0, 0, 0, NULL},
  {XSEG,    "XSEG",    0, 0, 0, 0, NULL},
  {XISEG,   "XISEG",   0, 0, 0, 0, NULL},
};

struct MODULE {
  char *name;
  int offset[MAX_SEGMENTS];
  int size[MAX_SEGMENTS];
  int isLib;
  struct MODULE *next;
  struct MODULE *last;
} *modules=NULL;


struct SYMBOL {
  char *name;
  struct MODULE *module;
  int lineno;
  struct SEGMENT *segment;
  char absolute;
  int address;
  struct SYMBOL *next;
  struct SYMBOL *last;
} *symbols=NULL;

struct REFERENCE {
  char *name;
  struct MODULE *module;
  struct SEGMENT *segment;
  int lineno;
  unsigned address, pc;
  short how;
  short resolved;
  struct REFERENCE *next;
  struct REFERENCE *last;
} *references=NULL;

char *libraryPaths[128];
int nlibPaths=0;
char *libraryFiles[128];
int nlibFiles=0;

static char outFileName[PATH_MAX]={'\0'};
static char mapFileName[PATH_MAX]={'\0'};
FILE *mapOut;

struct SEGMENT *currentSegment;
struct MODULE *currentModule;
int currentLine;

int howToReference(char *how) {
  int r;
  for (r=1; r<MAX_REFS; r++) {
    if (strcmp(refModes[r], how)==0) {
      return r;
    }
  }
  return 0;
}

struct SEGMENT *findSegmentByName(char *segment) {
  int s;
  for (s=0; s<MAX_SEGMENTS; s++) {
    if (strcmp(segments[s].name, segment)==0) {
      return &segments[s];
    }
  }
  return 0;
}

struct SYMBOL *findSymbolByName(char *symName) {
  struct SYMBOL *symbol;
  for (symbol=symbols; symbol; symbol=symbol->next) {
    if (strcmp(symbol->name, symName)==0) {
      return symbol;
    }
  }
  return 0;
}

struct MODULE *findModuleByName(char *modName) {
  struct MODULE *module;
  for (module=modules; module; module=module->next) {
    if (strcmp(module->name, modName)==0) {
      return module;
    }
  }
  return NULL;
}

void addToModules (char *name, int isLib) {
  struct MODULE *module;
  int s;

  module=calloc(1, sizeof(struct MODULE));
  module->name=strdup(name);
  for (s=0; s<MAX_SEGMENTS; s++) {
    module->offset[s]=(segments[s]._size+1)&0xfffffe;
  }
  module->isLib=isLib;
  if (!modules) {
    modules=module;
   } else {
    modules->last->next=module;
  }
  currentModule=modules->last=module;
}

void addToRefs(char *ref, int address, char *how, int pc) {
  struct REFERENCE *reference;

  reference=calloc(1, sizeof(struct REFERENCE));
  reference->name=strdup(ref);
  reference->module=currentModule;
  reference->segment=currentSegment;
  reference->lineno=currentLine;
  reference->address=address;
  reference->how=howToReference(how);
  if (reference->how==ABS_PC) {
    reference->resolved=1;
  }
  reference->pc=pc;
  if (!references) {
    references=reference;
  } else {
    references->last->next=reference;
  }
  references->last=reference;
}

void resolve() {
  struct REFERENCE *reference;
  for (reference=references; reference; reference=reference->next) {
    if ((reference->how==ABS_PC) || findSymbolByName(reference->name)) {
      reference->resolved=1;
    }
  }
}

int isUnresolved(char *ref, int resolved) {
  struct REFERENCE *reference;

  for (reference=references; reference; reference=reference->next) {
    if (strcmp(reference->name, ref)==0) {
      // found 
      if (reference->resolved) {
	// already resolved
	return 0;
      }
      if (resolved) {
	reference->resolved=1;
	return 1;
      }
    }
  }
  return 0;
}

void addToDefs(char *def, int address, char absolute) {
  struct SYMBOL *symbol;

  // no duplicates allowed
  if ((symbol=findSymbolByName(def))) {
    fprintf (stderr, "*** %s:%d duplicate symbol %s first defined in "
	     "module %s:%d\n",
	     currentModule->name, currentLine, def, 
	     symbol->module->name, symbol->lineno);
    fatalErrors++;
  }

  symbol=calloc(1, sizeof(struct SYMBOL));
  symbol->name=strdup(def);
  symbol->module=currentModule;
  symbol->lineno=currentLine;
  symbol->segment=currentSegment;
  symbol->absolute=absolute;
  symbol->address=currentModule->offset[currentSegment->id]+address;
  if (!symbols) {
    symbols=symbol;
  } else {
    symbols->last->next=symbol;
  }
  symbols->last=symbol;
  currentSegment->hasSymbols++;
}

void syntaxError (char *err) {
  fprintf (stderr, "*** %s:%d error while parsing '%s'\n", 
	   currentModule->name, currentLine, err);
  fatalErrors++;
}

void readModule(char *module, int isLib) {
  double hisVersion;
  char line[132];
  FILE *relModule;
  char moduleName[PATH_MAX];
  int segments, globals;

  currentLine=1;

  if ((relModule=fopen(module, "r"))==NULL) {
    perror (module);
    exit (1);
  }

  // first we need to check if this is a valid file
  if (sscanf(fgets(line, 132, relModule), 
	     "SDCCXA rel, version %lf", &hisVersion)!=1) {
    fprintf (stderr, "*** %s is not a valid input file\n", module);
    exit (1);
  }
  if (hisVersion!=version) {
    fprintf (stderr, "*** WARNING: version conflict; "
	     "we(%1.1f) != %s(%1.1f)\n", 
	     version, module, hisVersion);
  }
  currentLine++;
  
  // H 7 areas 168 global symbols
  if (sscanf(fgets(line, 132, relModule),
	     "H %d areas %d global symbols",
	     &segments, &globals)!=2) {
    syntaxError(line);
  }
  currentLine++;

  // M module
  if (sscanf(fgets(line, 132, relModule),
	     "M %s", moduleName)!=1) {
    syntaxError(line);
  }

  // add this to the known modules with current offsets
  addToModules(module, isLib);

  currentLine++;

  // now for the ASTR tags
  while (fgets(line, 132, relModule)) {
    switch (line[0]) 
      {
      case 'A': {
	char segment[32];
	int size, flags;
	if (sscanf(line, "A %[^ ] size %d flags %d",
		   segment, &size, &flags)!=3) {
	  syntaxError(line);
	}
	// do we know this segment?
	if (!(currentSegment=findSegmentByName(segment))) {
	  fprintf (stderr, "*** %s:%d unknown area: %s\n", module,
		   currentLine, segment);
	  exit (1);
	}
	// double check repeated 'A' records
	if (currentModule->size[currentSegment->id]) {
	  // pleased to meet you again, I hope ...
	  if (currentModule->size[currentSegment->id] != size) {
	    fprintf (stderr, "*** %s:%d error %s size %d != %d\n",
		     module, currentLine,
		     currentSegment->name,
		     currentModule->size[currentSegment->id], 
		     size);
	    fatalErrors++;
	  }
	} else {
	  currentSegment->_size += size;
	  currentModule->size[currentSegment->id] = size;
	}
	// never mind about the flags for now
	break;
      }
      case 'S': {
	char symbol[132];
	char refdef[132];
	unsigned int address;
	if (sscanf(line, "S %[^ ] %s", symbol, refdef)!=2) {
	  fprintf (stderr, "*** %s:%d syntax error near \"%s\"\n",
		   module, currentLine, line);
	  exit (1);
	}
	if (strncmp(refdef, "Ref", 3)==0) {
	  // we don't need them
	} else if (strncmp(refdef, "Def", 3)==0) {
	  sscanf (refdef, "Def%04x", &address);
	  addToDefs(symbol, address, 0);
	} else if (strncmp(refdef, "Abs", 3)==0) {
	  sscanf (refdef, "Abs%04x", &address);
	  addToDefs(symbol, address, 1);
	} else {
	  fprintf (stderr, "%s:%d found invalid symbol definition \"%s\"\n", 
		   module, currentLine, line);
	  exit (1);
	}
	break;
      }
      case 'T': {
	unsigned int address;
	unsigned int byte;
	char *tline=NULL;
	if (currentSegment->id!=CSEG && 
	    currentSegment->id!=GSINIT &&
	    currentSegment->id!=XINIT) {
	  fprintf (stderr, "%s:%d cannot emit bytes in %s\n",
		   module, currentLine, currentSegment->name);
	  exit (1);
	}
	if (sscanf(strtok(&line[2], " "), "%04x", &address)!=1) {
	  fprintf (stderr, "%s:%d error in T record\n", module, currentLine);
	  fatalErrors++;
	}

	address+=currentModule->offset[currentSegment->id];
	//address+=currentSegment->current;
	for ( ;
	      (tline=strtok(NULL, " \t\n")) && 
		(sscanf(tline, "%02x", &byte)==1);
	      ) {
	  currentSegment->image[address++]=byte;
	  currentSegment->current++;
	}
	break;
      }
      case 'R': {
	unsigned address, pc;
	char symbol[132];
	char how[32];
	sscanf (line, "R %x %[^ ] %[^ ] %x", &address, how, symbol, &pc);
	addToRefs (symbol, address, how, pc);
	break;
      }
      default:
	fprintf (stderr, "%s:%d unknown record \"%s\"\n",
		 module, currentLine, line);
	fatalErrors++;
	break;
      }
    currentLine++;
  }
  fclose (relModule);
}

void writeModule(char *outFileName) {
  FILE *fOut;
  unsigned int address=segments[GSFINAL].start;
  unsigned int size=segments[GSFINAL]._size;
  unsigned int len;
  unsigned int checksum;

  if ((fOut=fopen(outFileName, "w"))==NULL) {
    perror (outFileName);
  }

  while (size) {
    len = size>16 ? 16 : size;
    size-=len;
    fprintf (fOut, ":%02X%04X%02X", len, address, 0);
    checksum = len + (address>>8) + (address&0xff);
    while (len--) {
      checksum += gsfinalImage[address];
      fprintf (fOut, "%02X", gsfinalImage[address++]);
    }
    checksum &= 0xff;
    if (checksum) {
      checksum = 0x100 - checksum;
    }
    fprintf (fOut, "%02X\n", checksum);
  }
  fprintf (fOut, ":00000001FF\n");

  fclose (fOut);
}

int relocate() {
  struct SYMBOL *symbol;
  struct REFERENCE *reference;
  char *from, *to;
  int length=segments[GSINIT]._size +
    segments[CSEG]._size +
    segments[XINIT]._size;
  int unresolved=0;

  // first check if it will fit
  if (length > 0xffff) {
    fprintf (stderr, "error: code segment exceeds 0xffff\n");
    fatalErrors++;
  }

  // resolve reverences
  for (reference=references; reference; reference=reference->next) {
    if (!reference->resolved && !findSymbolByName(reference->name)) {
      unresolved++;
    }
  }
  if (unresolved) {
    // first scan the libraries
    return unresolved;
  }

  // GSFINAL starts at --code-loc ( -b CSEG = 0x1234 )
  if (segments[CSEG].start & 1) {
    fprintf (stderr, "*** error: code doesn't start at "
	     "an even address: %04x\n", segments[CSEG].start);
    exit (1);
  }
  segments[GSFINAL].start=segments[CSEG].start;
  memset(gsfinalImage, 0xff, CODESIZE);

  // copy gsinit to gsfinal
  from = gsinitImage;
  to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
  memcpy(to, from, segments[GSINIT]._size);
  segments[GSINIT].start=segments[GSFINAL].start;
  segments[GSFINAL]._size += segments[GSINIT]._size;
  if (segments[GSFINAL]._size & 1) {
    segments[GSFINAL]._size++;
  }
    
  // append cseg to gsfinal
  from=csegImage;
  to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
  memcpy(to, from, segments[CSEG]._size);
  segments[CSEG].start=segments[GSFINAL].start+segments[GSFINAL]._size;
  segments[GSFINAL]._size += segments[CSEG]._size;
  if (segments[GSFINAL]._size & 1) {
    segments[GSFINAL]._size++;
  }

  // append xinit to gsfinal
  from=xinitImage;
  to = gsfinalImage + segments[GSFINAL].start + segments[GSFINAL]._size;
  memcpy(to, from, segments[XINIT]._size);
  segments[XINIT].start=segments[GSFINAL].start+segments[GSFINAL]._size;
  segments[GSFINAL]._size += segments[XINIT]._size;
  if (segments[GSFINAL]._size & 1) {
    segments[GSFINAL]._size++;
  }

  // XISEG is located after XSEG
  if (segments[XSEG].start & 1) {
    fprintf (stderr, "*** warning: xdata doesn't start at "
	     "an even address: %04x\n", segments[XSEG].start);
  }
  if (segments[XSEG]._size & 1) {
    segments[XSEG]._size++;
  }
  
  segments[XISEG].start=segments[XSEG].start + 
    segments[XSEG]._size;  

  // now relocate the defined symbols
  for (symbol=symbols; symbol; symbol=symbol->next) {
    if (!symbol->absolute) {
      symbol->address += symbol->segment->start;
    }
  }
  // and the references
  for (reference=references; reference; reference=reference->next) {
    symbol=findSymbolByName(reference->name);
    if (!reference->resolved && !symbol && reference->how!=ABS_PC) {
      // this reference isn't resolved after all
      fprintf (stderr, "*** %s:%d undefined symbol %s\n",
	       reference->module->name, reference->lineno,
	       reference->name);
      fatalErrors++;
    } else {
      reference->address += 
	reference->module->offset[reference->segment->id]+
	reference->segment->start;
      reference->pc += 
	reference->module->offset[reference->segment->id]+
	reference->segment->start;
      switch (reference->how) 
	{
	case REL_FF: {
	  int rel8 = symbol->address-(reference->pc & ~1);
	  if (rel8<-256 || rel8>256) {
	    fprintf (stderr,
		     "rel8 target for %s is out of range in module %s:%d\n",
		     reference->name, reference->module->name, 
		     reference->lineno);
	    fatalErrors++;
	  }
	  gsfinalImage[reference->address]=rel8/2;
	  break;
	}
	case REL_FFFF: {
	  int rel16 = symbol->address-(reference->pc & ~1);
	  if (rel16<-65536 || rel16>65534) {
	    fprintf (stderr, 
		     "rel16 target for %s is out of range in module %s:%d\n",
		     reference->name, reference->module->name,
		     reference->lineno);
	    fatalErrors++;
	  }
	  gsfinalImage[reference->address]=(rel16/2)>>8;
	  gsfinalImage[reference->address+1]=rel16/2;
	  break;
	}
	case DIR_70FF:
	  gsfinalImage[reference->address] = 
	    (gsfinalImage[reference->address]&~0x70) + 
	    ((symbol->address>>4)&0x70);
	  gsfinalImage[reference->address+1] = symbol->address;
	  break;
	case ABS_FFFF:
	  gsfinalImage[reference->address] = symbol->address>>8;
	  gsfinalImage[reference->address+1] = symbol->address;
	  break;
	case ABS_FF:
	  gsfinalImage[reference->address] = symbol->address;
	  break;
	case ABS_PC: 
	  {
	    unsigned int address=
	      (gsfinalImage[reference->address]<<8) +
	      gsfinalImage[reference->address+1];
	    address += reference->module->offset[reference->segment->id];
	    address += segments[reference->segment->id].start;
	    gsfinalImage[reference->address] = address>>8;
	    gsfinalImage[reference->address+1] = address;
	  };
	  break;
	default:
	  fprintf (stderr, "unsupported reference mode %d.\n",
		   reference->how);
	  fatalErrors++;
	}
    }
  }
  return 0;
}

void usage (char * progName, int errNo) {
  fprintf (stderr, "usage: %s lnkCmdFile\n", progName);
  if (errNo) {
    exit (errNo);
  }
}

int scanLibraries(int unresolved) {
  int resolved=0;
  int nlp, nlf;
  char libFiles[PATH_MAX];
  char libFile[PATH_MAX];
  char line[132];
  char symName[132];
  FILE *lf, *lfs;
  
  for (nlp=0; nlp<nlibPaths; nlp++) {
    for (nlf=0; nlf<nlibFiles; nlf++) {
      sprintf (libFiles, "%s/%s.lib", libraryPaths[nlp], libraryFiles[nlf]);
      if ((lfs=fopen(libFiles,"r"))==NULL) {
	continue;
      }
      while (fgets(line, 132, lfs)) {
	// remove trailing \n
	line[strlen(line)-1]='\0';
	sprintf (libFile, "%s/%s", libraryPaths[nlp], line);
	if ((lf=fopen(libFile,"r"))==NULL) {
	  continue;
	}
	while (fgets(line, 132, lf)) {
	  int dummy; // we need this to get the right count of the next sscanf
	  if (sscanf(line, "S %[^ ] Def%04x", symName, &dummy)==2) {
	    if (isUnresolved(symName, 1)) {
	      readModule(libFile,1);
	      if (resolved++ == unresolved) {
		// we are done
		return resolved;
	      }
	      // skip to next lib module
	      break;
	    }
	  }
	}
      }
    }
  }
  return resolved;
}

int main(int argc, char **argv) {
  FILE *linkCommandsFile;
  char linkCommandsPath[PATH_MAX];
  char linkCommand[PATH_MAX];
  struct MODULE *module;
  struct SYMBOL *symbol;
  int s;
  int unresolved;

  if (argc!=2) {
    usage(argv[0], 1);
  }

  // read in the commands
  sprintf (linkCommandsPath, "%s.lnk", argv[1]);
  if (!(linkCommandsFile=fopen(linkCommandsPath, "r"))) {
    perror(linkCommandsPath);
    exit(1);
  }
  while (fgets(linkCommand, PATH_MAX, linkCommandsFile)) {
    linkCommand[strlen(linkCommand)-1]='\0';

    // skip empty lines
    if (!*linkCommand) {
      continue;
    }

    //puts (linkCommand);
    if (*linkCommand=='-') {
      switch (linkCommand[1]) 
	{
	case 'm':
	  // probably -muxi, ignore for now
	  break;
	case 'e':
	  // -e, always in the last line, ignore for now
	  break;
	case 'b': 
	  {
	    // a segment start address like: "-b XSEG = 0x4000"
	    int s;
	    char *seg=strtok(&linkCommand[3], " \t");
	    for (s=0; s<MAX_SEGMENTS; s++) {
	      if (strcmp(segments[s].name, seg)==0) {
		strtok(NULL, " \t"); // skip the '='
		if (sscanf(strtok(NULL, " \t"), "%x", 
			   &segments[s].start)!=1) {
		  syntaxError(linkCommand);
		}
		break;
	      }
	    }
	    if (s==MAX_SEGMENTS) {
	      syntaxError(linkCommand);
	    }
	  }
	  break;
	case 'k':
	  // a lib path like: "-k /usr/local/share/sdcc/lib/xa51"; one/line
	  libraryPaths[nlibPaths++]=strdup(&linkCommand[3]);
	  break;
	case 'l':
	  // a lib file like: "-l libsdcc"; one/line
	  libraryFiles[nlibFiles++]=strdup(&linkCommand[3]);
	  break;
	default:
	  syntaxError(linkCommand);
	}
    } else {
      // not a switch, must be an inputfile; one/line
      readModule(linkCommand, 0);
      // the first one defines the output name
      if (!outFileName[0]) {
	strncpy(outFileName, linkCommand,
		strlen(linkCommand)-4);
	sprintf(mapFileName, "%s.map", outFileName);
	strcat(outFileName, ".hex");
	if ((mapOut=fopen(mapFileName, "w"))==NULL) {
	  perror(mapFileName);
	}
      }
    }
  }

  // add the segment symbols
  currentSegment=findSegmentByName("XINIT");
  addToDefs("s_XINIT", 0, 0);
  addToDefs("l_XINIT", segments[XINIT]._size, 1);
  currentSegment=findSegmentByName("XISEG");
  addToDefs("s_XISEG", 0, 0);
  addToDefs("l_XISEG", segments[XISEG]._size, 1);

  // mark the resolved references
  resolve();

  // now do something EXTREMELY SLOW AND INEFFICIENT :)
  while ((unresolved=relocate())) {
    if (!scanLibraries(unresolved)) {
      struct REFERENCE *reference;
      resolve();
      for (reference=references; reference; reference=reference->next) {
	if (!reference->resolved) {
	  fprintf (stderr, "*** unresolved symbol %s in %s:%d\n",
		   reference->name, reference->module->name,
		   reference->lineno);
	  fatalErrors++;
	}
      }
      break;
    }
  }

  if (unresolved==0) {
    writeModule(outFileName);
  }

  // the modules
  fprintf (mapOut, "Modules:\n");
  for (module=modules; module; module=module->next) {
    fprintf (mapOut, "\t%s\n", module->name);
    for (s=0; s<MAX_SEGMENTS; s++) {
      if (module->size[s]) {
	fprintf (mapOut, "\t\t%s:0x%04x-0x%04x\n", segments[s].name,
		 module->offset[s]+segments[s].start,
		 module->offset[s]+segments[s].start+module->size[s]);
      }
    }
  }

  // the segments
  fprintf (mapOut, "\nSegments:\n");
  for (s=1; s<MAX_SEGMENTS; s++) {
    if (segments[s]._size) {
      fprintf (mapOut, "\t%s start 0x%04x size 0x%04x %d symbols\n",
	       segments[s].name, segments[s].start,
	       segments[s]._size, 
	       segments[s].hasSymbols);
    }
  }

  // the symbols
  fprintf (mapOut, "\nSymbols:\n");
  for (symbol=symbols; symbol; symbol=symbol->next) {
    fprintf (mapOut, "%s\t%s %s0x%04x %s\n", symbol->name, 
	     symbol->segment->name,
	     symbol->absolute ? "= " : "",
	     symbol->address, symbol->module->name);
  }

  fclose(mapOut);
  return fatalErrors? 1 : 0;
}

