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

/*
 * Flow control stuff (macro calls, loops, etc.)
 */

#include "flowpo.h"

/*
 * Offset assembly
 */
static void FN_FlowPO_offset() {

  SKIPSPACE;
  if(GotByte) {
    PC_CPU = FN_ALU_GetValue_Strict();
  } else {
    PC_CPU = PC_Mem;
  }
  FN_EnsureEOL();
}

/*
 * Switch to new zone ("!zone" or "!zn")
 */
static void FN_FlowPO_zone() {
  int len;

  Context[nContext].nZone_Now    = ++nZone_Max;
  Context[nContext].pSourceTitle = psUntitled;
  SKIPSPACE;
  if(GotByte) {
    len = FN_Stream_ReadKeyword(&pTitle[nContext][0], LSMAX, FALSE);
    if(len) Context[nContext].pSourceTitle = &pTitle[nContext][0];
    FN_EnsureEOL();
  }
}

/*
 * Start subzone ("!subzone" or "!sz"). Has to be re-entrant.
 */
static void FN_FlowPO_subzone() {
  int len,
      Reason;

  /* Initialize new context */
  FN_Flow_NewContext();
  Context[nContext].nLines       = Context[nContext-1].nLines;
  Context[nContext].nZone_Now    = ++nZone_Max;
  Context[nContext].hByteSource  = Context[nContext-1].hByteSource;
  Context[nContext].u.hFile      = Context[nContext-1].u.hFile;
  Context[nContext].pSourceFile  = Context[nContext-1].pSourceFile;
  Context[nContext].pSourceTitle = psUntitled;
  Context[nContext].pSourceType  = Context[nContext-1].pSourceType;
  Context[nContext].OldFileByte  = 0;
  Context[nContext].OldRawByte   = ' ';/* Don't increase line number */
  SKIPSPACE;
  /* Check for zone title */
  if((pFlagTable[(unsigned char) GotByte] & MBYTEILLEGAL) == 0) {
    len = FN_Stream_ReadKeyword(&pTitle[nContext][0], LSMAX, FALSE);
    if(len) Context[nContext].pSourceTitle = &pTitle[nContext][0];
    SKIPSPACE;
  }
  if(GotByte == '{' ) {
    FN_ParseBlock();
    Reason = EndReason;
    EndReason = RNONE;/* Clear global variable */
    if(Reason != RRIGHTBRACE) FN_Message(pseeRightBrace, EERROR);
    FN_GetByte();
    FN_EnsureEOL();
  } else FN_Message(pseeLeftBrace, ESERIOUS);
  /* Adjust old context's data */
  Context[nContext-1].nLines = Context[nContext].nLines;
  Context[nContext-1].OldFileByte = 0;
  Context[nContext-1].OldRawByte  = 0;/* Increase line number */
  nContext--;/* Old context */
}

/*
 * End of source file ("!end")
 */
static void FN_FlowPO_end() {
  EndReason = RENDOFFILE;
  FN_EnsureEOL();
}

/*
 * Include source file ("!source" or "!src"). Has to be re-entrant.
 */
static void FN_FlowPO_source() {/* GotByte = illegal character */
  char pnfSub[LNPMAX + 1];/* file name */
  int  len = FN_Stream_ReadFilename(pnfSub, LNPMAX, TRUE);

  if(len) {
    if(FN_Stream_OpenSub(pnfSub)) {
      /* Initialize new context */
      FN_Flow_NewContext();
      Context[nContext].nLines       = 0;
      Context[nContext].nZone_Now    = Context[nContext-1].nZone_Now;
      Context[nContext].hByteSource  = BYTESOURCE_FILE;
      Context[nContext].u.hFile      = hfSub;
      Context[nContext].pSourceFile  = pnfSub;
      Context[nContext].pSourceTitle = Context[nContext-1].pSourceTitle;
      Context[nContext].pSourceType  = Context[nContext-1].pSourceType;
      Context[nContext].OldFileByte  = 0;
      Context[nContext].OldRawByte   = 0;
      /* Parse block and check end reason */
      FN_ParseBlock();
      if(EndReason != RENDOFFILE) FN_Message(pseeEndOfFile, EERROR);
      EndReason = RNONE;/* Clear global variable */
      FN_Stream_CloseSub();
      nContext--;/* Old context */
    }
    FN_EnsureEOL();
  } else {
    FN_SkipRest();
  }
}

