/*
 * ACME - a crossassembler for producing 6502/65c02/65816 code.
 * Copyright (C) 1998 Marco Baye
 * Have a look at "acme.c" for further info
 */

/*
 * Buffers and tables
 */

#include "global.h"

static char pnfOut [LNFMAX + 1];/* Filename of output file */
static char pnfDump[LNFMAX + 1];/* Filename of label dump */

/*
 * Main program
 */

int main(int argc, char *argv[]) {

  /* Pre-initialise, because may be needed on error */
  nContext = 0;
  Context[0].nLines       = 0;
  Context[0].pSourceFile  = pnfTop;
  Context[0].pSourceTitle = psUntitled;
  Context[0].pSourceType  = psZone;

  FN_ReadParameters(argc, argv);/* get command line arguments */
  FN_Platform_FindLib();
  FN_DataTables_Init();/* link predefined list items to lists */

  /* First pass */
  if(ffProgram & FBEVERBOSE) printf(psvFirst);

  FN_Pass(FPASSDOONCE);

  if(nNeedvalue) {
    /* If there were undefined values, another pass will have to be done.*/
    do {
      nNeedvalue_Last = nNeedvalue;/* Buffer previous error counter */

      /* Further passes */
      if(ffProgram & FBEVERBOSE) printf(psvFurther);
      FN_Pass(0);
      /* keep doing passes as long as there are no real errors and the */
      /* number of undefined values is decreasing */
    } while(nNeedvalue && (nNeedvalue < nNeedvalue_Last));
  }

  if(nNeedvalue) {
    /* Extra pass for finding "NeedValue" type errors */
    if(ffProgram & FBEVERBOSE) printf(psvFind);
    FN_Pass(FPASSDOERROR);
  } else {
    /* If wanted, save assembled code */
    if(ffProgram & FOUTFILECHOSEN) FN_Stream_Output(PC_Lowest, PC_Highest);
  }

#ifdef FDEBUG
  system("OS_File, 10, RAM:$.H, &ffd,, pHashTestTable, pHashTestTable+256");
#endif

  FN_CleanExit(EXIT_SUCCESS);/* that's all folks ! */
  return(0);
}

/*
 * Main routines
 */

/*
 * The core of every pass
 */
static void FN_Pass(int Flags) {
  ffPass     = Flags;/* Flags im Pass */
  nNeedvalue = 0;/* no "NeedValue" errors yet */
  nErrors    = 0;/* no errors yet */
  fPCdefined = FALSE;/* Flag for "PC defined" */
  PC_inc     = 0;/* Increase PCs by this amount at end of line */
  hCodeTable_Now = HCODETABLE_RAW;/* Handle of current code table */
  /* Open toplevel file */
  if(!FN_Stream_OpenTop()) {
    printf(pseCannotOpenTopFile);
    FN_CleanExit(EXIT_FAILURE);
  }
  /* Initialize context */
  nContext = 0;
  Context[0].nLines       = 0;
  Context[0].nZone_Now    = NZONE_START;/* current zone is default zone */
  Context[0].hByteSource  = BYTESOURCE_FILE;
  Context[0].u.hFile      = hfTop;
  Context[0].pSourceFile  = pnfTop;
  Context[0].pSourceTitle = psUntitled;
  Context[0].pSourceType  = psZone;
  Context[0].OldFileByte  = 0;
  Context[0].OldRawByte   = 0;

  EndReason = RNONE;

  nZone_Max = NZONE_START;/* highest zone number used yet */
  GotByte = 0;
  hCPU_Now = HCPU_6502;/* CPU = 6502 as default */
  fCPU_LongA  = FALSE;/* short accu and registers */
  fCPU_LongR  = FALSE;
  fCPU_LongAb = FALSE;
  fCPU_LongRb = FALSE;

  /* do the work */
  FN_ParseBlock();
  if(EndReason != RENDOFFILE) FN_Message(pseeEndOfFile, EERROR);
  EndReason = RNONE;/* Clear global variable */

  /* Reached end of source, so end of pass... */
  FN_Stream_CloseTop();
  if(nErrors) FN_CleanExit(EXIT_FAILURE);
}

