/* lex.c - for reading tokens for execution.
 *  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 <string.h>

#include "lfxmit.h"
#include "config.h"
#include "serial.h"
#include "lex.h"
#include "fs.h"
#include "storage.h"

// The input buffer and its index.
__xdata char INBUF[INLINESIZE + 1];

// The input line number and character index.
__xdata uint16_t lineno;
__xdata uint8_t  index;

// The token type.
__xdata uint8_t ttype;

// String/ID/punctuation.
__xdata char   STR[MAXSTR];

// Integers of all types.
__xdata uint32_t N;

// The input file handle.
__xdata uint8_t infh;

// CTERM buffer.
__xdata char     CBUF[64];
__xdata uint8_t  cindex;
__xdata uint8_t  cinbuf;

extern __bit errorThrown;
extern __xdata floatOrInt FACC;
extern __xdata floatOrInt IACC;
extern __xdata floatOrInt IACCB;

void initLex(void)
// Initialize the lexical scanner for input. Default input is serial port.
{
  lineno = 0;
  cinbuf = 0;
  index = 0;
  infh = serialIn;
}


static int16_t getCTERMchar(void)
// Input is from a remote file. Get from our buffer unless we need to get
// a new one. Default size is 64 bytes so don't futz with the size in CTERM.
{
  uint8_t i;
  if (cinbuf == 0 || cindex >= cinbuf)
  {
    putch(27);
    putch('d');
    if ((cinbuf = getch()) == 0) return -1;
    for (i = 0; i < cinbuf; ++i) CBUF[i] = getch();
    cindex = 0;
  }
  return CBUF[cindex++];
}



void getCTERMLine(void)
// Get a line from a remote PC file which are buffered 64 as above. Switch back
// to serial input if this file ends.
{
  int16_t c;

  index = 0;
  for (c = getCTERMchar(); c != -1 && c != '\n'; c = getCTERMchar())
    if (c != 13 && index < INLINESIZE - 1) INBUF[index++] = c;
  INBUF[index] = 0;
  if (c == -1) infh = serialIn;
  
}
  


void getFileLine(uint8_t fh)
// Get a line from a local SPI flash file system. Switch back to serial input
// when you get to the file end.
{
  int16_t c;

  // Read characters to EOL, EOF. Ignore CR. Don't overflow buffer.
  index = 0;
  for (c = getchFile(fh); c != -1 && c != '\n'; c = getchFile(fh))
    if (c != 13)
    {
      if (index < INLINESIZE - 1) INBUF[index++] = c;
    }
  INBUF[index] = 0;

  // If EOF, then switch back to plain serial input. Set error to indicate EOF found.
  if (c == -1)
  {
    closeFile(fh);
    infh = serialIn;
    errorThrown = 1;
  }
}



void newLine(void)
// Get a new input line from one of the possible sources.
//    file0In:        File 0.
//    file1In:        File 1.
//    serialIn:       Standard serial port input. Does line number echo.
//    remoteFn:       Input from CTERM.
{
  
  switch (infh) {

  // File 0.
  case file0In:
    getFileLine(0);
    break;

  // File 1.
  case file1In:
    getFileLine(1);
    break;

  // Serial input has line editing and interactive.
  case serialIn:
    putuint16(++lineno);
    putstring(": ");
    getline(INBUF, INLINESIZE);
    break;

  // Read a remote (PC) file using CTERM protocol.
  case remoteFIn:
    getCTERMLine();
    break;
  }
  index = 0;
}



// Lexical scanner states.
enum {
  lexWhiteSpace,
  lexID,
  lexInt,
  lexIntHex,
  lexFloat,
  lexString,
  lexStringSlash,
  lexEqual,
  lexLess,
  lexGreater,
  lexNeq,
  lexAnd,
  lexOr,
  lexDSlash
};

void ntok(void)
// Read the next input token and return. This is a state machine reader, not
// to efficient but the C8051 has nothing else to do.
{
  __xdata uint8_t state = lexWhiteSpace;
  char    c;
  __xdata uint8_t oindex = 0;

  // Scan through the buffer from where we left off (index is global).
  for (c = INBUF[index]; c != 0; )
  {
    c = INBUF[index];
    switch (state) {

    // White space characters and switching to the input types.
    case lexWhiteSpace:

      // Identifiers start with A-Z, a-z then can have 0-9 and . in them.
      // They are converted to uppercase.
      if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
      {
        STR[0] = c,
          state = lexID,
          oindex = 1,
          ++index;
      }

      // Numbers (even hexadecimal) start with 0-9.
      else if (c >= '0' && c <= '9')
      {
        N = c - '0',
          state = lexInt,
          ++index;
      }

      // Strings are double quoted. Back slash for \r, \n and \ anything else.
      // Characters stored in STR, their case is left alone.
      else if (c == '"')
      {
        oindex = 0,
          state = lexString,
          ++index;
      }
      // Lots of different punctuation, some with 2 character, but no emoji's.
      // Characters stored in STR.
      // =, ==
      else if (c == '=')
      {
        state = lexEqual,
          ++index;
      }

      // <, <=
      else if (c == '<')
      {
        state = lexLess,
          ++index;
      }

      // >, >=
      else if (c == '>')
      {
        state = lexGreater,
          ++index;
      }

      // !, !=
      else if (c == '!')
      {
        state = lexNeq,
          ++index;
      }

      // &, &&
      else if (c == '&')
      {
        state = lexAnd,
          ++index;
      }

      // |, ||
      else if (c == '|')
      {
        state = lexOr,
          ++index;
      }

      // // comment start.
      else if (c == '/')
      {
        state = lexDSlash,
          ++index;
      }

      // Start of floating point.
      else if (c == '.')
      {
        N = 0,
          IACC.i = 0,
          IACCB.i = 1,
          state = lexFloat,
          ++index;
      }

      // Single marks.
      else if (c == '@' ||
               c == '#' ||
               c == '$' ||
               c == '%' ||
               c == '^' ||
               c == '*' ||
               c == '(' ||
               c == ')' ||
               c == '_' ||
               c == '-' ||
               c == '+' ||
               c == '=' ||
               c == '\\' ||
               c == '{' ||
               c == '}' ||
               c == '[' ||
               c == ']' ||
               c == ':' ||
               c == ';' ||
               c == '\'' ||
               c == ',' ||
               c == '~' ||
               c == '?')

      {
        STR[0] = c,
          STR[1] = 0,
          ++index,
          ttype = LEXPUNCT;
        return;
      }

      // Otherwise, just 
      else ++index;
      break;

    // Capture an identifier. Follow ons are A-Z and 0-9 and a dot (so we
    // can do reasonable file names).
    case lexID:
      if ((c >= 'A' && c <= 'Z') ||
          (c >= 'a' && c <= 'z') ||
          (c >= '0' && c <= '9') || c == '.')
        STR[oindex++] = c;
      else
      {
        STR[oindex] = 0,
          ttype = LEXID;
        return;
      }
      ++index;
      break;

    // Capture a 32 bit decimal number. Hexadecimal is allowed, even in the
    // middle of a number. A . here means start of a floating point value.
    case lexInt:
      if (c == 'X' || c == 'x')
      {
        state = lexIntHex,
          ++index;
      }
      else if (c >= '0' && c <= '9')
      {
        N = N * 10 + (c - '0'),
          ++index;
      }
      else if (c == '.')
      {
        IACCB.i = 1,
          IACC.i = 0,
          ++index,
          state = lexFloat;
      }
      else
      {
        ttype = LEXNUM;
        return;
      }
      break;

    // We also allow hex.
    case lexIntHex:
      if (c >= '0' && c <= '9')
      {
        N = (N << 4) + (c - '0'),
          ++index;
      }
      else if (c >= 'A' && c <= 'F')
      {
        N = (N << 4) + (c - 'A' + 10),
          ++index;
      }
      else if (c >= 'a' && c <= 'f')
      {
        N = (N << 4) + (c - 'a' + 10),
          ++index;
      }

      else
      {
        ttype = LEXNUM;
        return;
      }
      break;

    case lexFloat:
      if (c >= '0' && c <= '9')
      {
        IACC.i = IACC.i * 10 + c - '0',
          IACCB.i *= 10,
          ++index;
      }
      else
      {
        FACC.f = (float) N + ((float) IACC.i) / ((float) IACCB.i),
          ttype = LEXFLOAT;
        return;
      }
      break;

    // Capture a string, convert \r, \n to 13 and 10, anything else just left
    // as is.
    case lexString:
      if (c == '"')
      {
        STR[oindex] = 0,
          ++index,
          ttype = LEXSTRING;
        return;
      }
      else if (c == '\\')
      {
        ++index,
          state = lexStringSlash;
      }
      else
      {
        STR[oindex] = c,
          ++oindex,
          ++index;
      }
      break;

    // Back quoted characters in a string.
    case lexStringSlash:
      if (c == 'r')
      {
        STR[oindex++] = 13,
          ++index,
          state = lexString;
      }
      else if (c == 'n')
      {
        STR[oindex++] = 10,
          ++index,
          state = lexString;
      }
      else
      {
        STR[oindex++] = c,
          ++index,
          state = lexString;
      }
      break;

    // =, ==
    case lexEqual:
      STR[0] = '=';
      if (c == '=')
      {
        STR[1] = '=',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // <, <=
    case lexLess:
      STR[0] = '<';
      if (c == '=')
      {
        STR[1] = '=',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // >, >=
    case lexGreater:
      STR[0] = '>';
      if (c == '=')
      {
        STR[1] = '=',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // !, !=
    case lexNeq:
      STR[0] = '!';
      if (c == '=')
      {
        STR[1] = '=',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // &, &&
    case lexAnd:
      STR[0] = '&';
      if (c == '&')
      {
        STR[1] = '&',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // |, ||
    case lexOr:
      STR[0] = '|';
      if (c == '|')
      {
        STR[1] = '|',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;

    // / or //
    case lexDSlash:
      STR[0] = '/';
      if (c == '/')
      {
        STR[1] = '/',
          STR[2] = 0,
          ++index,
          ttype = LEXPUNCT;
      }
      else
      {
        STR[1] = 0,
          ttype = LEXPUNCT;
      }
      return;
      
    }
  }
  ttype = LEXEOL;
  return;
}


__bit isPunctuation(char *c)
// Checking for punctuation.
{
  if (ttype == LEXPUNCT && strcmp(STR, c) == 0) return 1;
  return 0;
}


__bit isKeyword(char *k)
// Check for a keyword.
{
  if (ttype == LEXID && strcmp(STR, k)) return 1;
  return 0;
}


// EOF
