/* $VER: pasm main.c V1.3d (08.10.00)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-2000  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-2000 by Volker Barthelmann.
 * pasm may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v1.3d  (08.10.00) jbecher
 *        StormGCC special (define STORMSPECIAL):
 *        - New options -unitname, -errport, -debugfile
 *        - Writes StormGCC debug files
 * v0.7   (02.01.98) phx
 *        Allow more than two assembler passes, as required for
 *        optimizations.
 *        Option -O (output format) was renamed to -F. New option -O for
 *        enabling optimizations.
 *        Output format 3 is ADOS (like EHF, but doesn't use HUNK_PPC_CODE).
 * v0.6   (30.10.97) phx
 *        Options to disable warnings for optional, 64-bit and super-
 *        visor instructions: -mo, -m64, -ms.
 * v0.5   (12.10.97) phx
 *        Option -D defines symbols. If an '=' is missing, a default
 *        value of '1' will be assigned. Assignment of symbols, which
 *        are defined later in the source, is also allowed.
 * v0.4   (05.07.97) phx
 *        Program returns EXIT_FAILURE if an error occurs.
 *        -V prints only version and build string and no instructions.
 *        Base address for absolute code may be set with -B option.
 *        EHF support.
 *        New option -I to specify some include paths.
 *        Option -x automatically declares unknown symbols as
 *        externally defined.
 * v0.2   (25.03.97) phx
 *        Writes ELF object for 32-bit PowerPC big-endian. Either absolute
 *        or ELF output format may be selected. ELF is default for all
 *        currently supported platforms. PPCasm supports nine different
 *        relocation types (there are much more...).
 *        Compiles and works also under NetBSD/amiga (68k).
 *        Changed function declaration to 'new style' in all sources
 *        (to avoid problems with '...' for example).
 *        Supports multiple output formats. Currently Absolute and ELF.
 * v0.1   (11.03.97) phx
 *        First test version with all PowerPC instructions and most
 *        important directives. Only raw, absolute output.
 * v0.0   (14.02.97) phx
 *        File created. Project started.
 */


#define MAIN_C
#include "ppcasm.h"


struct GlobalVars gvars;


void cleanup(struct GlobalVars *);

static char *default_destname(struct GlobalVars *);
static void reset_sections(struct GlobalVars *);
static char *get_option_arg(int,char *[],int *);
static char *get_nextoption_arg(int,char *[], int *);
static void write_object(struct GlobalVars *);
#ifdef STORMSPECIAL
static void reworkstabs(struct GlobalVars *);
static void dumpstabs(struct GlobalVars *);
#endif



int main(int argc,char *argv[])
{
  struct GlobalVars *gv = &gvars;
  int i,j;
  char *buf;
  char *stdassgn = "1";  /* default assignment for user definitions */
  struct UserDefine *udn;

  /* initialize and set default values */
  memset(gv,0,sizeof(struct GlobalVars));
  gv->maxerrors = DEF_MAXERRORS;
  gv->output = OFMT_DEFAULT;  /* default output format */
  initlist(&gv->sourcelist);
  initlist(&gv->sectionlist);
  initlist(&gv->userdeflist);

  if (argc<2 || (argc==2 && *argv[1]=='?')) {
    show_usage();
    exit(EXIT_SUCCESS);
  }

  for (i=1; i<argc; i++) {
    if (*argv[i] == '-') {

#ifdef STORMSPECIAL
      if (stricmp(argv[i],"-unitname") == 0)
      {
        if (buf = get_nextoption_arg(argc,argv,&i))
          gv->storm_unitname = buf;
        else
          error(59,"-unitname");  /* option -unitname: argument expected */
      }
      else if (stricmp(argv[i],"-errport") == 0)
      {
        if (buf = get_nextoption_arg(argc,argv,&i))
          gv->storm_errport = buf;
        else
          error(59,"-errport");  /* option -errport: argument expected */
      }
      else if (stricmp(argv[i],"-debugfile") == 0)
      {
        if (buf = get_nextoption_arg(argc,argv,&i))
          gv->storm_debugfilename = buf;
        else
          error(59,"-debugfile");  /* option -debugfile: argument expected */
      }
      else
#endif
      {

        /* option detected */
        switch (argv[i][1]) {

          case 'V':  /* show version */
            show_version();
            exit(EXIT_SUCCESS);

          case 'o':  /* set output file */
            if (!(gv->dest_name = get_option_arg(argc,argv,&i)))
              error(3);  /* missing output file name */
            break;

          case 'w':  /* suppress warnings */
            gv->dontwarn = TRUE;
            break;

          case 'x':  /* auto extern */
            gv->autoextern = TRUE;
            break;

          case 'R':  /* don't predefine register symbols, etc. */
            gv->noregsymbols = TRUE;
            break;

          case 'X':  /* no extended mnemonics */
            gv->noextmnemo = TRUE;
            break;

          case 'F':  /* set output format (0=abs, 1=elf, 2=ehf, ... ) */
            if (buf = get_option_arg(argc,argv,&i)) {
              if ((j = atoi(buf)) > OFMT_LAST)
                error(49,j);  /* unknown output format */
              else
                gv->output = j;
            }
            else
              error(48,'F');  /* option -F: argument expected */
            break;

          case 'O':  /* set optimization level (0=none, ...) */
            if (buf = get_option_arg(argc,argv,&i))
              gv->opt = (uint32)atoi(buf);
            else
              error(48,'O');  /* option -O: argument expected */
            break;

          case 'I':  /* add include path */
            for (j=0; j<MAX_INCPATHS; j++) {
              if (gv->incpaths[j] == NULL) {
                if (!(gv->incpaths[j] = get_option_arg(argc,argv,&i)))
                  error(48,'I');  /* option -I: argument expected */
                break;
              }
            }
            if (j >= MAX_INCPATHS)  /* path was ignored */
              error(51,MAX_INCPATHS,get_option_arg(argc,argv,&i));
            break;

          case 'B':  /* base address for absolute code */
            if (buf = get_option_arg(argc,argv,&i)) {
              if (!(sscanf(buf,"%li",(long *)&gv->absbase)))
                error(54,'B');  /* option -B: integer expected */
            }
            else
              error(48,'B');  /* option -B: argument expected */
            break;

          case 'D':  /* define a symbol */
            if (buf = get_option_arg(argc,argv,&i)) {
              buf = getsymbol(gv,buf);
              if (*gv->strbuf) {
                buf = skipspaces(buf);
                if (*buf == '=')
                  buf = skipspaces(++buf);
                else
                  buf = stdassgn;
                udn = alloc(sizeof(struct UserDefine));
                udn->line = alloc(8 + strlen(gv->strbuf) + strlen(buf));
                sprintf(udn->line,".set %s,%s\n",gv->strbuf,buf);
                addtail(&gv->userdeflist,&udn->n);
              }
              else
                error(56,'D');  /* option -D: symbol name expected */
            }
            else
              error(48,'D');  /* option -D: argument expected */
            break;

          case 'm':  /* -m set assembler mode */
            if (buf = get_option_arg(argc,argv,&i)) {
              if (!strcmp(buf,"64")) {
                gv->sixtyfourmode = TRUE;
                break;
              }
              else if (*buf == 'o') {
                gv->optinstrmode = TRUE;
                break;
              }
              else if (*buf == 's') {
                gv->supermode = TRUE;
                break;
              }
            }
            error(57,buf);  /* Unknown assembler mode -mX */
            break;

          default:
            printf("Unknown option -%c ignored.\n",argv[i][1]);
            break;
        }
      }
    }
    else {
      /* source file name */
      if (gv->source_name)
        error(4);  /* multiple source file names detected */
      gv->source_name = argv[i];
    }
  }
  if (!gv->source_name)
    error(2);  /* missing source file name */

  if (!gv->dest_name)
    gv->dest_name = default_destname(gv);

  /* initialization */
  init_hashtables(gv);
#ifdef STORMSPECIAL
  /* like .file directive */
  if (gv->storm_unitname)
    gv->file = allocstring(gv->storm_unitname);
#endif

  /* assemble */
  exec_pass1(gv);

  for (;;) {
    gv->anotherpass = FALSE;
    exec_pass2(gv);
    if (!gv->anotherpass)
      break;
    /* another pass is required! */
    reset_sections(gv);
  }

  /* write output file */
  write_object(gv);

#ifdef STORMSPECIAL
  /* create debug files */
  if (gv->storm_debugfilename)
  {
    reworkstabs(gv);
/*    dumpstabs(gv);*/
    debug_output(gv->storm_debugfilename, gv->storm_firstssymbol);
  }
#endif

  /* exit */
  cleanup(gv);
  return 0;
}


