//============================================================================
//
//   SSSS    tt          lll  lll
//  SS  SS   tt           ll   ll
//  SS     tttttt  eeee   ll   ll   aaaa
//   SSSS    tt   ee  ee  ll   ll      aa
//      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
//  SS  SS   tt   ee      ll   ll  aa  aa
//   SSSS     ttt  eeeee llll llll  aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================

#include "Base.hxx"
#include "DebuggerExpressions.hxx"

#include "YaccParser.hxx"

namespace YaccParser {
#include <cstdio>
#include <cctype>

#include "y.tab.h"
static YYSTYPE result;
static string errMsg;

void yyerror(const char* e);

#ifdef __clang__
  #pragma clang diagnostic push
  #pragma clang diagnostic ignored "-Wold-style-cast"
  #pragma clang diagnostic ignored "-Wimplicit-fallthrough"
  #pragma clang diagnostic ignored "-Wmissing-variable-declarations"
  #include "y.tab.c"
  #pragma clang diagnostic pop
#else
  #include "y.tab.c"
#endif

enum class State {
  DEFAULT,
  IDENTIFIER,
  OPERATOR,
  SPACE
};

static State state = State::DEFAULT;
static const char *input, *c;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string& errorMessage()
{
  return errMsg;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expression* getResult()
{
  lastExp = nullptr;
  return result.exp;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void setInput(const string& in)
{
  input = c = in.c_str();
  state = State::DEFAULT;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int parse(const string& in)
{
  lastExp = nullptr;
  errMsg = "(no error)";
  setInput(in);
  return yyparse();
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// hand-rolled lexer. Hopefully faster than flex...
inline bool is_base_prefix(char x)
{
  return ( (x=='\\' || x=='$' || x=='#') );
}

inline bool is_identifier(char x)
{
  return ( (x>='0' && x<='9') ||
           (x>='a' && x<='z') ||
           (x>='A' && x<='Z') ||
            x=='.' || x=='_'  );
}

inline bool is_operator(char x)
{
  return ( (x=='+' || x=='-' || x=='*' ||
            x=='/' || x=='<' || x=='>' ||
            x=='|' || x=='&' || x=='^' ||
            x=='!' || x=='~' || x=='(' ||
            x==')' || x=='=' || x=='%' ||
            x=='[' || x==']' ) );
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// const_to_int converts a string into a number, in either the
// current base, or (if there's a base override) the selected base.
// Returns -1 on error, since negative numbers are the parser's
// responsibility, not the lexer's
int const_to_int(char* ch)
{
  // what base is the input in?
  Common::Base::Fmt format = Common::Base::format();

  switch(*ch) {
    case '\\':
      format = Common::Base::Fmt::_2;
      ch++;
      break;

    case '#':
      format = Common::Base::Fmt::_10;
      ch++;
      break;

    case '$':
      format = Common::Base::Fmt::_16;
      ch++;
      break;

    default: // not a base_prefix, use default base
      break;
  }

  int ret = 0;
  switch(format) {
    case Common::Base::Fmt::_2:
      while(*ch) {
        if(*ch != '0' && *ch != '1')
          return -1;
        ret *= 2;
        ret += (*ch - '0');
        ch++;
      }
      return ret;

    case Common::Base::Fmt::_10:
      while(*ch) {
        if(!isdigit(*ch))
          return -1;
        ret *= 10;
        ret += (*ch - '0');
        ch++;
      }
      return ret;

    case Common::Base::Fmt::_16:
      while(*ch) { // FIXME: error check!
        if(!isxdigit(*ch))
          return -1;
        int dig = (*ch - '0');
        if(dig > 9) dig = tolower(*ch) - 'a' + 10;
        ret *= 16;
        ret += dig;
        ch++;
      }
      return ret;

    default:
      cerr << "INVALID BASE in lexer!" << endl;
      return 0;
  }
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// special methods that get Cart RAM/ROM internal state
CartMethod getCartSpecial(char* ch)
{
  if(BSPF::equalsIgnoreCase(ch, "_bank"))
    return &CartDebug::getPCBank;
  else if(BSPF::equalsIgnoreCase(ch, "__lastread"))
    return &CartDebug::lastReadBaseAddress;
  else if(BSPF::equalsIgnoreCase(ch, "__lastwrite"))
    return &CartDebug::lastWriteBaseAddress;
  else
    return nullptr;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// special methods that get e.g. CPU registers
CpuMethod getCpuSpecial(char* ch)
{
  if(BSPF::equalsIgnoreCase(ch, "a"))
    return &CpuDebug::a;
  else if(BSPF::equalsIgnoreCase(ch, "x"))
    return &CpuDebug::x;
  else if(BSPF::equalsIgnoreCase(ch, "y"))
    return &CpuDebug::y;
  else if(BSPF::equalsIgnoreCase(ch, "pc"))
    return &CpuDebug::pc;
  else if(BSPF::equalsIgnoreCase(ch, "sp"))
    return &CpuDebug::sp;
  else if(BSPF::equalsIgnoreCase(ch, "c"))
    return &CpuDebug::c;
  else if(BSPF::equalsIgnoreCase(ch, "z"))
    return &CpuDebug::z;
  else if(BSPF::equalsIgnoreCase(ch, "n"))
    return &CpuDebug::n;
  else if(BSPF::equalsIgnoreCase(ch, "v"))
    return &CpuDebug::v;
  else if(BSPF::equalsIgnoreCase(ch, "d"))
    return &CpuDebug::d;
  else if(BSPF::equalsIgnoreCase(ch, "i"))
    return &CpuDebug::i;
  else if(BSPF::equalsIgnoreCase(ch, "b"))
    return &CpuDebug::b;
  else if(BSPF::equalsIgnoreCase(ch, "_icycles"))
    return &CpuDebug::icycles;
  else
    return nullptr;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// special methods that get TIA internal state
TiaMethod getTiaSpecial(char* ch)
{
  if(BSPF::equalsIgnoreCase(ch, "_scan"))
    return &TIADebug::scanlines;
  if(BSPF::equalsIgnoreCase(ch, "_scycles"))
    return &TIADebug::cyclesThisLine;
  else if(BSPF::equalsIgnoreCase(ch, "_fcount"))
    return &TIADebug::frameCount;
  else if(BSPF::equalsIgnoreCase(ch, "_fcycles"))
    return &TIADebug::frameCycles;
  else if(BSPF::equalsIgnoreCase(ch, "_cyclesLo"))
    return &TIADebug::cyclesLo;
  else if(BSPF::equalsIgnoreCase(ch, "_cyclesHi"))
    return &TIADebug::cyclesHi;
  else if(BSPF::equalsIgnoreCase(ch, "_cclocks"))
    return &TIADebug::clocksThisLine;
  else if(BSPF::equalsIgnoreCase(ch, "_vsync"))
    return &TIADebug::vsyncAsInt;
  else if(BSPF::equalsIgnoreCase(ch, "_vblank"))
    return &TIADebug::vblankAsInt;
  else
    return nullptr;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int yylex() {
  static char idbuf[255];  // NOLINT  (will be rewritten soon)
  char o, p;
  yylval.val = 0;
  while(*c != '\0') {
    switch(state) {
      case State::SPACE:
        yylval.val = 0;
        if(isspace(*c)) {
          c++;
        } else if(is_identifier(*c) || is_base_prefix(*c)) {
          state = State::IDENTIFIER;
        } else if(is_operator(*c)) {
          state = State::OPERATOR;
        } else {
          state = State::DEFAULT;
        }

        break;

      case State::IDENTIFIER:
        {
          CartMethod cartMeth;
          CpuMethod  cpuMeth;
          TiaMethod  tiaMeth;

          char *bufp = idbuf;
          *bufp++ = *c++; // might be a base prefix
          while(is_identifier(*c)) { // may NOT be base prefixes
            *bufp++ = *c++;
          }
          *bufp = '\0';
          state = State::DEFAULT;

          // Note: specials (like "a" for accumulator) have priority over
          // numbers. So "a" always means accumulator, not hex 0xa. User
          // is welcome to use a base prefix ("$a"), or a capital "A",
          // to mean 0xa.

          // Also, labels have priority over specials, so Bad Things will
          // happen if the user defines a label that matches one of
          // the specials. Who would do that, though?

          if(Debugger::debugger().cartDebug().getAddress(idbuf) > -1) {
            yylval.Equate = idbuf;
            return EQUATE;
          } else if( (cpuMeth = getCpuSpecial(idbuf)) ) {
            yylval.cpuMethod = cpuMeth;
            return CPU_METHOD;
          } else if( (cartMeth = getCartSpecial(idbuf)) ) {
            yylval.cartMethod = cartMeth;
            return CART_METHOD;
          } else if( (tiaMeth = getTiaSpecial(idbuf)) ) {
            yylval.tiaMethod = tiaMeth;
            return TIA_METHOD;
          } else if( Debugger::debugger().getFunctionDef(idbuf) != EmptyString ) {
            yylval.DefinedFunction = idbuf;
            return FUNCTION;
          } else {
            yylval.val = const_to_int(idbuf);
            if(yylval.val >= 0)
              return NUMBER;
            else
              return ERR;
          }
        }

      case State::OPERATOR:
        o = *c++;
        if(!*c) return o;
        if(isspace(*c)) {
          state = State::SPACE;
          return o;
        } else if(is_identifier(*c) || is_base_prefix(*c)) {
          state = State::IDENTIFIER;
          return o;
        } else {
          state = State::DEFAULT;
          p = *c++;
          if(o == '>' && p == '=')
            return GTE;
          else if(o == '<' && p == '=')
            return LTE;
          else if(o == '!' && p == '=')
            return NE;
          else if(o == '=' && p == '=')
            return EQ;
          else if(o == '|' && p == '|')
            return LOG_OR;
          else if(o == '&' && p == '&')
            return LOG_AND;
          else if(o == '<' && p == '<')
            return SHL;
          else if(o == '>' && p == '>')
            return SHR;
          else {
            c--;
            return o;
          }
        }
        // break;  Never executed

      case State::DEFAULT:
        yylval.val = 0;
        if(isspace(*c)) {
          state = State::SPACE;
        } else if(is_identifier(*c) || is_base_prefix(*c)) {
          state = State::IDENTIFIER;
        } else if(is_operator(*c)) {
          state = State::OPERATOR;
        } else {
          yylval.val = *c++;
          return yylval.val;
        }
        break;
    }
  }

  return 0; // hit NUL, end of input.
}

} // namespace YaccParser
