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

/*
 * Input/output
 */

#include "stream.h"

/*
 * Get byte from file and convert newline characters to zero.
 */
static char FN_Stream_ConvertNL() {
  char old;
  int    b;

  /* Exchange newline characters by zero */
  /* If reading LF of CRLF, read another byte */
  do {
    old = Context[nContext].OldFileByte;
    if(EndReason == RENDOFFILE) {
      Context[nContext].OldFileByte = 0;/* Fake end-of-statement */
    } else {
      /* Get byte from file */
      b = fgetc(Context[nContext].u.hFile);
      if(b == EOF) {
        /* Act upon end of file */
        EndReason = RENDOFFILE;
        Context[nContext].OldFileByte = 0;/* Fake end-of-statement */
      } else {
        Context[nContext].OldFileByte = (char) b;
      }
    }
  } while((old == 13) && (Context[nContext].OldFileByte == 10));/* CRLF test */
/* Check for LF and CR separately. Note that setting
 * "Context[nContext].OldFileByte" to zero would confuse the
 * "old/Context[nContext].OldFileByte" stuff above.*/
  if(Context[nContext].OldFileByte == 10) return(0);
  if(Context[nContext].OldFileByte == 13) return(0);
  return(Context[nContext].OldFileByte);
}

/*
 * Get byte (low level)
 */
static char FN_Stream_GetRawByte() {
  char old = Context[nContext].OldRawByte;

  if(old == 0) Context[nContext].nLines++;/* Increase line counter */
  switch(Context[nContext].hByteSource) {

    case BYTESOURCE_FILE:
    /* File handler */
    if(QuoteChar != NO_QUOTE_CHAR) {
      /* Inside quotes, don't process. Only complain about zero */
      Context[nContext].OldRawByte = FN_Stream_ConvertNL();
      if(Context[nContext].OldRawByte == 0) {
        FN_Message(pseOpenQuotes, EERROR);
        QuoteChar = NO_QUOTE_CHAR;
      }
    } else {
      /* Outside quotes, process */
      do {
        Context[nContext].OldRawByte = FN_Stream_ConvertNL();
        /* convert TAB to space, kill multiple spaces */
        if(Context[nContext].OldRawByte == 9)
           Context[nContext].OldRawByte = ' ';
      } while((Context[nContext].OldRawByte == ' ') &&
              ((old == ' ') || (old == 0) || (old == ',') || (old == ':')));
      /* After semicolon, skip remainder of line */
      if(Context[nContext].OldRawByte == ';')
        while((Context[nContext].OldRawByte = FN_Stream_ConvertNL()));
    }
    break;

    case BYTESOURCE_RAM:
    /* RAM handler */
    Context[nContext].OldRawByte = *(Context[nContext].u.pRAM++);
    if(Context[nContext].OldRawByte == SEPARATOR) {
      EndReason = RENDOFBLOCK;
      Context[nContext].OldRawByte = 0;/* Fake end-of-statement */
    }
  }
  /* Check quoting */
  if(QuoteChar != NO_QUOTE_CHAR) {
    if(Context[nContext].OldRawByte == QuoteChar) QuoteChar = NO_QUOTE_CHAR;
  } else {
    if(Context[nContext].OldRawByte == '"')  QuoteChar = '"';
    if(Context[nContext].OldRawByte == '\'') QuoteChar = '\'';
  }
  return(Context[nContext].OldRawByte);
}

/*
 * Get byte (high level)
 */
static char FN_GetByte() {
  GotByte = FN_Stream_GetRawByte();
  if(QuoteChar == NO_QUOTE_CHAR) {
    /* Check for braces */
    if(GotByte == '}') {
      EndReason = RRIGHTBRACE;
      GotByte = 0;
    } else {
      /* Check for colon */
      if(GotByte == ':') GotByte = 0;
    }
  }
#ifdef FDEBUG
  fputc(GotByte, filter);
#endif
  return(GotByte);
}

/*
 * This routine reads in characters and stores them in memory, until an
 * illegal character is read or too many bytes have been received.
 * The second case produces a "string too long" error.
 * The routine zero-terminates the string and returns its length. Zero lengths
 * will produce a "missing string" error.
 */
