#include "pch.h"

//////////////////////////////////////////////////////////////////////////////
//
//  Token
//
//////////////////////////////////////////////////////////////////////////////

void TextTokenImpl::SetError(const ZString& strError)
{
    ZError("Parsing error - " + strError);
    m_strError = strError;
}

void TextTokenImpl::SetErrorUnexpected()
{
    if (m_type == Symbol) {
        SetError("Unexpected symbol " + m_string);
    } else if (m_type == String) {
        SetError("Unexpected string " + m_string);
    } else if (m_type == Number) {
        SetError("Unexpected number " + ZString(m_numberValue));
    } else if (m_type == NULL) {
        SetError("Unexpected end of file");
    } else {
        ZString str;
        m_tokenMap.Find(m_type, str);

        SetError("Unexpected symbol " + str);
    }
}

bool TextTokenImpl::Error()
{
    return !m_strError.IsEmpty();
}

//////////////////////////////////////////////////////////////////////////////
//
//  TextTokenImpl
//
//////////////////////////////////////////////////////////////////////////////

const int TextTokenImplNull   = 0;
const int TextTokenImplString = 1;
const int TextTokenImplNumber = 2;
const int TextTokenImplSymbol = 3;

inline bool isDigit(char ch)
{
    return (ch >= '0' && ch <= '9');
}

inline bool isAlpha(char ch) {
    return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
}

inline bool isNumber(char ch)
{
    return isDigit(ch) || ch == '-' || ch == '.';
}

inline bool isSymbol(const char* pcc)
{
    return isAlpha(pcc[0]) || pcc[0] == '_' || pcc[0] == '\'';
}

inline bool IsWhite(const char* pcc)
{
    return
        (
               pcc[0] == ' '
            || pcc[0] == '\t'
            || pcc[0] == '\n'
            || pcc[0] == 0xd
            || pcc[0] == '#'
            || (pcc[0] == '/' && (pcc[1] == '/' || pcc[1] == '*'))
        );
}

//////////////////////////////////////////////////////////////////////////////
//
//  Lexer
//
//////////////////////////////////////////////////////////////////////////////

