/*
  Expression Evaluator Library (NS-EEL) v2
  Copyright (C) 2004-2013 Cockos Incorporated
  Copyright (C) 1999-2003 Nullsoft, Inc.
  
  nseel-eval.c

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

#include <string.h>
#include <ctype.h>
#include "ns-eel-int.h"
#include "../wdlcstring.h"


static const char *nseel_skip_space_and_comments(const char *p, const char *endptr)
{
  for (;;)
  {
    while (p < endptr && isspace((unsigned char)p[0])) p++;
    if (p >= endptr-1 || *p != '/') return p;

    if (p[1]=='/')
    {
      while (p < endptr && *p != '\r' && *p != '\n') p++;
    }
    else if (p[1] == '*')
    {
      p+=2;
      while (p < endptr-1 && (p[0] != '*' || p[1] != '/')) p++;
      p+=2;
      if (p>=endptr) return endptr;
    }
    else return p;
  }
}

// removes any escaped characters, also will convert pairs delim_char into single delim_chars
int nseel_filter_escaped_string(char *outbuf, int outbuf_sz, const char *rdptr, size_t rdptr_size, char delim_char) 
{
  int outpos = 0;
  const char *rdptr_end = rdptr + rdptr_size;
  while (rdptr < rdptr_end && outpos < outbuf_sz-1)
  {
    char thisc=*rdptr;
    if (thisc == '\\' && rdptr < rdptr_end-1) 
    {
      const char nc = rdptr[1];
      if (nc == 'r' || nc == 'R') { thisc = '\r'; }
      else if (nc == 'n' || nc == 'N') { thisc = '\n'; }
      else if (nc == 't' || nc == 'T') { thisc = '\t'; }
      else if (nc == 'b' || nc == 'B') { thisc = '\b'; }
      else if ((nc >= '0' && nc <= '9') || nc == 'x' || nc == 'X')
      {
        unsigned char c=0;
        char base_shift = 3; 
        char num_top = '7';

        rdptr++; // skip backslash
        if (nc > '9') // implies xX
        {
          base_shift = 4;
          num_top = '9';
          rdptr ++; // skip x
        }

        while (rdptr < rdptr_end)
        {
          char tc=*rdptr;
          if (tc >= '0' && tc <= num_top) 
          {
            c = (c<<base_shift) + tc - '0';
          }
          else if (base_shift==4)
          {
            if (tc >= 'a' && tc <= 'f')
            {
              c = (c<<base_shift) + (tc - 'a' + 10);
            }
            else if (tc >= 'A' && tc <= 'F') 
            {
              c = (c<<base_shift) + (tc - 'A' + 10);
            }
            else break;
          }
          else break;

          rdptr++;
        }
        outbuf[outpos++] = (char)c;
        continue;
      }
      else  // \c where c is an unknown character drops the backslash -- works for \, ', ", etc
      { 
        thisc = nc; 
      }
      rdptr+=2; 
    }
    else 
    {
      if (thisc == delim_char) break; 
      rdptr++;
    }
    outbuf[outpos++] = thisc;
  }
  outbuf[outpos]=0;
  return outpos;
}

int nseel_stringsegments_tobuf(char *bufOut, int bufout_sz, struct eelStringSegmentRec *list) // call with NULL to calculate size, or non-null to generate to buffer (returning size used)
{
  int pos=0;
  while (list)
  {
    if (!bufOut)
    {
      pos += list->str_len;
    }
    else if (list->str_len > 1) 
    {
      if (pos >= bufout_sz) break;
      pos += nseel_filter_escaped_string(bufOut + pos, bufout_sz-pos,  list->str_start+1, list->str_len-1, list->str_start[0]); 
    }
    list = list->_next;
  }
  return pos;
}



// state can be NULL, it will be set if finished with unterminated thing: 1 for multiline comment, ' or " for string
const char *nseel_simple_tokenizer(const char **ptr, const char *endptr, int *lenOut, int *state)
{
  const char *p = *ptr;
  const char *rv = p;
  char delim;

  if (state) // if state set, returns comments as tokens
  {
    if (*state == 1) goto in_comment;

    #ifndef NSEEL_EEL1_COMPAT_MODE
      if (*state == '\'' || *state == '\"')
      {
        delim = (char)*state;
        goto in_string;
      }
    #endif

    // skip any whitespace
    while (p < endptr && isspace((unsigned char)p[0])) p++;
  }
  else
  {
    // state not passed, skip comments (do not return them as tokens)
    p = nseel_skip_space_and_comments(p,endptr);
  }

  if (p >= endptr) 
  {
    *ptr = endptr;
    *lenOut = 0;
    return NULL;
  }

  rv=p;

  if (*p == '$' && p+3 < endptr && p[1] == '\'' && p[3] == '\'')
  {
    p+=4;
  }
  else if (state && *p == '/' && p < endptr-1 && (p[1] == '/' || p[1] == '*'))
  {
    if (p[1] == '/')
    {
      while (p < endptr && *p != '\r' && *p != '\n') p++; // advance to end of line
    }
    else
    {
      if (state) *state=1;
      p+=2;
in_comment:
      while (p < endptr)
      {
        const char c = *p++;
        if (c == '*' && p < endptr && *p == '/')
        {
          p++;
          if (state) *state=0;
          break;
        }
      }

    }
  }
  else if (isalnum((unsigned char)*p) || *p == '_' || *p == '#' || *p == '$' || (*p == '.' && p < endptr-1 && p[1] >= '0' && p[1] <= '9'))
  {
    if (*p == '$' && p < endptr-1 && p[1] == '~') p++;
    p++;
    while (p < endptr && (isalnum((unsigned char)*p) || *p == '_' || *p == '.')) p++;
  }
#ifndef NSEEL_EEL1_COMPAT_MODE
  else if (*p == '\'' || *p == '\"')
  {    
    delim = *p++;
    if (state) *state=delim;
in_string:

    while (p < endptr)
    {
      const char c = *p++;
      if (p < endptr && c == '\\') p++;  // skip escaped characters
      else if (c == delim) 
      {
        if (state) *state=0;
        break;
      }
    }
  }
#endif
  else 
  {  
    p++;
  }
  *ptr = p;
  *lenOut = (int) (p - rv);
  return p>rv ? rv : NULL;
}



#ifdef NSEEL_SUPER_MINIMAL_LEXER

  int nseellex(opcodeRec **output, YYLTYPE * yylloc_param, compileContext *scctx)
  {
    int rv=0,toklen=0;
    const char *rdptr = scctx->rdbuf;
    const char *endptr = scctx->rdbuf_end;
    const char *tok = nseel_simple_tokenizer(&rdptr,endptr,&toklen,NULL);
    *output = 0;
    if (tok)
    {
      rv = tok[0];
      if ((rv == '0' || rv == '$') && toklen > 1 && (tok[1] == 'x' || tok[1] == 'X')) // 0xf00 or $xf00
      {
        int x;
        for (x = 2; x < toklen; x ++)
          if (!((tok[x] >= '0' && tok[x] <= '9') ||
                (tok[x] >= 'a' && tok[x] <= 'f') ||
                (tok[x] >= 'A' && tok[x] <= 'F')))
          {
            tok += x;
            break;
          }

        *output = x == toklen && toklen > 2 ? nseel_translate(scctx,tok,toklen) : NULL;
        if (*output) rv=VALUE;
      }
#ifndef NSEEL_EEL1_COMPAT_MODE
      else if (rv == '#' && scctx->onNamedString)
      {
        *output = nseel_translate(scctx,tok,toklen);
        if (*output) rv=STRING_IDENTIFIER;
      }
      else if (rv == '\'')
      {
        if (toklen > 1 && tok[toklen-1] == '\'')
        {
          *output = nseel_translate(scctx, tok, toklen);
          if (*output) rv = VALUE;
        }
        else scctx->gotEndOfInput|=8;
      }
      else if (rv == '\"' && scctx->onString)
      {
        if (toklen > 1 && tok[toklen-1] == '\"')
        {
          *output = (opcodeRec *)nseel_createStringSegmentRec(scctx,tok,toklen);
          if (*output) rv = STRING_LITERAL;
        }
        else scctx->gotEndOfInput|=16;
      }
#endif
      else if (isalpha((unsigned char)rv) || rv == '_')
      {
        char buf[NSEEL_MAX_VARIABLE_NAMELEN*2];
        if (toklen > sizeof(buf) - 1) toklen=sizeof(buf) - 1;
        memcpy(buf,tok,toklen);
        buf[toklen]=0;
        *output = nseel_createCompiledValuePtr(scctx, NULL, buf);
        if (*output) rv = IDENTIFIER;
      }
      else if ((rv >= '0' && rv <= '9') || (rv == '.' && toklen > 1 && tok[1] >= '0' && tok[1] <= '9')) // 123.45 or .45
      {
        int x, pcnt = 0;
        for (x = 0; x < toklen; x ++)
        {
          if (tok[x] == '.' ? (++pcnt > 1) : (tok[x] < '0' || tok[x] > '9'))
          {
            tok += x;
            break;
          }
        }
        *output = x == toklen ? nseel_translate(scctx,tok,toklen) : NULL;
        if (*output) rv=VALUE;
      }
      else if (rv == '$' && toklen > 1)
      {
        if (tok[1] == '~')
        {
          int x;
          for (x = 2; x < toklen; x ++)
            if (tok[x] < '0' || tok[x] > '9')
            {
              tok += x;
              break;
            }

          if (x<toklen || toklen == 2) toklen = 0;
        }

        *output = toklen > 1 ? nseel_translate(scctx,tok,toklen) : NULL;
        if (*output) rv=VALUE;
      }
      else if (rv == '<')
      {
        const char nc=*rdptr;
        if (nc == '<')
        {
          rdptr++;
          rv=TOKEN_SHL;
        }
        else if (nc == '=')
        {
          rdptr++;
          rv=TOKEN_LTE;
        }
      }
      else if (rv == '>')
      {
        const char nc=*rdptr;
        if (nc == '>')
        {
          rdptr++;
          rv=TOKEN_SHR;
        }
        else if (nc == '=')
        {
          rdptr++;
          rv=TOKEN_GTE;
        }
      }
      else if (rv == '&' && *rdptr == '&')
      {
        rdptr++;
        rv = TOKEN_LOGICAL_AND;
      }      
      else if (rv == '|' && *rdptr == '|')
      {
        rdptr++;
        rv = TOKEN_LOGICAL_OR;
      }
      else if (*rdptr == '=')
      {         
        switch (rv)
        {
          case '+': rv=TOKEN_ADD_OP; rdptr++; break;
          case '-': rv=TOKEN_SUB_OP; rdptr++; break;
          case '%': rv=TOKEN_MOD_OP; rdptr++; break;
          case '|': rv=TOKEN_OR_OP;  rdptr++; break;
          case '&': rv=TOKEN_AND_OP; rdptr++; break;
          case '~': rv=TOKEN_XOR_OP; rdptr++; break;
          case '/': rv=TOKEN_DIV_OP; rdptr++; break;
          case '*': rv=TOKEN_MUL_OP; rdptr++; break;
          case '^': rv=TOKEN_POW_OP; rdptr++; break;
          case '!':
            rdptr++;
            if (rdptr < endptr && *rdptr == '=')
            {
              rdptr++;
              rv=TOKEN_NE_EXACT;
            }
            else
              rv=TOKEN_NE;
          break;
          case '=':
            rdptr++;
            if (rdptr < endptr && *rdptr == '=')
            {
              rdptr++;
              rv=TOKEN_EQ_EXACT;
            }
            else
              rv=TOKEN_EQ;
          break;
        }
      }
    }

    scctx->rdbuf = rdptr;
    yylloc_param->first_column = (int)(tok - scctx->rdbuf_start);
    return rv;
  }


  void nseelerror(YYLTYPE *pos,compileContext *ctx, const char *str)
  {
    ctx->errVar=pos->first_column>0?pos->first_column:(int)(ctx->rdbuf_end - ctx->rdbuf_start);
  }


#else

  int nseel_gets(compileContext *ctx, char *buf, size_t sz)
  {
    int n=0;
    const char *endptr = ctx->rdbuf_end;
    const char *rdptr = ctx->rdbuf;
    if (!rdptr) return 0;
    
    while (n < sz && rdptr < endptr) buf[n++] = *rdptr++;
    ctx->rdbuf=rdptr;
    return n;

  }


  //#define EEL_TRACE_LEX

  #ifdef EEL_TRACE_LEX
  #define nseellex nseellex2

  #endif
  #include "lex.nseel.c"

  #ifdef EEL_TRACE_LEX

  #undef nseellex

  int nseellex(YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
  {
    int a=nseellex2(yylval_param,yylloc_param,yyscanner);

    wdl_log("tok: %c (%d)\n",a,a);
    return a;
  }
  #endif//EEL_TRACE_LEX


  void nseelerror(YYLTYPE *pos,compileContext *ctx, const char *str)
  {
    ctx->errVar=pos->first_column>0?pos->first_column:(int)(ctx->rdbuf_end - ctx->rdbuf_start);
  }
#endif // !NSEEL_SUPER_MINIMAL_LEXER