/*
 * Parse block, beginning with next byte. Has to be re-entrant.
 */
static void FN_ParseBlock() {
  int fPC2Label,
      len;

  do {
    /* As long as there are statements */
    fPC2Label = 0;/* no "label = pc" definition yet */
    FN_GetByte();/* read start of statement */
    do {
/*    Parse until end of statement is reached. Only loops if statement contains
      "label = pc" definition and something else.*/
      switch(GotByte) {

        case 0:/* Ignore now, act later (stops zero from being "default") */
        break;

        case '+':
        FN_Flow_MacroCall();
        break;

        case '!':
        FN_PseudoOpcode();
        break;

        case '*':
        FN_SetPC();
        break;

        case '.':
        if(fPC2Label) {
          FN_Message(pseSyntax, EERROR);
          FN_SkipRest();
        } else {
          FN_LocalLabel();
          fPC2Label = -1;
        }
        break;

        default:
        if(pFlagTable[(unsigned char) GotByte] & MBYTEILLEGAL) {
          FN_Message(pseSyntax, EERROR);
          FN_SkipRest();
        } else {
          len = FN_Stream_ReadKeyword(&StringItem.String[2], LSMAX - 2, FALSE);
          /* (now: GotByte = illegal char) */
          /* It is only a label if it isn't a mnemonic */
          if(FN_Mnemo_IsInvalid(len)) {
            if(fPC2Label) {
              FN_Message(pseSyntax, EERROR);
              FN_SkipRest();
            } else {
              FN_SetLabel(NZONE_GLOBAL, len);
              fPC2Label = -1;
            }
          }
        }
      }
    } while(GotByte);

#ifdef FDEBUG
    printf("%d \n", Context[nContext].nLines);
#endif

    PC_CPU += PC_inc;
    PC_Mem += PC_inc;
    if(PC_Mem > PC_Highest) PC_Highest = PC_Mem;
    PC_inc = 0;
  } while(EndReason == RNONE);
}

/*
 * Skip remainder of statement, for example on error
 */
static void FN_SkipRest() {
  while(GotByte) FN_GetByte();/* Read characters until zero */
}

/*
 * Ensure that the remainder of the current statement is empty, for example
 * after mnemonics using implicit addressing.
 */
static void FN_EnsureEOL() {/* GotByte = first char to test */
  SKIPSPACE;
  if(GotByte) {
    FN_Message(pseSyntax, EERROR);
    FN_SkipRest();
  }
}

/*
 * Parse pseudo opcodes. Has to be re-entrant.
 */
static void FN_PseudoOpcode() {/* (GotByte = "!") */
  ListItem  *p;
  void     (*fn)(void);
  int        len;

#ifdef FDEBUG
  printf(" PO ");
#endif
  len = FN_Stream_ReadKeyword(&StringItem.String[0], LSMAX, TRUE);
  if(len) {
    /* Search for list item, length = type + string */
    FN_String_ToLower(&StringItem, len);
    p = FN_Struct_Search(&StringItem, HTYPE_PSEUDO, len + 1, 0);
    if(p) {
      fn = (void (*)()) p->Data.Body;
      if(fn != NULL) (fn)();
      else           FN_Message(pseUkPO, EERROR);
    } else FN_Message(pseUkPO, EERROR);
  }
}

/*
 * Parse (re-)definitions of program counter
 */
static void FN_SetPC() {/* GotByte = "*" */
  Value v;

#ifdef FDEBUG
  printf(" *= ");
#endif
  NEXTANDSKIPSPACE;/* proceed with next char */
  if(GotByte == '=') {
    FN_GetByte();/* proceed with next char */
    v = FN_ALU_GetValue_Strict();
    PC_CPU = v;
    PC_Mem = v;
    if(fPCdefined) {
      if(v < PC_Lowest) PC_Lowest = v;
    } else {
      PC_Lowest = v;
      PC_Highest = v;
    }
    fPCdefined = TRUE;
  } else {
    FN_Message(pseSyntax, EERROR);
    FN_SkipRest();
  }
  FN_EnsureEOL();
}

