/* for.c - forloop.
 *  Copyright (C) 2022 by Jed Marti
 *   This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see https://www.gnu.org/licenses/.
 */

#include <C8051F380.h>
#include <stdint.h>

#include "lfxmit.h"
#include "storage.h"
#include "serial.h"


// The FOR loop table for nested ones.
__xdata forValues FRT[FORDEPTH];
__xdata uint8_t   FINDEX;            // Points to first available.


extern __bit               errorThrown;
extern __bit               doit;
extern __bit               stopBit;
extern __xdata SymtabEntry S[];
extern __xdata uint16_t    iONE;
extern __xdata uint16_t    PC;
extern __xdata floatOrInt  FACC;
extern __xdata floatOrInt  FACCB;
extern __xdata floatOrInt  IACC;
extern __xdata floatOrInt  IACCB;
extern uint16_t            DREG;

extern uint8_t SPTR;


void resetFOR(void)
{
  FINDEX = 0;
}



static void forError(char *msg)
//    An attempt to save some typing. 
{
  putstring("FOR ");
  putstring(msg);
  terpri();
  errorThrown = 1;
}



void runFOR(void)
// for var = value to value [ by value];
{
  uint8_t var = 0;
  __bit   updown = 1;    // 1 is up, 0 is down.

  // Skip the FOR.
  incPC();

  //putstring("FINDEX: "); putbyte(FINDEX); terpri();
  // Too deep, then quit.
  if (FINDEX >= FORDEPTH) { forError("too deep"); return; }

  // Must be a variable name.
  if (getPByte() != VARIABLE) { forError("no variable"); return; }

  // Save the variable locally, not on the FOR stack because we don't leave the FOR while
  // we're executing the body.
  incPC();
  if (doit) var = getPByte();

  // Check for =.
  incPC();
  if (getPByte() != ASSIGN) { forError("missing ="); return; }

  // Start expression. If we're executing, then save the starting value in the symbol.
  incPC();
  parseExpression();
  if (errorThrown) return;
  if (doit) S[var].addr = DREG = popST();

  // Look for limit TO, get the value and save it on the FOR stack where we'll pick it up later.
  if (getPByte() != TO) { forError("missing TO"); return; }
  incPC();
  parseExpression();
  if (errorThrown) return;
  if (doit) FRT[FINDEX].limit = popST();
  
  // Check for BY. If not, the default increment is 1.
  if (getPByte() != BY)
  {
    if (doit) FRT[FINDEX].increment = iONE;
  }
  else
  {
    incPC();
    parseExpression();
    if (errorThrown) return;
    if (doit) FRT[FINDEX].increment = popST();
  }

  // We're now at the first byte (probably the ; trailing the FOR). This is where execution
  // restarts when necessary.
  FRT[FINDEX].restart = PC;
  ++FINDEX;

 loop:
  //putstring("  At "); puthex16(PC); terpri();
  runStatement();

  // If an error or we're not executing, then return.
  if (errorThrown || stopBit || !doit) return;
  //putstring("  Body finished at PC = "); puthex16(PC); terpri();
  
  // Perform the increment.
  DREG = S[var].addr;
  switch (tag()) {
  case DESC_INT:
    getIACC();
    DREG = FRT[FINDEX - 1].increment;
    switch (tag()) {
    case DESC_INT:
      getIACCB();
      updown = IACCB.i > 0;
      S[var].addr = newInt(IACC.i + IACCB.i);
      DREG = S[var].addr;
      getIACC();
      break;
    case DESC_FLOAT:
      getFACC();
      updown = FACC.f > 0.0f;
      FACCB.f = (float) IACC.i;
      S[var].addr = newFloat(FACCB.f + FACC.f);
      break;
    default:
      forError("bad increment");
      return;
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = FRT[FINDEX - 1].increment;
    switch (tag()) {
    case DESC_INT:
      getIACC();
      updown = IACC.i > 0;
      FACCB.f = (float) IACC.i;
      S[var].addr = newFloat(FACC.f + FACCB.f);
      break;
    case DESC_FLOAT:
      //      putstring("Float increment\n");
      getFACCB();
      updown = FACCB.f > 0.0f;
      S[var].addr = newFloat(FACC.f + FACCB.f);
      break;
    default:
      forError("bad increment");
      return;
    }
    break;

  default:
    forError("bad iterator");
    return;
  }

  // Check to see if limit exceeded. We switch on the limit type.
  DREG = FRT[FINDEX - 1].limit;
  // putstring("For limit type "); putbyte(tag()); putstring(" at "); puthex16(DREG); terpri();
  switch (tag()) {
  case DESC_INT:
    getIACC();
    DREG = S[var].addr;
    switch (tag()) {
    case DESC_INT:
      getIACCB();
      if (updown)
      {
        if (IACCB.i > IACC.i) goto done;
      }
      else if (IACCB.i < IACC.i) goto done;
      break;
    case DESC_FLOAT:
      getFACC();
      FACCB.f = (float) IACC.i;
      if (updown)
      {
        if (FACC.f > FACCB.f) goto done;
      }
      else
        if (FACC.f < FACCB.f) goto done;
      break;

    default:
      forError("bad limit");
      return;
    }
    break;

  case DESC_FLOAT:
    getFACC();
    DREG = S[var].addr;
    switch (tag()) {
    case DESC_INT:
      getIACC();
      FACCB.f = (float) IACC.i;
      if (updown)
      {
        if (FACCB.f > FACC.f) goto done;
      }
      else if (FACCB.f < FACC.f) goto done;
      break;
    case DESC_FLOAT:
      getFACCB();
      if (updown)
      {
        if (FACCB.f > FACC.f) goto done;
      }
      else if (FACCB.f < FACC.f) goto done;
      break;
    default:
      forError("bad limit");
      return;
    }
    break;
    
  default:
    forError("bad limit");
    return;
  }
  
  // Limit not exceed, restart.
  //  putstring("  not at limit, restarting\n");
  PC = FRT[FINDEX - 1].restart;
  goto loop;

 done:
  --FINDEX;
  return;
}




// EOF