int ReadHexNumber(const char*& pcc, int max)
{
    int number = 0;

    while (true) {
        if (isDigit(pcc[0])) {
            number = number * 16 + pcc[0] - '0';
        } else if (pcc[0] >= 'a' && pcc[0] <= 'f') {
            number = number * 16 + pcc[0] - 'a' + 10;
        } else if (pcc[0] >= 'A' && pcc[0] <= 'F') {
            number = number * 16 + pcc[0] - 'A' + 10;
        } else {
            return number;
        }

        pcc++;
        if (max != -1) {
            max--;
            if (max == 0) {
                return number;
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////////

int TranslateCharacter(const char*& pcc)
{
    if (pcc[0] == '\\') {
        pcc += 2;
        switch (pcc[-1]) {
            case '\\': return '\\';
            case '\'': return '\'';
            case '"': return '"';
            case 'n': return '\n';
            case 't': return '\t';
            case '0': return 0;
            case 'x': return ReadHexNumber(pcc, 2);

        }
    }

    pcc++;
    return pcc[-1];
}

//////////////////////////////////////////////////////////////////////////////

bool TextTokenImpl::SkipWhite()
{
    while (m_pcc < m_pccEnd && IsWhite(m_pcc)) {
        if (m_pcc[0] == '#') {
            while (m_pcc < m_pccEnd && m_pcc[0] != '\n') m_pcc++;
            if (m_pcc[-1] == '\n') {
                m_line++;
                m_pccLine = m_pcc;
            }
        } else if (m_pcc[0] == '/' && (m_pcc[1] == '/' || m_pcc[1] == '*')) {
            if (m_pcc[1] == '/') {
                while (m_pcc < m_pccEnd && m_pcc[0] != '\n') m_pcc++;
                if (m_pcc[-1] == '\n') {
                    m_line++;
                    m_pccLine = m_pcc;
                }
            } else {
                while (m_pcc < m_pccEnd && (m_pcc[0] != '*' || m_pcc[1] != '/')) {
                    m_pcc++;
                    if (m_pcc[-1] == '\n') {
                        m_line++;
                        m_pccLine = m_pcc;
                    }
                }
                m_pcc+=2;
           }
        } else if (m_pcc[0] == '\n') {
            m_pcc++;
            m_line++;
            m_pccLine = m_pcc;
        } else {
            m_pcc++;
        }
    }
    return true;
}

//////////////////////////////////////////////////////////////////////////////

bool TextTokenImpl::ReadExponent()
{
    m_pcc++;
    float mantisa = m_numberValue;

    if (!ReadNumber(true)) {
        return false;
    }

    m_numberValue = mantisa * pow((float)10, m_numberValue);
    return true;
}

bool TextTokenImpl::ReadNumber(bool bExponent)
{
    float multiplier = 0.1f;
    float sign       = 1;

    m_numberValue = 0;

    if (m_pcc[0] == '-') {
        sign = -1;
        m_pcc++;
    } else if (m_pcc[0] == '+') {
        m_pcc++;
    }

    while (true) {
        if (m_pcc[0] == '.') {
            m_pcc++;
            while (isDigit(m_pcc[0])) {
                m_numberValue += multiplier * (m_pcc[0] - '0');
                m_pcc++;
                multiplier /= 10;
                if ((!bExponent) && (m_pcc[0] == 'e' || m_pcc[0] == 'E')) {
                    m_numberValue *= sign;
                    return ReadExponent();
                }
            }
            m_numberValue *= sign;
            return true;
        } else if ((!bExponent) && (m_pcc[0] == 'e' || m_pcc[0] == 'E')) {
            m_numberValue *= sign;
            return ReadExponent();
        } else if (isDigit(m_pcc[0])) {
            m_numberValue = m_numberValue * 10 + m_pcc[0] - '0';
            m_pcc++;
        } else {
            m_numberValue *= sign;
            return true;
        }
    }
}

bool TextTokenImpl::ReadString(char chEnd)
{
    m_string.SetEmpty();

    //
    // Skip initial quote
    //

    m_pcc++;

    //
    // Read 256 characters at a time
    //

    char  buf[256];
    char* pccDest = buf;

    while (m_pcc < m_pccEnd) {
        //
        // Read 256 characters at a time
        //

        if ((pccDest - buf) == 256) {
            m_string += ZString(buf, 256);
            pccDest = buf;
        } else if (m_pcc[0] == '"') {
            m_string += ZString(buf, pccDest - buf);
            
            //
            // See if there is another string
            //

            m_pcc++;
            SkipWhite();
            if (m_pcc[0] != '\"') {
                return true;
            }

            //
            // Another string
            //

            m_pcc++;
            pccDest = buf;
        } else {
            *pccDest = TranslateCharacter(m_pcc);
            pccDest++;
        }
    }

    return false;
}

bool TextTokenImpl::IsSymbolChar(char ch)
{
    return
           isAlpha(ch)
        || isDigit(ch)
        || ch == '_';
}

bool TextTokenImpl::ReadSymbol()
{
    char buf[1000];
    char* pccDest = buf;

    if (m_pcc[0] == '\'') {
        *pccDest = m_pcc[0];
        pccDest++;
        m_pcc++;
    }

    while (m_pcc < m_pccEnd && IsSymbolChar(m_pcc[0])) {
        *pccDest = m_pcc[0];
        pccDest++;
        m_pcc++;
    }

    *pccDest = 0;
    m_string = ZString(buf);

    int type;

    if (m_symbolMap.Find(m_string, type)) {
        m_type = type;
    } else {
        m_type = Symbol;
    }

    return true;
}

void TextTokenImpl::Next()
{
    m_pccStart = m_pcc;
    m_type     = Null;

    if (m_pcc < m_pccEnd) {
        if (m_pcc[0] == '"') {
            // it's a string

            m_type = String;
            ReadString('"');
        } else if (m_pcc[0] == '0' && m_pcc[1] == 'x') {
            // hex number

            m_pcc += 2;
            PCC pccSave = m_pcc;
            m_numberValue = float(ReadHexNumber(m_pcc));
            if (m_pcc != pccSave) {
                m_type = Number;
            }
        } else if (
               (m_pcc[0] >= '0' && m_pcc[0] <= '9')
            || (m_pcc[0] == '-' && m_pcc[1] >= '0' && m_pcc[1] <= '9')
            || m_pcc[0] == '.'
        ) {
            // it's a number

            m_type = Number;
            ReadNumber();
        } else if (m_pcc[0] == '\'') {
            // it's a character

            m_pcc++;
            m_type = Number;
            m_numberValue = (float)TranslateCharacter(m_pcc);
            ZAssert(m_pcc[0] == '\'');
            m_pcc++;
        } else if (isSymbol(m_pcc)) {
            // it's a symbol

            ReadSymbol();
        } else {
            m_type = ParseToken(m_pcc);
        }
        SkipWhite();
    }
}

int TextTokenImpl::AddSymbol(const ZString& str)
{
    m_symbolMap.Set(str, m_idFree);
    return m_idFree++;
}

int TextTokenImpl::AddToken(const ZString& str)
{
    m_tokenMap.Set(m_idFree, str);
    return m_idFree++;
}

int TextTokenImpl::ParseToken(PCC& pcc)
{
    return 0;
}

TextTokenImpl::TextTokenImpl(PCC pcc, int length) :
    m_pcc(pcc),
    m_pccEnd(pcc + length),
    m_pccExpressionStart(pcc),
    m_pccLine(pcc),
    m_line(1),
    m_string(ZString()),
    m_idFree(0),
    Null(0),
    String(0),
    Number(0),
    Symbol(0)
{
    (*(int*)&Null)   = AddToken("end of text");
    (*(int*)&String) = AddToken("string");
    (*(int*)&Number) = AddSymbol("number");
    (*(int*)&Symbol) = AddSymbol("symbol");

    SkipWhite();
}

TextTokenImpl::~TextTokenImpl()
{
}

bool TextTokenImpl::MoreTokens()
{
    return m_type != Null;
}

int TextTokenImpl::GetLineNumber()
{
    return m_line;
}

int TextTokenImpl::GetLineCharNumber()
{
    return m_pcc - m_pccLine + 1;
}

int TextTokenImpl::GetCharNumber()
{
    return m_pcc - m_pccExpressionStart + 1;
}

bool TextTokenImpl::IsSymbol(ZString& str, bool bError)

{
    if (m_type == Symbol) {
        str = m_string;
        Next();
        return true;
    } else if (bError) {
        SetError("Symbol expected");
    }

    return false;
}

bool TextTokenImpl::IsString(ZString& str, bool bError)
{
    if (m_type == String) {
        str = m_string;
        Next();
        return true;
    } else if (bError) {
        SetError("String expected");
    }

    return false;
}

bool TextTokenImpl::IsNumber(float& number, bool bError)
{
    if (m_type == Number) {
        number = m_numberValue;
        Next();
        return true;
    } else if (bError) {
        SetError("Number expected");
    }

    return false;
}

bool TextTokenImpl::Is(int id, bool bError)
{
    if (m_type == id) {
        Next();
        return true;
    } else if (bError) {
        ZString str;
        m_tokenMap.Find(m_type, str);

        SetError("Unexpected " + str);
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////////
//
//  CommandLineToken
//
//////////////////////////////////////////////////////////////////////////////

CommandLineToken::CommandLineToken(PCC& pcc, int length) :
    m_pcc(pcc),
    m_pccEnd(pcc + length)
{
}

bool CommandLineToken::MoreTokens()
{
    return m_pcc != m_pccEnd;
}

bool CommandLineToken::IsMinus(ZString& str)
{
    if (m_pcc[0] == '-') {
        m_pcc++;
        return IsString(str);
    }

    return false;
}

void CommandLineToken::SkipWhite()
{
   while (m_pcc < m_pccEnd && m_pcc[0] == ' ') {
       m_pcc++;
   }
}

bool CommandLineToken::IsString(ZString& str)
{
    PCC pccStart = m_pcc;

    if (m_pcc[0] == '"') {
        m_pcc++;
        while (m_pcc < m_pccEnd && m_pcc[0] != '"') {
            m_pcc++;
        }

        if (m_pcc < m_pccEnd) {
            m_pcc++;
            str = ZString(pccStart + 1, m_pcc - pccStart - 2);
        } else {
            str = ZString(pccStart + 1, m_pcc - pccStart - 1);
        }
    } else {
        while (m_pcc < m_pccEnd && m_pcc[0] != ' ') {
            m_pcc++;
        }

        if (m_pcc - pccStart != 0) {
            str = ZString(pccStart, m_pcc - pccStart);
        } else {
            return false;
        }
    }

    SkipWhite();

    return true;
}

bool CommandLineToken::ReadExponent(float& number)
{
    m_pcc++;
    float mantisa = number;
    float exponent;

    if (!ReadNumber(exponent, true)) {
        return false;
    }

    number = mantisa * pow((float)10, number);
    return true;
}

bool CommandLineToken::ReadNumber(float& number, bool bExponent)
{
    float multiplier = 0.1f;
    float sign       = 1;

    number = 0;

    if (m_pcc[0] == '-') {
        sign = -1;
        m_pcc++;
    } else if (m_pcc[0] == '+') {
        m_pcc++;
    }

    while (true) {
        if (m_pcc[0] == '.') {
            m_pcc++;
            while (isDigit(m_pcc[0])) {
                number += multiplier * (m_pcc[0] - '0');
                m_pcc++;
                multiplier /= 10;
                if ((!bExponent) && (m_pcc[0] == 'e' || m_pcc[0] == 'E')) {
                    number *= sign;
                    return ReadExponent(number);
                }
            }
            number *= sign;
            return true;
        } else if ((!bExponent) && (m_pcc[0] == 'e' || m_pcc[0] == 'E')) {
            number *= sign;
            return ReadExponent(number);
        } else if (isDigit(m_pcc[0])) {
            number = number * 10 + m_pcc[0] - '0';
            m_pcc++;
        } else {
            number *= sign;
            return true;
        }
    }
}

bool CommandLineToken::IsNumber(float& number)
{
    if (
           (m_pcc[0] >= '0' && m_pcc[0] <= '9')
        || (m_pcc[0] == '-' && m_pcc[1] >= '0' && m_pcc[1] <= '9')
        || m_pcc[0] == '.'
    ) {
        // it's a number

        ReadNumber(number, false);
        SkipWhite();
        return true;
    }

    return false;
}

void CommandLineToken::Skip()
{
    ZString str;

    if (!IsMinus(str)) {
        if (!IsString(str)) {
            float num;
            IsNumber(num);
        }
    }
}