/*
 * Parse definitions of local labels
 */
static void FN_LocalLabel() {/* GotByte = "." */
  int len;

#ifdef FDEBUG
  printf(" . ");
#endif
  len = FN_Stream_ReadKeyword(&StringItem.String[2], LSMAX - 2, TRUE);
  /* (now: GotByte = illegal char) */
  if(len) FN_SetLabel(Context[nContext].nZone_Now, len);
}

/*
 * Defines a label (may be global or local). The stringbuffer holds the label's
 * name ("len" giving the length, "Zone" its zone).
 */
static void FN_SetLabel(Sixteen Zone, int len) {/* GotByte = illegal */
  ListItem *p;
  int       pf = FN_Mnemo_GetPostfix();/* read length info (skips spaces) */

  p = FN_Struct_GetPreparedLabel(Zone, len, pf);
  if(GotByte == '=') {
    /* label = parsed value */
    FN_GetByte();/* proceed with next char */
    FN_SetLabelValue(p, FN_ALU_GetValue_Medium(), FALSE);
    p->Data.Bytes.FLAGS |= (ffValue & (MVALUE_UNSURE | MVALUE_DEFINED));
    FN_EnsureEOL();
  } else {
    /* label = PC */
    if(fPCdefined) {
      FN_SetLabelValue(p, PC_CPU, FALSE);
      p->Data.Bytes.FLAGS |= MVALUE_DEFINED;
    } else FN_Message(pseNoPC, EERROR);
  }
}

/*
 * Assign value to label list item. The routine acts upon the list item's flag
 * bits and produces errors if needed. "p" points to the list item, "v" is the
 * value to assign.
 */
static void FN_SetLabelValue(ListItem *p, Value v, int fRedefine) {
  Value vOld;
  char  size,
        flags = p->Data.Bytes.FLAGS;

  if((flags & MVALUE_DEFINED) && (fRedefine==FALSE)) {
    /* if label already defined, compare new and old values */
    vOld = (p->Data.Bytes.LOW)
         + (p->Data.Bytes.HIGH <<  8)
         + (p->Data.Bytes.BANK << 16);
    if(vOld != v) FN_Message(pseLabelTwice, EERROR);
#ifdef FDEBUG
    printf("Line: %d \n Old: %d \n New: %d \n", Context[nContext].nLines, vOld, v);
#endif
  } else {
    /* label not yet defined OR allowed redefinition */
    size = flags & MVALUE_SIZE;
    /* compare size flags and value size */
    if(size == 0) size = 3;/* Kluge */
    p->Data.Bytes.LOW  = v & 255;
    if(size > 1) {
      p->Data.Bytes.HIGH = (v >> 8) & 255;
      if(size > 2) p->Data.Bytes.BANK = (v >> 16) & 255;
      else if(v > 65535) FN_Message(pseTooBig, EERROR);
    } else if(v >   255) FN_Message(pseTooBig, EERROR);
  }
}


/*
 * Error handling
 */

/*
 * This routine handles problems that may be solved by performing further
 * passes: "NeedValue" type errors.
 * If the current pass has the corresponding flagbit set, this routine will not
 * only count, but also show these errors to the user.
 */
static void FN_NeedValue() {
  nNeedvalue++;
  if(ffPass & FPASSDOERROR) FN_Message(pseNeedValue, EERROR);
}