static int FN_Stream_ReadKeyword(char *p, int MaxLen, int fNext) {
  int c = 0;

  if(fNext) FN_GetByte();
  do {
    p[c] = GotByte;
    /* now: c = number of bytes present minus one */
    if(pFlagTable[(unsigned char) GotByte] & MBYTEILLEGAL) break;
    FN_GetByte();
  } while(c++ < MaxLen);
  if(c > MaxLen) {
    FN_Message(pseTooLong, EERROR);
    c--;
  }
  p[c] = 0;/* terminate by overwriting illegal character */
  if(c == 0) FN_Message(pseMissingString, EERROR);
  return(c);
}

/*
 * Try to read a filename. If the flag "fAllowLib" is TRUE, library access by
 * using <...> quoting is possible as well. The file name given in the
 * assembler source code is converted from UNIX style to system style.
 */
static int FN_Stream_ReadFilename(char *p, int MaxLen, int fAllowLib) {
  int c = 0,
      d = 0;

  SKIPSPACE;
  if(fAllowLib && (GotByte == '<')) {
    strcpy(p, psLibPath);
    c = strlen(p);
    QuoteChar = '>';
  }
  if((QuoteChar == '"') || (QuoteChar == '>')) {
    d = FN_Stream_FinishQuoted(p, MaxLen, c);
    PLATFORM_CONVERTPATH(p + c);/* Convert to system style */
  } else FN_Message(pseMissingString, EERROR);
  return(d);
}

/*
 * Reads string and stores it in memory until closing quote is reached. Does
 * not store quote. Zero-terminates string and returns its length.
 * If the string is too long or there are no closing quotes, an error will be
 * produced. "cAlready" is the number of chars already present. Storing will
 * take place after them, their number is added when comparing to MaxLen.
 */
static int FN_Stream_FinishQuoted(char *p, int MaxLen, int cAlready) {
  do {
    p[cAlready++] = FN_GetByte();
  } while((cAlready < MaxLen) && GotByte && (QuoteChar != NO_QUOTE_CHAR));
  if(cAlready >= MaxLen) {
    FN_Message(pseTooLong, EERROR);
    cAlready = MaxLen;/* shrink back to limit */
  }
  p[--cAlready] = 0;/* terminate, overwriting last character (closing quote) */
  FN_GetByte();/* proceed with next char */
  SKIPSPACE;
  return(cAlready);
}

/*
 * Try to read a comma, skipping spaces before and after. Return TRUE if comma
 * found, otherwise FALSE.
 */
static int FN_Stream_Comma() {
  SKIPSPACE;
  if(GotByte != ',') return(FALSE);
  NEXTANDSKIPSPACE;
  return(TRUE);
}

/*
 * Check whether the given code byte runs on the current CPU and output it.
 */
static void FN_Stream_PutCodeByte(char byte) {
  int offset;

  if(fPCdefined) {
    if(pFlagTable[(unsigned char) byte] & (1 << hCPU_Now)) {
      offset = PC_Mem + PC_inc;
      if(offset >= OUTBUFFERSIZE) FN_Message(pseTooMuch, ESERIOUS);
      OutputBuffer[offset] = byte;
    } else {
      FN_Message(pseWrongCPU, EERROR);
      FN_SkipRest();
    }
    PC_inc++;
  } else FN_Message(pseNoPC, ESERIOUS);
}

/*
 * Send byte to output file, automatically increasing the program counter
 */
static void FN_Stream_PutByte(Value byte) {
  int offset;

  if(fPCdefined) {
    if(byte < 256) {
      offset = PC_Mem + PC_inc;
      if(offset >= OUTBUFFERSIZE) FN_Message(pseTooMuch, ESERIOUS);
      OutputBuffer[offset] = byte;
    } else FN_Message(pseTooBig, EERROR);
    PC_inc++;
  } else FN_Message(pseNoPC, ESERIOUS);
}

/*
 * Try to open top level source file
 */