static void reset_sections(struct GlobalVars *gv)
/* free section contents, all xrefs and relocations */
{
  struct Section *nexts,*sec=(struct Section *)gv->sectionlist.first;
  struct node *n;

  while (nexts = (struct Section *)sec->n.next) {
    while (n = remhead(&sec->reloclist))
      free(n);
    while (n = remhead(&sec->xreflist))
      free(n);
    if (!(sec->flags & SF_UNINITIALIZED) && sec->contents) {
      free(sec->contents);
      sec->data = sec->contents = NULL;
    }
    sec = nexts;
  }
}


static char *default_destname(struct GlobalVars *gv)
/* make default output file name from source file name */
{
  char dname[STRBUFSIZE];
  int i;

  strncpy(dname,gv->source_name,STRBUFSIZE-1);
  i = strlen(dname);
  while (i--) {
    if (dname[i] == '.') {  /* replace old extension by ".o" */
      dname[i] = 0;
      break;
    }
  }
  strcat(dname,".o");
  return (allocstring(dname));
}


static char *get_option_arg(int argc,char *argv[],int *i)
/* get pointer to the string, which either directly follows the option
   character or is stored in the next argument */
{
  if (!argv[*i][2])
    if (++*i<argc)
      return (argv[*i]);
    else
      return NULL;
  else
    return (&argv[*i][2]);
}


static char *get_nextoption_arg(int argc,char *argv[], int *i)
/* get pointer to the string, which is stored in the next argument
   or not given if next argument starts with - */
{
  if (++*i>=argc)
    return NULL;
  if (argv[*i][0] == '-')
    return NULL;
  return argv[*i];
}


static void write_object(struct GlobalVars *gv)
/* creates the object file */
{
  switch(gv->output) {
    case OFMT_ABSOLUTE:
      output_absolute(gv);
      break;
    case OFMT_ELF:
      output_elf32msb(gv);
      break;
    case OFMT_EHF:
    case OFMT_ADOS:
      output_ehf(gv);
      break;
  }
}


#ifdef STORMSPECIAL
static void reworkstabs(struct GlobalVars *gv)
{
  symbolS *sym = gv->storm_firstssymbol;

  while (sym)
  {
    if (sym->section)
      sym->hunknumber = sym->section->index;
    sym = sym->next;
  }
}

static void dumpstabs(struct GlobalVars *gv)
{
  symbolS *sym = gv->storm_firstssymbol;

  while (sym)
  {
    if (sym->name)
      printf("\"%s\" ",sym->name);
    printf("value = %d, desc = %d, type = %d, hunk = %d\n",(int) sym->value,
           (int) sym->desc,(int) sym->type,sym->hunknumber);
    sym = sym->next;
  }
}
#endif


void cleanup(struct GlobalVars *gv)
{
  exit(gv->returncode);
}