/*
 * Conditional assembly ("!if"). Has to be re-entrant.
 */
static void FN_FlowPO_if() {/* GotByte = illegal character */
  ListItem *p;
  int       len,
            Reason;
  Value     v = FN_ALU_GetValue_Strict();

  if(GotByte == '{') {
    if(v) {
      FN_ParseBlock();
    } else {
      FN_Flow_StoreBlock(NULL);
    }
    Reason = EndReason;
    EndReason = RNONE;/* Clear global variable */
    if(Reason == RRIGHTBRACE) {
      /* check for "else" */
      NEXTANDSKIPSPACE;
      if(GotByte) {
        len = FN_Stream_ReadKeyword(&StringItem.String[0], LSMAX, FALSE);
        if(len) {
          /* Search for list item. Don't create */
          FN_String_ToLower(&StringItem, len);
          p = FN_Struct_Search(&StringItem, HTYPE_ELSE, len + 1, 0);
          if(p) {
            if(p->Data.Bytes.VALUE == ID_ELSE) {
              SKIPSPACE;
              if(GotByte == '{') {
                if(v) {
                  FN_Flow_StoreBlock(NULL);
                } else {
                  FN_ParseBlock();
                }
                Reason = EndReason;
                EndReason = RNONE;/* Clear global variable */
                if(Reason != RRIGHTBRACE) FN_Message(pseeRightBrace, EERROR);
                FN_GetByte();
              } else FN_Message(pseeLeftBrace, ESERIOUS);
            } else FN_Message(pseSyntax, EERROR);
          } else FN_Message(pseSyntax, EERROR);
        }
        FN_EnsureEOL();
      }
    } else FN_Message(pseeRightBrace, EERROR);
  } else FN_Message(pseeLeftBrace, ESERIOUS);
}

/*
 * Looping assembly ("!do"). Has to be re-entrant.
 */
static void FN_FlowPO_do() {/* GotByte = illegal character */
  Value  v;
  int    line,
         fGoOn = TRUE;
  char  *pb,
        *pbOwn,
         id;

  line = Context[nContext].nLines;/* Remember line number */
  pb = FN_Flow_StoreCondition(pFlowBuffer);/* Read head to buffer */
  if(GotByte == '{') {
    pb = FN_Flow_StoreBlock(pb);/* Read block to buffer */
    EndReason = RNONE;/* Clear global variable */
    FN_GetByte();/* proceed with next char */
    pb = FN_Flow_StoreCondition(pb);/* Read tail to buffer */
    if(GotByte == 0) {
      pbOwn = malloc(pb - pFlowBuffer);/* Get memory for this incarnation */
      if(pbOwn) {
        memcpy(pbOwn, pFlowBuffer, pb - pFlowBuffer);
        /* Initialize new context */
        FN_Flow_NewContext();
        Context[nContext].nZone_Now    = Context[nContext-1].nZone_Now;
        Context[nContext].hByteSource  = BYTESOURCE_RAM;
        Context[nContext].pSourceFile  = Context[nContext-1].pSourceFile;
        Context[nContext].pSourceTitle = Context[nContext-1].pSourceTitle;
        Context[nContext].pSourceType  = Context[nContext-1].pSourceType;
        Context[nContext].OldFileByte  = 0;
        Context[nContext].OldRawByte   = ' ';/* Don't increase line number */
        do {
          /* Start of loop */
          Context[nContext].u.pRAM = pbOwn;/* Begin at beginning */
          Context[nContext].nLines = line;/* Use remembered line number */
          /* Check head condition */
          id = FN_GetByte();/* get condition type (until/while) */
          FN_GetByte();/* proceed with next char */
          v = FN_ALU_GetValue_Strict();
          if(EndReason == RENDOFBLOCK) {
            EndReason = RNONE;/* Clear global variable */
            if(id == ID_UNTIL) v = !v;
            if(v) {
              FN_ParseBlock();
              if(EndReason == RENDOFBLOCK) {
                EndReason = RNONE;/* Clear global variable */
                /* Check tail condition */
                id = FN_GetByte();/* get condition type (until/while) */
                FN_GetByte();/* proceed with next char */
                v = FN_ALU_GetValue_Strict();
                if(EndReason == RENDOFBLOCK) {
                  EndReason = RNONE;/* Clear global variable */
                  if(id == ID_UNTIL) v = !v;
                  if(v == 0) fGoOn = FALSE;
                } else FN_Message(pseSyntax, ESERIOUS);
              } else FN_Message(pseeRightBrace, ESERIOUS);
            } else fGoOn = FALSE;
          } else FN_Message(pseSyntax, ESERIOUS);
        } while(fGoOn);
        free(pbOwn);/* Free memory */
        nContext--;/* Old context */
        FN_EnsureEOL();
      } else FN_Message(pseNoMemLeft, ESERIOUS);
    } else FN_Message(pseSyntax, ESERIOUS);
  } else FN_Message(pseeLeftBrace, ESERIOUS);
}

