
#include <fstream.h>
#include <stdio.h>
#include <string.h>

#include <base/binding.h>
#include <base/string.h>

#include "Intrprtr.h"

#ifdef __GNUC__
template class CL_Binding0<CL_Interpreter>;
#endif

CL_Interpreter::CL_Interpreter(ifstream& stream, bool caseSensitive /* = FALSE*/ )
// [1.0.0]
{
    _stream = &stream;
    _caseSensitive = caseSensitive;
    
    _eolComment = CL_String("#"); // default comment delimiter
    
    // We are dependent on the comment delimiter strings
    CL_Binding0<CL_Interpreter> bind(this, _CommentDelimitersChanged);
    _eolComment.AddDependent(bind);
    _commentBegin.AddDependent(bind);
    _commentEnd.AddDependent(bind);
}


bool CL_Interpreter::ReadKeyWord(const CL_String& keyWord)
    throw(CL_InterpreterException)
// [1.0.0]
{
    _Check(__PRETTY_FUNCTION__);
    CL_String readWord;
    
    streampos pos = _stream->tellg();
    readWord = ReadWord();
    if(_caseSensitive)
    {
        if(keyWord == readWord) return TRUE;
    }
    else
    {
        if(keyWord.CompareIgnoringCase(readWord)) return TRUE;
    }
    
    // We arrive here if the keyword is not the one given
    _stream->seekg(pos);
    return FALSE;
}


CL_String CL_Interpreter::ReadStringBetweenQuotes()
    throw(CL_InterpreterException)
{
    int  c;			// A character read from the file
    char fAdd = 0; // Tells whether we must add the character to the string
    int  fOk = 0;	// Tells whether the closing '"' has been encountered
    
    _Check(__PRETTY_FUNCTION__);
    CL_String readString;
    
    while(!fOk && (c = _NextChar()) != EOF)
    {
        if(c == '\"')
        {
            if(fAdd) { fOk = 1;	fAdd = 0; } // it is the closing quote
            else fAdd = 1; // it is the opening quote
            continue;
        }
        
        if(c == '\n') continue;
        
        if(fAdd) readString.Append(c);
    }
    
    if(fAdd) // we have reached EOF before the string was terminated
	throw CL_InterpreterException(
            "End-of-file reached before closing quote.  Syntax error in file ?",
            __PRETTY_FUNCTION__,
            (const char *) readString);
    
    return readString;
}


unsigned long CL_Interpreter::ReadNumber(Base base)
    throw(CL_InterpreterException)
// [1.0.0]
{
    int c = 0;
    int	fOk = 0;
    int	fEmpty = 1;  // Tells whether the string is empty
    char number[33];	// In binary (base 2), at most 32 digits.
    unsigned long r = 0L;
    int i = 0;
    
    _Check(__PRETTY_FUNCTION__);
    
    while(!fOk && (c = _NextChar()) != EOF)
    {
        switch(c)
        {
        case 'F' : case 'f' :
        case 'E' : case 'e' :
        case 'D' : case 'd' :
        case 'C' : case 'c' :
        case 'B' : case 'b' :
        case 'A' : case 'a' : if(base < RN_HEX) goto error;
        case '9' : if(base < RN_DEC) goto error;
        case '8' :
        case '7' : if(base < RN_OCT) goto error;
        case '6' :
        case '5' :
        case '4' :
        case '3' :
        case '2' : if(base < RN_BIN) goto error;
        case '1' :
        case '0' : if(i < 32) { number[i++] = (char) c; fEmpty = 0; } break;
        default : if(!fEmpty) fOk = 1; break;
        }
    }
    
    number[i] = '\0';
    switch(base)
    {
    case RN_HEX : sscanf(number, "%lX", &r); break;
    case RN_DEC : sscanf(number, "%lu", &r); break;
    default: break; /* this is to make GNU C++ happy */
    }
    return r;
    
    error :
	CL_String info;
    info.AssignWithFormat("Character %c encountered in base-%d number",
                          c, base);
    throw CL_InterpreterException(
        "Unrecognised character encountered",
        __PRETTY_FUNCTION__,
        info);
}


CL_String CL_Interpreter::ReadWord()
    throw(CL_InterpreterException)
// [1.0.0]
{
    int c;
    char fEmpty = 1;
    char fPunct = 0;	// true if the word consists of punctuations.
    
    _Check(__PRETTY_FUNCTION__);
    CL_String readWord;
    
    while((c = _NextChar()) != EOF)
    {
        switch(c)
        {
        case '\n' :
        case ' ' :
        case '\t' :
            if(!fEmpty)
            {
                _stream->putback(c);
                return readWord;
            }
            break;
            
        case ',' :
        case ';' :
        case ':' :
        case '.' :
        case '(' :
        case ')' :
        case '{' :
        case '}' :
        case '[' :
        case ']' :
        case '!' :
        case '?' :
        case '$' :
        case '+' :
        case '-' :
        case '*' :
        case '/' :
        case '=' :
        case '|' :
        case '~' :
            if(!fEmpty && !fPunct)
            {
                _stream->putback(c);
                return readWord;
            }
            else // the word will consist solely of punctuators
            {
                readWord.Append(c); fEmpty = 0; fPunct = 1;
            }
            break;
            
        default :
            if(!fPunct) { readWord.Append(c); fEmpty = 0; }
            else { _stream->putback(c); return readWord; }
            break;
        }
    }
    return readWord;
}