/*
 * Output of warning or error messages. "psm" points to the string, , "type" is
 * the message's type:
 *
 * EWARNING is not a "real" error but just a warning; the produced code looks
 *   as expected. But there has been a situation that should be reported to the
 *   user, for example there may has been assembled a 16-bit parameter with an
 *   8-bit value.
 *
 * EERROR is a "real" error; something went wrong in a way that implies that
 *   the output almost for sure won't look like expected, for example when
 *   there was a syntax error. The assembler will try to go on with the
 *   assembly though, as the user surely would like to know about more than
 *   one of his typos at a time.
 *
 * ESERIOUS is a serious error, an error that makes it impossible to go on with
 *   the assembly. Example: "!fill" without a parameter - the program counter
 *   cannot be adjusted correctly in this case, so proceeding would be of no
 *   use at all.
 *
 * The routine will show the given error string, as well as the current
 * context: Filename, line number, source type and source title.
 */
static void FN_Message(char *psm, int type) {
  /* psm points to desired message */
  PLATFORM_FN_MESSAGE(psm, type);
  printf("%s - File %s", eType[type], Context[nContext].pSourceFile);
  printf(", Line %d",                 Context[nContext].nLines);
  printf(" (%s ",                     Context[nContext].pSourceType);
  printf("%s): %s\n",                 Context[nContext].pSourceTitle, psm);
  if(type == ESERIOUS) FN_CleanExit(EXIT_FAILURE);
  if(type == EERROR) nErrors++;
  if(nErrors >= MAXERRORS) FN_CleanExit(EXIT_FAILURE);
}

/*
 * Get command line arguments
 */

/*
 * This routine sets "pnfTop" to point to the given filename string and acts
 * upon the supported option character(s).
 */
static void FN_ReadParameters(int argc, char *argv[]) {
  char c = 0;
  int  i = 1,
       j;

  while((i < argc) && (argv[i][0] == PLATFORM_OPTIONPREFIX)) {
    j = 1;
    while((c = argv[i][j++]) != 0) {
      switch(c) {

        PLATFORM_SWITCHES /* platform specific switches are inserted here */

        case 'h':
        FN_String_ShowUsage();/* show syntax message */
        break;

        case 'v':
        ffProgram |= FBEVERBOSE;/* verbose mode */
        FN_String_ShowCopyright();
        break;

        default:
        printf(pseUkSwitch, c);
      }
    }
    i++;
  }
  if(i == argc) {
    if(i - 1) {
      printf(pseNoTop);
      FN_CleanExit(EXIT_FAILURE);
    } else {
      FN_String_ShowUsage();
      FN_CleanExit(EXIT_SUCCESS);
    }
  } else {
    pnfTop = argv[i];
  }
}

/*
 * Tidy up before exiting
 */
static void FN_CleanExit(int exitstatus) {
  if(ffProgram & FDOLABELDUMP) FN_Stream_Dump();
  PLATFORM_EXIT;
  FN_Stream_CloseTop();/* close files */
  FN_Stream_CloseSub();
#ifdef FDEBUG
  fclose(filter);
  fclose(filterRaw);
#endif
  /* Free memory !!! */
  exit(exitstatus);
}

/*
 * Conversion routines...
 */

/*
 * Convert character using given conversion table
 */
static char FN_ConvertChar(char byte, int hCT) {
  switch(hCT) {

    case HCODETABLE_RAW:
    return(byte);

    case HCODETABLE_PET:
    if((byte > 64) && (byte <  91)) return((char) (byte + 128));
    if((byte > 96) && (byte < 123)) return((char) (byte -  32));
    return(byte);

    case HCODETABLE_SCR:
    if((byte > 96) && (byte <123))
      return((char) (byte - 96));/* shift uppercase down */
    if((byte > 90) && (byte < 96))
      return((char) (byte - 64));/* shift [\]^_ down */
    if(byte == 96) return(64);                       /* shift ` down */
    if(byte == 64) return(0);                        /* shift @ down */
    return(byte);

    default:
    FN_Message(pseNotYet, EERROR);
    return(byte);
  }
}

/*
 * Convert string using given conversion table
 */
static void FN_ConvertString(char *p, int len, int hCT) {
  int a;

  for(a = 0; a < len; a++) {
    p[a] = FN_ConvertChar(p[a], hCT);
  }
}
