/*
 *  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 "Token.h"

using namespace Cauchy::Cfd;

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

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

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

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

int Lexer::getNextNonSeparatorChar()
{
  int lastChar = ' ';
  while( not eof() and isspace(lastChar = getNextChar() ) )
  { // 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();
}

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

Cauchy::String Lexer::getIdentifier(int lastChar)
{
  Cauchy::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::getString(int lastChar)
{
  int initial_col = column();
  int previousChar = lastChar;
  Cauchy::String identifierStr = "";
  while( not eof() )
  {
    int nextChar = getNextChar();
    if( nextChar == '"' and previousChar != '\\')
    {
      return Token( Token::IDENTIFIER, 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 == '#' )
  { // Starting a comment
    String comment;
    while (not eof() )
    {
      lastChar = getNextChar();
      if( lastChar != '\n')
      {
        comment += lastChar;
      } else {
        break;
      }
    }
    return nextToken();
  }

  // if it is alpha, it's an identifier or a keyword
  if(isalpha(lastChar) or lastChar == '_')
  {
    identifierStr = getIdentifier(lastChar);
    IDENTIFIER_IS_KEYWORD( "constant", CONSTANT );
    IDENTIFIER_IS_KEYWORD( "function", FUNCTION );
    IDENTIFIER_IS_KEYWORD( "global", GLOBAL );
    IDENTIFIER_IS_KEYWORD( "in", IN );
    IDENTIFIER_IS_KEYWORD( "is", IS );
    IDENTIFIER_IS_KEYWORD( "named", NAMED );
    IDENTIFIER_IS_KEYWORD( "returns", RETURNS );
    IDENTIFIER_IS_KEYWORD( "takes", TAKES );
    IDENTIFIER_IS_KEYWORD( "structure", STRUCTURE );
    IDENTIFIER_IS_KEYWORD( "contains", CONTAINS );
    IDENTIFIER_IS_KEYWORD( "as", AS );
    IDENTIFIER_IS_KEYWORD( "and", AND );
    IDENTIFIER_IS_KEYWORD( "Complex", COMPLEX );
    IDENTIFIER_IS_KEYWORD( "Matrix", MATRIX );
    IDENTIFIER_IS_KEYWORD( "CMatrix", CMATRIX );
    IDENTIFIER_IS_KEYWORD( "Number", NUMBER );
    IDENTIFIER_IS_KEYWORD( "String", STRING );
    IDENTIFIER_IS_KEYWORD( "Variant", VARIANT );
    IDENTIFIER_IS_KEYWORD( "Void", VOID );
    IDENTIFIER_IS_KEYWORD( "FunctionHandle", FUNCTION_HANDLE );
    return Token(Token::IDENTIFIER, identifierStr,line(), initial_col);
  } else if( lastChar == '"' ) {
    return getString(lastChar);
  }
  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);
}