static int FN_Stream_OpenTop() {
  if(ffOpenFiles & FOPENED_TOP) {
    FN_Message(pseNo3rdFile, EERROR);
    return(FALSE);
  }
  hfTop = fopen(pnfTop, "rb");/* open file */
  if(hfTop) {
    ffOpenFiles |= FOPENED_TOP;
    return(TRUE);
  } else {
    return(FALSE);
  }
}

/*
 * Close top level source file, if open
 */
static void FN_Stream_CloseTop() {
  if(ffOpenFiles & FOPENED_TOP) {
    fclose(hfTop);
    ffOpenFiles &= ~FOPENED_TOP;
  }
}

/*
 * Try to open sub level file (source/binary)
 */
static int FN_Stream_OpenSub(char *pnfSub) {
  if(ffOpenFiles & FOPENED_SUB) {
    FN_Message(pseNo3rdFile, EERROR);
    return(FALSE);
  }
  hfSub = fopen(pnfSub, "rb");/* open file */
  if(hfSub) {
    ffOpenFiles |= FOPENED_SUB;
    return(TRUE);
  } else {
    FN_Message(pseCannotOpenSubFile, EERROR);
    return(FALSE);
  }
}

/*
 * Close sub level file, if open
 */
static void FN_Stream_CloseSub() {
  if(ffOpenFiles & FOPENED_SUB) {
    fclose(hfSub);
    ffOpenFiles &= ~FOPENED_SUB;
  }
}

/*
 * Dump global labels into dump file
 */
static void FN_Stream_Dump() {
  FILE     *hfDump;
  ListItem *p;
  Value     v;
  char     *p1;
  int       a,
            ff;

  hfDump = fopen(pnfDump, "wb");/* try to open dump file */
  if(hfDump) {
    PLATFORM_SETFILETYPE(pnfDump, FILETYPE_TEXT);
    for(a = 0; a < 256; a++) {
      fprintf(hfDump, ";- %x\n", a);
      p = (ListItem *) &pPointerTable[a];/* point to pointer */
      while(p->Next) {
        p = p->Next;
        if(p->Type == HTYPE_LABEL) {
          /* list item is label, so prepare for zone check */
          p1 = &p->String[0];
          if((*(p1++) == 0) && (*(p1++) == 0)) {
            /* label is global, so dump it */
            fprintf(hfDump, "%s", &p->String[2]);
            ff = p->Data.Bytes.FLAGS;
            v = (p->Data.Bytes.LOW)
              + (p->Data.Bytes.HIGH << 8)
              + (p->Data.Bytes.BANK << 16);
            switch(ff & MVALUE_SIZE) {

              case MVALUESIZE_UK:
              fprintf(hfDump, "  =");
              break;

              case MVALUESIZE_08:
              fprintf(hfDump, "+1=");
              break;

              case MVALUESIZE_16:
              fprintf(hfDump, "+2=");
              break;

              case MVALUESIZE_24:
              fprintf(hfDump, "+3=");
            }
            if(ff & MVALUE_DEFINED) fprintf(hfDump, "$%x", (unsigned int) v);
            else                    fprintf(hfDump, " ?");
            if(ff & MVALUE_UNSURE)  fprintf(hfDump, "; ?\n");
            else                    fprintf(hfDump, "\n");
          }
        }
      }
    }
    fclose(hfDump);/* close dump file */
  } else printf(pseCannotOpenDumpFile);/* FN_Message would be endless loop */
}

/*
 * Dump code from buffer into output file
 */
static void FN_Stream_Output(Value Start, Value End) {
  FILE  *hfOut;
  Value  a;

  if(ffProgram & FBEVERBOSE) printf(psvOutput);
  /* Try to open output file */
  hfOut = fopen(pnfOut, "wb");
  if(hfOut) {
    /* !!! always produces cbm files !!! */
    PLATFORM_SETFILETYPE(pnfOut, FILETYPE_CBM);
    fputc(Start & 255, hfOut);/* load address, low  byte */
    fputc(Start >>  8, hfOut);/* load address, high byte */
    for(a = Start; a < End; a++) {
      fputc(OutputBuffer[a], hfOut);
    }
    fclose(hfOut);/* close output file */
  } else FN_Message(pseCannotOpenOutFile, ESERIOUS);
}
