/* program.c The parsed program storage. 
 *  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 "lex.h"
#include "serial.h"
#include "strings.h"
#include "fs.h"

// The I space and its end.
__xdata uint8_t  I[ISIZE];
__xdata uint16_t PMAX;


extern __xdata char       STR[];
extern __xdata uint8_t    ttype;
extern __xdata uint32_t   N;
extern __xdata floatOrInt FACC;
extern __bit              errorThrown;
extern __xdata uint8_t    infh;
extern __xdata uint8_t    SINDEX;

typedef struct {
  char    __code *str;
  uint8_t cde;
} tableConvert;


// The different punctuation marks and their equivalents stored in I space. (see storage.h).
static __code tableConvert PunctTable[] = {
  {"*", TIMES},
  {"/", QUOTIENT},
  {"+", PLUS},
  {"-", DIFFERENCE},
  {"%", REMAINDER},
  {"=", ASSIGN},
  {"<", LESSP},
  {"<=", LEQ},
  {">", GREATERP},
  {">=", GEQ},
  {"==", EQUAL},
  {"!=", NEQ},
  {"||", OR},
  {"&&", AND},
  {"!", NOT},
  {"|", BOR},
  {"&", BAND},
  {"~", BNOT},
  {"(", LPAREN},
  {")", RPAREN},
  {"{", LCBRACKET},
  {"}", RCBRACKET},
  {";", SEMICOLON},
  {":", COLON},
  {",", COMMA},
  {"[", LSQB},
  {"]", RSQB}
};


// The different keywords and their equivalent values as stored in I space (see storage.h).
static __code  tableConvert KeyTable[] = {
  {"LET", LET},
  {"IF", IF},
  {"ELSE", ELSE},
  {"FOR", FOR},
  {"TO", TO},
  {"BY", BY},
  {"SEND", SEND},
  {"PRINT", PRINT},
  {"STOP", STOP},
  {"FREQUENCY", FREQUENCY},
  {"CHAIN", CHAIN},
  {"CREATE", CREATE},
  {"OPEN", OPEN},
  {"READ", READ},
  {"WRITE", WRITE},
  {"CLOSE", CLOSE},
  {"WHILE", WHILE},

  {"DIRECTION", DIRECTION},
  {"DIG0", DIG0},
  {"DIG1", DIG1},
  {"ANYCHAR", ANYCHAR},
  {"RED", RED},
  {"YELLOW", YELLOW},
  {"GREEN", GREEN},
  {"BLUE", BLUE},
  {"IN", IN},
  {"OUT", OUT},
  {"PP", PP},
  {"OD", OD},
  {"A2DC0", A2DC0},
  {"A2DC1", A2DC1},
  {"RAWTEMP", RAWTEMP},
  {"TEMP", TEMP},

  {"PAUSE", PAUSE},
  {"SLEEP", SLEEP},
  {"MS", MILLISECONDS},
  {"MILLISECONDS", MILLISECONDS},
  {"S", SECONDS},
  {"SECOND", SECONDS},
  {"SECONDS", SECONDS},
  {"M", MINUTES},
  {"MINUTE", MINUTES},
  {"MINUTES", MINUTES},

  {"DITMS", DITMS},
  {"SPACEDITS", SPACEDITS},
  {"BYTEVECTOR", BYTEVECTOR},
  {"FLOATVECTOR", FLOATVECTOR},

  {"ISINT", ISINT},
  {"ISFLOAT", ISFLOAT},
  {"ISSTRING", ISSTRING},
  {"ISBYTEVECTOR", ISBYTEVECTOR},
  {"ISFLOATVECTOR", ISFLOATVECTOR},
  {"SQRT", SQRT},
  {"ROUND", ROUND},
  {"ABS", ABS},
  {"READCH", READCH},
  {"PUTCH", PUTCH}
};



static void saveByte(uint8_t v)
// Save a byte in I space checking for space overflow. We usually have something like 2k here
// (11 bits worth). This probably 3 or 4 pages of code.
{
  if (PMAX >= ISIZE)
  {
    errorThrown = 1;
    return;
  }
  I[PMAX++] = v;
}



void initProgram(void)
// Indicates there's no program loaded. This called from startup as well as here so a run
// without a program doesn't go wild.
{
  PMAX = 0;
}

      

void loadProgramFile(void)
//     Load a program source - the file handle is already in infh and we read utnil EOF.
// The file is closed 
{
  __xdata uint8_t *p;
  uint8_t i;

  // Clean up storage.
  initProgram();
  initDynamic();
  initSymtab();
  initInts();
  resetFOR();

  // Scan until we get an error on newLine.
 loop:
  ntok();

  switch (ttype) {
  // We have an integer - put it into the I space.
  case LEXNUM:
    if (N == 0) saveByte(ZERO);
    else if (N == 1) saveByte(ONE);
    else
    {
      saveByte(INT32);
      saveByte(N);
      saveByte(N >> 8);
      saveByte(N >> 16);
      saveByte(N >> 24);
    }
    break;

  // We have a float - write it to the I space from lexical.
  case LEXFLOAT:
    saveByte(FLOAT);
    p = ((__xdata uint8_t*) &FACC.f);
    saveByte(*p++);
    saveByte(*p++);
    saveByte(*p++);
    saveByte(*p++);
    break;

  // A string, copy bytes to I space, from there, they go to dynamic RAM.
  case LEXSTRING:
    saveByte(STRING);
    p = (__xdata uint8_t *) &STR;
    while (*p != 0) saveByte(*p++);
    saveByte(0);
    break;

    // Punctuation - convert to an index or flag an error.
  case LEXPUNCT:
    //    putstring("punct '"); putstring(STR); putstring("'\n");
    if (strEq(STR, "//"))
    {
      newLine();
      goto loop;
    }

    for (i = 0; i < sizeof(PunctTable) / sizeof(tableConvert); ++i)
      if (strEq(PunctTable[i].str, STR))
      {
        saveByte(PunctTable[i].cde);
        goto loop;
      }
          
    putch('\'');
    putstring(STR);
    putstring("' bad punctuation\n");
    errorThrown = 1;
    break;

  // An identifier. Could be a keyword or an identifier.
  case LEXID:
    // First look for keywords.
    // putstring("checking "); putstring(STR); terpri();
    for (i = 0; i < sizeof(KeyTable) / sizeof(tableConvert); ++i)
      if (strCaseEq(KeyTable[i].str))
      {
        saveByte(KeyTable[i].cde);
        goto loop;
      }

    // We have a variable. Put out its symbol table location.
    saveByte(VARIABLE);
    if ((i = lookupPName(STR)) != 0xFF) saveByte(i);
    else saveByte(createSymbol());
    break;

  // End of line - gives an error if EOF otherwise continue on next line.
  case LEXEOL:
    newLine();
    if (errorThrown) 
    {
      errorThrown = 0;
      putstring("Loaded ");
      putuint16(PMAX);
      putstring(" bytes\n");
      return;
    }
    break;

  // Lexical fails - probably never occurs.
  default:
    putstring("Bad Program\n");
    errorThrown = 1;
    return;
  }
  goto loop;
}




void loadProgram(void)
// The command line level to load a program. This is the result of a 'load' command for debugging.
{
  uint8_t fh;
  
  ntok();
  if (ttype == LEXID || ttype == LEXSTRING)
  {
    if ((fh = openFile(STR)) == 255)
    {
      putstring("Can't load ");
      putstring(STR);
      terpri();
      return;
    }
    infh = fh;
    loadProgramFile();
    if (errorThrown) putstring("Load fails\n");
  }
  else putstring("load missing file\n");
}


void dumpProgram(void)
// Debug code - dump the I space in hexadecimal.
{
  uint16_t i;
  for (i = 0; i < PMAX; ++i)
  {
    if ((i & 0x000F) == 0) { puthex16(i); putstring(": "); }
    puthexbyte(I[i]);
    if ((i & 0x000F) == 0x000F) terpri(); else putch(' ');
  }
  terpri();
}
      
// EOF