/*
 * Try to read a condition and store it at the given location. Add separator.
 */
static char *FN_Flow_StoreCondition(char *pb) {
  ListItem *p;
  int       len;

  SKIPSPACE;
  if((GotByte == '{') || (GotByte == 0)) {/* Accept both head *and* tail */
    /* Write pseudo condition (always TRUE) into buffer, so we don't have */
    /* to worry about nonexistant conditions */
    *(pb++) = ID_WHILE;
    *(pb++) = '1';
  } else {
    /* Write given condition into buffer */
    len = FN_Stream_ReadKeyword(&StringItem.String[0], LSMAX, FALSE);
    if(len) {
      /* Search for list item. Don't create */
      FN_String_ToLower(&StringItem, len);
      p = FN_Struct_Search(&StringItem, HTYPE_DOLOOP, len + 1, 0);
      if(p) {
        *(pb++) = p->Data.Bytes.VALUE;/* store condition type handle */
        while((GotByte != '{') && GotByte) {
          if(pb == &pFlowBuffer[MAXBLOCKSIZE])
            FN_Message(pseNoMemLeft, ESERIOUS);
          *(pb++) = GotByte;
          FN_GetByte();
        }
      } else FN_Message(pseSyntax, EERROR);
    }
  }
  *(pb++) = SEPARATOR;/* Terminate / separate */
  return(pb);
}

/*
 * Macro definition ("!macro").
 */