char CL_Interpreter::NextPunctuation(char punct)
    throw(CL_InterpreterException)
// [1.0.0]
{
    int c;
    
    _Check(__PRETTY_FUNCTION__);
    
    while((c = _NextChar()) != EOF)
    {
        switch(c)
        {
        case '\n' :
        case ' ' :
        case '\t' :
            break;	// read next char
            
        case ',' :
        case ';' :
        case ':' :
        case '.' :
        case '(' :
        case ')' :
        case '{' :
        case '}' :
        case '[' :
        case ']' :
        case '!' :
        case '?' :
        case '$' :
        case '+' :
        case '-' :
        case '*' :
        case '/' :
        case '=' :
        case '|' :
        case '~' :
            if(punct == ' ' || punct == c) return c;
            
        default:
            _stream->putback(c);
            return 0;
        }
    }
}

void CL_Interpreter::_Check(const char *funcName) const
    throw(CL_InterpreterException)
// [1.0.0]
{
    if(_stream == NULL)
        throw CL_InterpreterException("No stream has been specified.", funcName, "");
    if(_stream->bad())
        throw CL_InterpreterException("The file is bad.", funcName, "");
}

bool CL_Interpreter::_CommentDelimitersChanged()
    throw(CL_InterpreterException)
// [1.0.0]
{
    _eolCommentsSupported = (_eolComment.Length() != 0);
    _delimCommentsSupported = (_commentBegin.Length() != 0 && _commentEnd.Length() != 0);
    if(_eolCommentsSupported && _delimCommentsSupported)
    {
        // Let's see if _eolComment is the same as _commentBegin
        if((_caseSensitive && _eolComment == _commentBegin) ||
           (!_caseSensitive && _eolComment.CompareIgnoringCase(_commentBegin)))
        {
            throw CL_InterpreterException(
                "End-of-line comment delimiter and beginning of comment delimiter are the same",
                __PRETTY_FUNCTION__,
                (const char *) (CL_String("Delimiters are ") + _eolComment +
                                " and " + _commentBegin));
        }
    }
    return TRUE;
}

int CL_Interpreter::_NextChar() const
    throw(CL_InterpreterException)
// [1.0.0]
{
    int c, d;
    
beginning:	// we'll come back here after one comment is processed
    
    c = _stream->get();
    if(c == EOF) return c;
    if(c == '\r') return ' ';	// this is for Unix machines who read CR-LF files
    if(c == '') return ' ';	// this character is used to prettyprint only; ignore it
    
    if(_eolCommentsSupported)
    {
        if(c == _eolComment[0]) // maybe we're at the beginning of an eol comment
        {
            bool isEolComment = TRUE;
            for(long i = 1; i < _eolComment.Length(); i++)
            {
                d = _stream->peek();
                if (d != _eolComment[i]) { isEolComment = FALSE; break; }
            }
            
            if(isEolComment)	// yes, we are indeed at an eol comment
            {
                // ignore characters until '\n' or EOF is found
                for(c = _stream->get(); c != '\n' && c != EOF; c = _stream->get());
                return '\n';	// return the end-of-line character.
            }
            else return c; // not an eol comment, return the character
        }
    }	/* _eolCommentSupported */
    
    if(_delimCommentsSupported)
    {
        if(c == _commentBegin[0])	// maybe we're in a delimited comment
        {
            bool isDelimComment = TRUE;
            for(long i = 1; i < _commentBegin.Length(); i++)
            {
                d = _stream->peek();
                if (d != _commentBegin[i]) { isDelimComment = FALSE; break; }
            }
            
            if(isDelimComment)  // yes, we are indeed in a delimited comment
            {
                // ignore the characters that make up the delimiter...
                _stream->ignore(_commentBegin.Length() - 1, EOF);
                
                // ... then ignore all characters until the end-of-comment is
                // found.
                bool endOfCommentFound = FALSE;
                while(!endOfCommentFound && c != EOF)
                {
                    c = _stream->get();
                    if(c == _commentEnd[0]) // maybe we're at the end of comment
                    {
                        bool isCommentEnd = TRUE;
                        for(long i = 1; i < _commentEnd.Length(); i++)
                        {
                            d = _stream->peek();
                            if(d != _commentEnd[i]) { isCommentEnd = FALSE; break; }
                        }
                        
                        if(isCommentEnd) // yes indeed
                        {
                            _stream->ignore(_commentEnd.Length() - 1, EOF);
                            goto beginning;
                        }	/* end of comment ! */
                    }	/* end of comment ? */
                }	/* ignore the comment */
            }	/* in a delimited comment ! */
            else return c; // not the beginning of a delim comment : return c.
        }	/* in a delimited comment ? */
    }	/* _delimCommentSupported */
    
    return c;
}


// EOF
