/* $VER: pasm main.c V0.7 (02.01.98)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-98  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-98 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.
 *
 *
 * 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 void write_object(struct GlobalVars *);



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] == '-') {
      /* 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);

  /* 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);
  cleanup(gv);
}


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 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;
  }
}


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