/*
 *  Copyright (c) 2008,2010 Cyrille Berger <cberger@cberger.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * either version 2, or (at your option) any later version of the License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "Lexer.h"

#include "Debug.h"
#include "String.h"
#include "Token_p.h"

using namespace Cauchy;

#define IDENTIFIER_IS_KEYWORD( tokenname, tokenid) \
  if( identifierStr == tokenname) \
  { \
    return Cauchy::Token(Cauchy::Token::tokenid, line(), initial_col); \
  }

#define CHAR_IS_TOKEN( tokenchar, tokenid ) \
  if( lastChar == tokenchar ) \
  { \
    return Cauchy::Token(Cauchy::Token::tokenid, line(), initial_col); \
  }

#define CHAR_IS_TOKEN_OR_TOKEN( tokenchar, tokendecidechar, tokenid_1, tokenid_2 ) \
  if( lastChar == tokenchar  ) \
  { \
    if( getNextChar() == tokendecidechar ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_2, line(), initial_col); \
    } else { \
      unget(); \
      return Cauchy::Token(Cauchy::Token::tokenid_1, line(), initial_col); \
    } \
  }

#define CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN( tokenchar_1, tokenchar_2, tokenchar_3, tokenid_1, tokenid_2, tokenid_3 ) \
  if( lastChar == tokenchar_1 ) \
  { \
    int nextChar = getNextChar(); \
    if( nextChar == tokenchar_2 ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_2, line(), initial_col); \
    } else if( nextChar  == tokenchar_3 ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_3, line(), initial_col); \
    } else { \
      unget(); \
      return Cauchy::Token(Cauchy::Token::tokenid_1, line(), initial_col); \
    } \
  }

#define CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN_OR_TOKEN( tokenchar_1, tokenchar_2, tokenchar_3, tokenchar_4, tokenid_1, tokenid_2, tokenid_3, tokenid_4 ) \
  if( lastChar == tokenchar_1 ) \
  { \
    int nextChar = getNextChar(); \
    if( nextChar == tokenchar_2 ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_2, line(), initial_col); \
    } else if( nextChar  == tokenchar_3 ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_3, line(), initial_col); \
    } else if( nextChar  == tokenchar_4 ) \
    { \
      return Cauchy::Token(Cauchy::Token::tokenid_4, line(), initial_col); \
    } else { \
      unget(); \
      return Cauchy::Token(Cauchy::Token::tokenid_1, line(), initial_col); \
    } \
  }

struct Lexer::Private {
  std::istream* stream;
  int col;
  int line;
  int followingnewline;
  bool useOctaveExtensions;
};

Lexer::Lexer(std::istream* sstream) : d(new Private)
{
  d->col = 1;
  d->line = 1;
  d->followingnewline = 1;
  d->stream = sstream;
  d->useOctaveExtensions = true;
}

Lexer::~Lexer()
{
  delete d;
}

int Lexer::getNextNonSeparatorChar()
{
  int lastChar = ' ';
  while( not eof() and isspace(lastChar = getNextChar() ) and lastChar != '\n' )
  { // Ignore space
  }
  return lastChar;
}

int Lexer::getNextChar()
{
  int nc = d->stream->get();
  if( nc == '\n' )
  {
    ++d->line;
    ++d->followingnewline;
    d->col = 1;
  } else {
    ++d->col;
    d->followingnewline = 0;
  }
  return nc;
}

void Lexer::unget()
{
  --d->col;
  d->stream->unget();
  if( d->followingnewline > 0 )
  {
    --d->followingnewline;
    --d->line;
  }
}

bool Lexer::eof() const
{
  return d->stream->eof() or d->stream->fail() or d->stream->bad();
}

int Lexer::line() const
{
  return d->line;
}
int Lexer::column() const
{
  return d->col;
}

String Lexer::getIdentifier(int lastChar)
{
  String identifierStr;
  if( lastChar != 0) {
    identifierStr = lastChar;
  }
  while (not eof() )
  {
    lastChar = getNextChar();
    if( isalnum(lastChar) or lastChar == '_')
    {
      identifierStr += lastChar;
    } else {
      unget();
      break;
    }
  }
  return identifierStr;
}

Token Lexer::getDigit(int lastChar)
{
  int initial_col = column();
  String identifierStr;
  identifierStr= lastChar;
  bool integer = true;
  bool complex = false;
  bool exponent = false;
  bool rightAfterExponent = false;
  while (not eof()
         and ( isdigit((lastChar = getNextChar()))
               or lastChar == '.' or lastChar == 'i' or (lastChar == 'e' and not exponent )
               or (rightAfterExponent and lastChar =='-' )
               or (rightAfterExponent and lastChar =='+' ) ) )
  {
    rightAfterExponent = false;
    identifierStr += lastChar;
    if( lastChar == '.' )
    {
      integer = false;
    }
    if( lastChar == 'e' )
    {
      integer = false;
      exponent = true;
      rightAfterExponent = true;
    }
    if( lastChar == 'i' )
    {
      complex = true;
      break;
    }
  }
  if(complex)
  {
    return Token( Token::COMPLEX_CONSTANT, identifierStr, line(), initial_col );
  } else if(integer)
  {
    unget();
    return Token( Token::INTEGER_CONSTANT, identifierStr, line(), initial_col );
  } else {
    unget();
    return Token( Token::FLOAT_CONSTANT, identifierStr, line(), initial_col );
  }
}

Token Lexer::getString(int lastChar)
{
  int initial_col = column();
  int previousChar = lastChar;
  String identifierStr = "";
  while( not eof() )
  {
    int nextChar = getNextChar();
    if( nextChar == '"' and previousChar != '\\')
    {
      return Token( Token::STRING_CONSTANT, identifierStr, line(), initial_col );
    } else {
      previousChar = nextChar;
      identifierStr += nextChar;
    }
  }
  return Token( Token::UNFINISHED_STRING, line(), initial_col);
}

Token Lexer::nextToken()
{
  int lastChar = getNextNonSeparatorChar();
  int initial_line = line() - 1;
  int initial_col = column() - 1;
  if( eof() ) return Token(Token::END_OF_FILE, line(), initial_col);
  String identifierStr;
  // Test for comment
  Token commentToken;
  
  if( lastChar == '%' or (lastChar == '#' && d->useOctaveExtensions) )
  { // Starting a comment
    String comment;
    while (not eof() )
    {
      lastChar = getNextChar();
      if( lastChar != '\n')
      {
        comment += lastChar;
      } else {
        break;
      }
    }
    return Token(Token::COMMENT, comment, line() - 1, initial_col);
  }

  // if it is alpha, it's an identifier or a keyword
  if(isalpha(lastChar) or lastChar == '_')
  {
    identifierStr = getIdentifier(lastChar);
    IDENTIFIER_IS_KEYWORD( "and", AND );
    IDENTIFIER_IS_KEYWORD( "or", OR );
    IDENTIFIER_IS_KEYWORD( "not", NOT );
    IDENTIFIER_IS_KEYWORD( "else", ELSE );
    IDENTIFIER_IS_KEYWORD( "for", FOR );
    IDENTIFIER_IS_KEYWORD( "if", IF );
    IDENTIFIER_IS_KEYWORD( "elseif", ELSEIF );
    IDENTIFIER_IS_KEYWORD( "return", RETURN );
    IDENTIFIER_IS_KEYWORD( "while", WHILE );
    IDENTIFIER_IS_KEYWORD( "break", BREAK );
    IDENTIFIER_IS_KEYWORD( "end", END );
    IDENTIFIER_IS_KEYWORD( "function", FUNCTION );
    IDENTIFIER_IS_KEYWORD( "global", GLOBAL );
    if(d->useOctaveExtensions)
    {
      // Octave "extensions"
      IDENTIFIER_IS_KEYWORD( "endfunction", END );
      IDENTIFIER_IS_KEYWORD( "endif", END );
      IDENTIFIER_IS_KEYWORD( "endwhile", END );
      IDENTIFIER_IS_KEYWORD( "endfor", END );
    }
    return Token(Token::IDENTIFIER, identifierStr,line(), initial_col);
  } else if( isdigit(lastChar) )
  { // if it's a digit 
    return getDigit(lastChar);
  } else if( lastChar == '"' ) {
    return getString(lastChar);
  } else {
    CHAR_IS_TOKEN(';', SEMI );
    CHAR_IS_TOKEN( '\n', END_OF_LINE );
    CHAR_IS_TOKEN( ',', COMA );
    if( lastChar == '.' )
    {
      lastChar = getNextChar();
      
      CHAR_IS_TOKEN_OR_TOKEN( '+', '=', DOTPLUS, DOTPLUSEQUAL );
      CHAR_IS_TOKEN_OR_TOKEN( '-', '=', DOTMINUS, DOTMINUSEQUAL );
      CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN( '*', '=', '*', DOTMULTIPLY, DOTMULTIPLYEQUAL, DOTPOWER );
      CHAR_IS_TOKEN_OR_TOKEN( '/', '=', DOTDIVIDE, DOTDIVIDEEQUAL);
      CHAR_IS_TOKEN( '^', DOTPOWER );
      unget();
      return Cauchy::Token(Cauchy::Token::DOT, line(), initial_col);
    }
    CHAR_IS_TOKEN( '@', AROBASE );
    CHAR_IS_TOKEN( ':', COLON );
    CHAR_IS_TOKEN( '\'', TRANSPOSE );
    CHAR_IS_TOKEN( '{', STARTBRACE );
    CHAR_IS_TOKEN( '}', ENDBRACE );
    CHAR_IS_TOKEN( '(', STARTBRACKET );
    CHAR_IS_TOKEN( ')', ENDBRACKET );
    CHAR_IS_TOKEN( '[', STARTBOXBRACKET );
    CHAR_IS_TOKEN( ']', ENDBOXBRACKET );
    CHAR_IS_TOKEN_OR_TOKEN( '=', '=', EQUAL, EQUALEQUAL );
    CHAR_IS_TOKEN_OR_TOKEN( '~', '=', NOT, DIFFERENT );
    CHAR_IS_TOKEN_OR_TOKEN( '&', '&', AND, AND);
    CHAR_IS_TOKEN_OR_TOKEN( '|', '|', OR, OR );
    CHAR_IS_TOKEN( '^', POWER );
    CHAR_IS_TOKEN_OR_TOKEN( '<', '=', INFERIOR, INFERIOREQUAL );
    CHAR_IS_TOKEN_OR_TOKEN( '>', '=', SUPPERIOR, SUPPERIOREQUAL );
    CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN_OR_TOKEN( '+', '+', '=', ' ', PLUSSMTHG, PLUSPLUS, PLUSEQUAL, PLUS );
    CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN_OR_TOKEN( '-', '-', '=', ' ', MINUSSMTHG, MINUSMINUS, MINUSEQUAL, MINUS );
    CHAR_IS_TOKEN_OR_TOKEN_OR_TOKEN( '*', '=', '*', MULTIPLY, MULTIPLYEQUAL, POWER);
    CHAR_IS_TOKEN_OR_TOKEN( '/', '=', DIVIDE, DIVIDEEQUAL);
    CHAR_IS_TOKEN( '~', TILDE );
  }
  if( lastChar > 128 ) return nextToken();
  identifierStr = lastChar;
  CAUCHY_DEBUG("Unknown token : " << lastChar << " '" << identifierStr << "' at " << initial_line << "," << initial_col);
  CAUCHY_ASSERT( not isspace(lastChar));
  return Token(Token::UNKNOWN, initial_line, initial_col);
}