static void FN_FlowPO_macro() {
  ListItem    *p;
  char        *pb,
              *pSource,
               byte;
  MacroStruct *pMacro,
              *pMacroGlobal;
  Sixteen      Zone         = NZONE_GLOBAL;
  int          len,
               line         = Context[nContext].nLines;/* Remember line nr */

  pMacroGlobal = &MacroBuffer;/* Macro/flow buffer */
  pb = &pMacroGlobal->Chars[0];/* pb points to start of sequential data */
  SKIPSPACE;
  if(ffPass & FPASSDOONCE) {
    /* Parse macro definition */
    if(GotByte == '.') {
      Zone = Context[nContext].nZone_Now;
      FN_GetByte();
    }
    len = FN_Stream_ReadKeyword(&StringItem.String[2], LSMAX - 2, FALSE);
    /* (now: GotByte = illegal char) */
    if(len) {
      p = FN_Struct_GetZoned(Zone, len, HTYPE_MACRO, TRUE);
      if(fMadeItem) {
        /* Copy file name to buffer (includes terminator) */
        pSource = Context[nContext].pSourceFile;
        do {
          byte = *(pSource++);
          *(pb++) = byte;
          if(pb == &pFlowBuffer[MAXBLOCKSIZE])
            FN_Message(pseNoMemLeft, ESERIOUS);
        } while(byte);
        /* Copy parameters to buffer */
        SKIPSPACE;
        while((GotByte != '{') && GotByte) {
          *(pb++) = GotByte;
          if(pb == &pFlowBuffer[MAXBLOCKSIZE])
            FN_Message(pseNoMemLeft, ESERIOUS);
          FN_GetByte();
        }
        if(GotByte) {
          *(pb++) = SEPARATOR;/* Terminate / separate */
          Context[nContext].OldRawByte = ' ';/* Kluge to skip spaces */
          pb = FN_Flow_StoreBlock(pb);/* Store macro body */
          if(EndReason == RRIGHTBRACE) {
            EndReason = RNONE;/* Clear global variable */
            FN_GetByte();/* Proceed with next character */
            /* Get own memory for macro */
            pMacro = malloc(pb - (char *) (&MacroBuffer));
            if(pMacro) {
              /* Set up macro structure in new memory block */
              memcpy(&pMacro->Chars[0], pFlowBuffer, pb - pFlowBuffer);
              pMacro->nLinesDef = line;
              /* Remember pointer to new memory block in list item */
              p->Data.Body = pMacro;
            } else FN_Message(pseNoMemLeft, ESERIOUS);
          } else FN_Message(pseeRightBrace, ESERIOUS);
        } else FN_Message(pseeLeftBrace, ESERIOUS);
      } else FN_Message(pseMacroTwice, ESERIOUS);
    }
  } else {
    /* Skip macro definition */
    while((GotByte != '{') && GotByte) FN_GetByte();
    if(GotByte) {
      FN_Flow_StoreBlock(NULL);
      EndReason = RNONE;/* Clear global variable */
      FN_GetByte();/* Proceed with next character */
    } else FN_Message(pseeLeftBrace, EERROR);
  }
  FN_EnsureEOL();
}

/*
 * Macro call ("+<macroname>"). Has to be re-entrant.
 */
static void FN_Flow_MacroCall() {/* GotByte = "+" */
  ListItem    *p;
  Value        v;
  int          a,
               len,
               nParaC = 0,/* Number of parameters given in call */
               nParaD = 0;/* Number of parameters given in definition */
  MacroStruct *pMacro;
  char        *pb;
  Sixteen      newZone,
               oldZone = NZONE_GLOBAL;

  FN_GetByte();
  if(GotByte == '.') {
    /* If macro is local, use current zone value */
    oldZone = Context[nContext].nZone_Now;
    FN_GetByte();
  }
  len = FN_Stream_ReadKeyword(&StringItem.String[2], LSMAX - 2, FALSE);
  /* (now: GotByte = illegal char) */
  if(len) {
    p = FN_Struct_GetZoned(oldZone, len, HTYPE_MACRO, FALSE);
    if(p) {
      pMacro = p->Data.Body;/* Get pointer to macro */
      /* Skip file name stored in macro */
      pb = &pMacro->Chars[0];
      while(*(pb++));
      /* Read parameters of call */
      SKIPSPACE;
      if(GotByte) {
        do {
          /* Read parameters up to end of statement, store in static array */
          if(nParaC == MAXMACROPARAMETERS) FN_Message(pseTooManyMP, ESERIOUS);
          MacroParaValue[nParaC] = FN_ALU_GetValue_Medium();
          MacroParaFlags[nParaC] = ffValue;
          nParaC++;
        } while(FN_Stream_Comma());
      }
      /* nParaC = number of parameters in call */

      /* Initialize new context */
      FN_Flow_NewContext();
      Context[nContext].nLines       = pMacro->nLinesDef;/* Line of def. */
      Context[nContext].nZone_Now    = ++nZone_Max;
      Context[nContext].hByteSource  = BYTESOURCE_RAM;
      Context[nContext].u.pRAM       = pb;
      Context[nContext].pSourceFile  = &pMacro->Chars[0];
      Context[nContext].pSourceTitle = &p->String[2];/* Macro title */
      Context[nContext].pSourceType  = psMacro;
      Context[nContext].OldFileByte  = 0;
      Context[nContext].OldRawByte   = ' ';/* Don't increase line number */
      /* Search for parameter labels and store pointers in static array */
      FN_GetByte();/* Get first byte of "new" context */
      if(GotByte) {
        do {
          SKIPSPACE;
          newZone = NZONE_GLOBAL;
          if(GotByte == '.') {
            /* If parameter is local (probably), use new zone value */
            newZone = Context[nContext].nZone_Now;
            FN_GetByte();
          }
          len = FN_Stream_ReadKeyword(&StringItem.String[2], LSMAX - 2, FALSE);
          /* (now: GotByte = illegal char) */
          if(len) {
            if(nParaD == MAXMACROPARAMETERS)
              FN_Message(pseTooManyMP, ESERIOUS);
            MacroParameter[nParaD] =
              FN_Struct_GetZoned(newZone, len, HTYPE_LABEL, TRUE);
            nParaD++;
          }
        } while(FN_Stream_Comma());
        FN_EnsureEOL();
      }
      Context[nContext].OldRawByte = ' ';/* Don't increase line number */
      EndReason = RNONE;/* Clear global variable */
      /* Compare number of parameters in call and definition */
      if(nParaC == nParaD) {
        /* Set parameters */
        for(a = 0; a < nParaC; a++) {
          v = MacroParaValue[a];
          MacroParameter[a]->Data.Bytes.FLAGS = MacroParaFlags[a];
          MacroParameter[a]->Data.Bytes.LOW   =  v        & 255;
          MacroParameter[a]->Data.Bytes.HIGH  = (v >>  8) & 255;
          MacroParameter[a]->Data.Bytes.BANK  = (v >> 16) & 255;
        }
        /* Actually *parse* the macro body */
        FN_ParseBlock();
        if(EndReason == RENDOFBLOCK) {
          EndReason = RNONE;/* Clear global variable */
          nContext--;/* Old context */
        } else FN_Message(pseeRightBrace, ESERIOUS);
      } else {
        /* Show two error messages, pointing to both definition and use */
        FN_Message(pseWrongNr, EERROR);
        nContext--;/* Old context */
        FN_Message(pseWrongNr, ESERIOUS);
      }
    } else FN_Message(pseUkMacro, EERROR);
  }
  FN_EnsureEOL();
}

/*
 * Shared utility routines
 */

/*
 * Check whether context depth is exceeded and increase counter if not.
 */
static void FN_Flow_NewContext() {
  if((nContext++) == MAXCONTEXTS) {
    nContext--;
    FN_Message(pseTooDeep, ESERIOUS);
  }
}

/*
 * Read block to address (starting with next byte) and terminate. The updated
 * pointer is returned. If the end address is exceeded, an error (serious)
 * will be generated. If the given RAM pointer is NULL, the block will simply
 * be skipped.
 */
static char *FN_Flow_StoreBlock(char *pb) {
  int  depth = 1;
  char byte;

  while(depth && (EndReason == RNONE)) {
    byte = FN_Stream_GetRawByte();
    if(pb) {
      if(pb == &pFlowBuffer[MAXBLOCKSIZE])
        FN_Message(pseNoMemLeft, ESERIOUS);
      *(pb++) = byte;
    }
    if((QuoteChar == NO_QUOTE_CHAR) && (byte == '{')) depth++;
    if((QuoteChar == NO_QUOTE_CHAR) && (byte == '}')) depth--;
  }
  if(depth) {
    FN_Message(pseeRightBrace, ESERIOUS);
  } else {
    EndReason = RRIGHTBRACE;
    GotByte = 0;/* Kluge */
    if(pb) *(pb - 1) = SEPARATOR;/* add separator */
  }
  return(pb);
}
