/*

IMASM Macro Precompiler

Copyright (C) 2003  Joe Fisher, Shiny Technologies, LLC
http://www.shinytechnologies.com
Portions Copyright (C) 2003  Joseph Zbiciak.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/


#include "includes.h"
#include <cstdarg>

using namespace std;

#define NUL '\0'

#if defined(WIN32X) || !defined(WIN32)

int stricmp_(const char *str1, const char *str2)
{
    while (tolower(*str1) == tolower(*str2))
    {
        if (*str1 == NUL)
        {
            return 0;
        }

        str1++;
        str2++;
    }

    if (tolower(*str1) > tolower(*str2))
    {
        return 1;
    }

    return -1;
}

#else

int stricmp_(const char *str1, const char *str2)
{
    return stricmp(str1, str2);
}

#endif

Parser::~Parser()
{
}


int Parser::ParseSourceFile()
{
    string s;
    bool Ignore;

    while (inputFIFO->getLine(s, Ignore))
    {
        StripReturn(s);

        if (Ignore)
        {
            PutString(s, true, false);
            continue;
        } 

        if (ParseLine(s) == -1)
            return 0;
    }

    return 1;
}

int Parser::ParseUntilOutput()
{
    string s;
    bool Ignore;

    while (outputFIFO->isEmpty() &&
           inputFIFO->getLine(s, Ignore))
    {
        StripReturn(s);

        if (Ignore)
        {
            PutString(s, true, false);
            continue;
        } 

        if (ParseLine(s) == -1)
            return 0;
    }

    return 1;
}

void Parser::PutString(string &s, bool addNL, bool Ignore)
{
    string sTmp = s;
    string sSub;
    string::size_type crPos;
    string::size_type nlPos;

    if (addNL) sTmp += "\n";

    // Nuke any CRs we find (from straggling CR+LF pairs)
    while ((crPos = sTmp.find('\r')) != string::npos)
        sTmp.erase(crPos, 1);

    // Now chop into newline-terminated substrings 
    while ((nlPos = sTmp.find('\n')) != string::npos)
    {
        sSub = sTmp.substr(0, nlPos + 1);
        outputFIFO->putLine(sSub.c_str(), Ignore);

        sTmp.erase(0, nlPos + 1);
    }

    if (sTmp.length() > 0)
        outputFIFO->putLine(sTmp.c_str(), Ignore);
}

void Parser::PutStringAsCmt(string &s, bool addNL, bool Ignore)
{
    string sTmp = s;
    string sSub;
    string::size_type crPos;
    string::size_type nlPos;

    if (addNL) sTmp += "\n";

    // Nuke any CRs we find (from straggling CR+LF pairs)
    while ((crPos = sTmp.find('\r')) != string::npos)
        sTmp.erase(crPos, 1);

    // Now chop into newline-terminated substrings 
    while ((nlPos = sTmp.find('\n')) != string::npos)
    {
        sSub = sTmp.substr(0, nlPos + 1);

        // Convert original line into a comment.
        // If the leading character is a space, replace it.  Otherwise, if
        // it's anything else (including a TAB), insert in front of it.
        if (sSub.length() > 0 && sSub[0] == ' ')
            sSub.replace(0,1, ";");
        else    
            sSub.insert(0, ";");

        outputFIFO->putLine(sSub.c_str(), Ignore);

        sTmp.erase(0, nlPos + 1);
    }

    if (sTmp.length() > 0)
        outputFIFO->putLine(sTmp.c_str(), Ignore);
}

int Parser::ParseLine(string &sLine)
{
    Token lineToks(", \t()\r\n");
    bool bHandled = false;
    string sOrig = sLine;
    int iRet;

    // Massacre any comments to keep them from mucking up the works
    string::size_type iPos = sLine.find(";");
    if (iPos != string::npos)
        sLine.substr(0, iPos);
    
    // Now, through the magic of operator overloading, tokenize the line.
    lineToks = sLine;

    // If we found any tokens, see if any of them are meaningful.
    if (lineToks.GetNumTokens() > 0)
    {
        if (stricmp_(lineToks[0], "MACRO") == 0) // macro definition
        {
            macro m;
            bool redef_err = false;

            // Output original line as a comment
            PutString(sOrig, true, true);

            if (GetMacroPtr(lineToks[1]) != NULL)
                redef_err = true;

            m.sName = lineToks[1];


            if (ReadMacro(&m, sLine) == -1)
                return -1;

            if (!redef_err)
                m_macroList.AddNode(m);
            else
                ThrowError("MACRO '%s' redefined!", lineToks[1]);

            bHandled = true;
        }
        else // Test to see if we have a macro match
        {
            string sMacro;

            iRet = FindMacros(sLine, sMacro);

            if (iRet == -1)
            {
                return -1;
            }
            else if (iRet == 1)
            {
                // Found a macro, so output the original line as a comment
                // followed by the expanded line.  
                PutStringAsCmt(sOrig, true, false);
                PutString(sMacro);

                bHandled = true;
            }
            // else if iRet == 0, pass the line through.
        }
        
    }

    if (!bHandled)
    {
        PutString(sOrig);
    }
        
    return 1;
}

int Parser::ReadMacro(macro *pMacro, string &sLine)
{
    string s, sTemp;
    Token t(" \t\r\n");
    bool ok;
    bool Ignore;
    
    pMacro->iType = mUNKNOWN;

    s = sLine;

    if (GetMacroArgs(pMacro, s) == -1)
    {
        return -1;
    }

    s.erase();

    // Read the first line of the macro
    do {
        ok = inputFIFO->getLine(s, Ignore);
        if (Ignore) 
            PutString(s, false, false);
    } while (ok && Ignore);
    if (!ok && inputFIFO->isEOF())
    {
        ThrowError("End of file encountered while reading macro %s", 
                   pMacro->sName.c_str());
        return -1;
    }
    PutString(s, false, true);
        

    // Strip leading whitespace and trailing return character.
    string::size_type iPos = 0, len = s.length();
    while (iPos < len && isspace(s[iPos]))
        iPos++;
    s.erase(0, iPos);

    StripReturn(s);

    t = s;

    if (t.GetNumTokens() == 0 || stricmp_(t[0], "ENDM") != 0)
    {           
        pMacro->codeList.AddNode(s);        
    }
    else 
    {
        ThrowWarning("Empty macro %s defined.", pMacro->sName.c_str());
        return 1;
    }
    

    // Read the rest of the macro
    while (1)
    {
        do {
            ok = inputFIFO->getLine(s, Ignore);
            if (Ignore) 
                PutString(s, false, false);
        } while (ok && Ignore);

        if (!ok && inputFIFO->isEOF())
        {
            ThrowError("End of file encountered while reading macro %s", 
                       pMacro->sName.c_str());
            return -1;
        }       

        PutString(s, false, true);
        StripReturn(s);
        t = s;

        if (t.GetNumTokens() > 0)
        {           
            if (stricmp_(t[0], "ENDM") != 0)
            {           
                pMacro->codeList.AddNode(s);        
            }
            else
            {
                break;
            }
        }
    }

    return 1;
}

int Parser::ThrowError(char *format, ...)
{
    char msg_buf[1024], loc_buf[16];
    va_list args;
    va_start(args, format);
    vsprintf(msg_buf, format, args);

    const StringFIFO::loc *inLoc = inputFIFO->getLocation();
    sprintf(loc_buf, ":%d: ", inLoc->lineNo);

    string msg = string(inLoc->fname) + loc_buf + "ERROR - " + msg_buf;

    throw ParseError(msg);
    
    return 1;
}

int Parser::ThrowWarning(char *format, ...)
{
    char msg_buf[256], loc_buf[16];
    va_list args;
    va_start(args, format);
    vsprintf(msg_buf, format, args);

    const StringFIFO::loc *inLoc = inputFIFO->getLocation();
    sprintf(loc_buf, ":%d: ", inLoc->lineNo);
    cerr << inLoc->fname << loc_buf << "WARNING - " << msg_buf << endl;

    return 1;
}

int Parser::GetMacroString(macro *pMacro, string &sLine, string &sOut)
{
    int iRet = 0;
    string sMacName;
    sMacName = pMacro->sName;

    string::size_type iPos = sLine.find(sMacName);
    
    if (iPos != string::npos)
    {
        int iParen = 0;

        char c;

        while (iPos < sLine.length())
        {
            c = sLine[iPos];
            sOut += c;

            if (pMacro->iType == mPAREN)
            {
                if (c == '(')
                {
                    iParen++;
                }
                else if (c == ')')
                {
                    iParen--;

                    if (iParen == 0)
                    {
                        break;
                    }
                }
            }

            iPos++;
        }

        if (iParen == 0)
        {
            iRet = 1;
        }
        else
        {
            ThrowError("Mismatched parenthesis in macro: %s", sMacName.c_str());
        }
    } 

    // Eat trailing whitespace
    iPos = sOut.length();

    if (iPos > 0)
    {
        while (iPos > 0 && isspace(sOut[--iPos]))
            ;

        sOut = sOut.substr(0, iPos+1);
    }

    return iRet;    
}

// Macros should be found and parsed in a left to right order
int Parser::FindMacros(string &sLine, string &sOut)
{
    int iRet = 0, i, iNumToks;
    Token t(" \t,()\r\n#$");
    macro *pMac = NULL;
    string sSub;
    string::size_type iPos;
    int total_iters = 0;

    string sOrig = sLine, sTmpStr = sLine, sCurStr, sCmtStr;

    bool bChanged;

    sOut.erase();

    do
    {
        // Grab the first line off of our input string.
        iPos = sTmpStr.find('\n');
        if (iPos == string::npos)
        {
            sCurStr = sTmpStr;
            sTmpStr.erase();
        } else
        {
            sCurStr = sTmpStr.substr(0, iPos + 1);
            sTmpStr = sTmpStr.erase(0, iPos + 1);
        }

        // Hide comments from the rest of the parser
        iPos = sCurStr.find(';');
        if (iPos != string::npos)
        {
            sCmtStr = sCurStr.substr(iPos, sCurStr.length() - iPos);
            sCurStr = sCurStr.substr(0, iPos);
        } else
        {
            sCmtStr = "";
        }

        // Tokenize it and expand all macros in the line.
        // A macro may expand to multiple lines.
        bChanged = false;
        pMac     = NULL;
        t        = sCurStr;
        iNumToks = t.GetNumTokens();

        for (i = 0; i < iNumToks; i++)
        {
            pMac = GetMacroPtr(t[i]);

            if (pMac != NULL)
            {   
                iRet = 1;

                // Always expand the leftmost macro
                iPos = sCurStr.find(pMac->sName, 0);

                string sMacro;

                sSub = sCurStr.substr(iPos);

                if (GetMacroString(pMac, sSub, sMacro))
                {
                    string sExpanded;

                    if (ExpandMacro(pMac, sMacro, sExpanded) == -1)
                    {
                        return -1;
                    }
                     
                    sCurStr.replace(iPos, sMacro.length(), sExpanded);

                    if (sCurStr.length() >= MAX_LINE - 1)
                    {
                        PutStringAsCmt(sOrig, true, false);
                        ThrowError("Maximum line length of %d exceeded "
                                   "during expansion of '%s'", 
                                   MAX_LINE, pMac->sName.c_str());
                    }

                    bChanged = true;

                    break;
                }
            }
        }

        // Put back any comments.
        sCurStr += sCmtStr;

        // If we expanded a macro, then put this guy back onto our
        // string of things to process.  Otherwise, push it to the
        // output and continue.  By doing this, we correctly handle
        // macros that expand to more than one line.
        if (bChanged)
        {
            sTmpStr = sCurStr + sTmpStr;
        } else
        {
            sOut += sCurStr;
        }
    } while (total_iters++ < 1000 && sTmpStr.length() > 0U);

    if (sTmpStr.length() > 0U)
    {
        PutStringAsCmt(sOrig, true, false);  
        ThrowError("Possible infinite loop during macro expansion.");
    }


    return iRet;
}

macro *Parser::GetMacroPtr(const char *macName)
{
    macro *pMac;
    int i = 0;

    pMac = m_macroList.GetNode(i);

    while (pMac != NULL)
    {
        if (pMac->sName == macName)
        {
            break;
        }

        pMac = m_macroList.GetNode(++i);
    }

    return pMac;
}

int Parser::ExpandMacro(macro *pMacro, string &sMacro, string &sTargetString)
{
    // Expand any symbols in the line
    int i, j; 
    string::size_type iPos, iSkip;
    string sSearch, sCurLine, *pString, *pArg, *pSym;

    int iNumLines = pMacro->codeList.GetNumNodes();

    LList<string> args;

    int iNumArgs = GetMacroArgs(pMacro->sName, sMacro, args, pMacro->iType);
    
    if (iNumArgs == -1)
    {
        return -1;
    }

    int iNumSyms = pMacro->argList.GetNumNodes();

    if (iNumArgs != iNumSyms)
    {
        ThrowError("MACRO '%s' expected %d arguments, got %d", 
                    pMacro->sName.c_str(), iNumSyms, iNumArgs);
        return -1;
    }

    for (i = 0; i < iNumLines; i++)
    {
        // Get a line of code from the macro
        pString = pMacro->codeList.GetNode(i);

        if (pString != NULL)
        {
            // Now expand any symbols in the line of code

            sCurLine = *pString;

            for (j = 0; j < iNumArgs; j++)
            {           
                // Set the symbol we will search for
                pSym = pMacro->argList.GetNode(j);
                sSearch = "%";
                sSearch += *pSym;
                sSearch += '%';
                
                // Now do a search and replace
                iPos  = sCurLine.find(sSearch);
                iSkip = 0;

                // If we found it, replace all occurances
                while (iPos != string::npos)
                {
                    pArg = args.GetNode(j);

                    iSkip = iPos + (*pArg).length();
                    sCurLine.replace(iPos, sSearch.length(), *pArg);

                    // Find again
                    iPos = sCurLine.find(sSearch, iSkip);
                }
            }

            sTargetString += sCurLine;
            
            if ((i + 1) < iNumLines)
            {
                sTargetString += '\n';
            }
        }           
    }


    return 1;
}

int Parser::GetMacroArgs(macro *pMacro, string &sMacro)
{
    int iNum = 0;
    string s;
    string::size_type iPos;

    if (pMacro == NULL)
    {
        iNum = -1;
    }
    else
    {
        if (pMacro->iType == mUNKNOWN)
        {
            // Get to the first char of the macro name
            iPos = sMacro.find(pMacro->sName);

            s = sMacro.substr(iPos);

            iPos = s.find_first_of('(');
            if (iPos == string::npos)
            {
                pMacro->iType = mNOPAREN;
            }
            else
            {
                pMacro->iType = mPAREN;
            }
        }

        iNum = GetMacroArgs(pMacro->sName, s, pMacro->argList, pMacro->iType);  
    }

    return iNum;
}

int Parser::GetMacroArgs(string &sMacName, string &sMacro, LList<string> &argList, int iType)
{
    int iParen, iBracket, iNum = 0, iTempPos;
    char c;
    bool bDone = false;
    bool bAdd;
    string sTemp;
    string::size_type iPos;

    iPos = sMacro.find(sMacName);

    if (iPos == string::npos)
    {
        ThrowError("Unable to find macro name %s in macro string %s", 
                   sMacName.c_str(), sMacro.c_str());
        return -1;
    }
    else
    {
        iPos += sMacName.length();

        if (iType == mNOPAREN)
        {
            if (iPos < sMacro.length() && !isspace(sMacro[iPos]))
            {
                ThrowError("Macro %s does not match macro name %s", 
                           sMacro.c_str(), sMacName.c_str());
                return -1;
            }
        }
        else if (iType == mPAREN)
        {
            // Eat whitespace
            for (; (iPos < sMacro.length() && isspace(sMacro[iPos])); iPos++)
                ;

            if (sMacro[iPos] != '(')
            {
                ThrowError("Opening parenthesis not found for macro %s", 
                           sMacro.c_str());
                return -1;
            }
            else
            {
                iPos++;
            }
        }
    }

    if (iPos != string::npos)
    {
        iParen = 1;
        iBracket = 0;

        // Grab each of the params
        while (!bDone)
        {           
            sTemp.erase();
        
            // Eat whitespace
            for (; iPos < sMacro.length() && isspace(sMacro[iPos]) ; iPos++)
                ;

            bAdd = false;
            
            do
            {
                c = iPos < sMacro.length() ? sMacro[iPos++] : NUL;

                if (c == '(')
                {
                    sTemp += c;
                    
                    if (iBracket == 0)
                    {
                        iParen++;
                    }
                }
                else if (c == ')')
                {
                    if (iBracket == 0)
                    {
                        iParen--;
                    }
                    
                    // If iParen is 0, we have reached the end of the macro
                    if (iParen == 0)
                    {
                        // If iBracket != 0, there was a bracketry problem
                        if (iBracket != 0)
                        {
                            ThrowError("Mismatched brackets in macro %s", sMacro.c_str());
                            bDone = true;
                            return -1;
                        }
                        else if (iType == mNOPAREN)
                        {
                            ThrowError("Mismatched parenthesis in macro %s", sMacro.c_str());
                            bDone = true;
                            return -1;
                        }
                        else
                        {
                            bAdd = true;
                            bDone = true;
                        }
                    }
                    else
                    {
                        sTemp += c;
                    }
                }
                else if (c == '[')
                {
                    if (iParen == 1)
                    {
                        iBracket++;
                    }
                    else
                    {
                        sTemp += c;
                    }
                }
                else if (c == ']')
                {
                    if (iParen == 1)
                    {
                        iBracket--;
                    }
                    else
                    {
                        sTemp += c;
                    }
                }
                else if (c == ',')
                {
                    if ((iParen == 1) && (iBracket == 0))
                    {
                        bAdd = true;
                    }
                    else
                    {
                        sTemp += c;
                    }
                }               
                else if (c == NUL)
                {
                    if (iType == mNOPAREN)
                    {
                        if (iBracket != 0)
                        {
                            ThrowError("Mismatched brackets while reading args for macro %s", sMacro.c_str());
                            bDone = true;
                            return -1;
                        }
                        else
                        {
                            bAdd = true;
                        }
                    }
                    else
                    {
                        ThrowError("End of line encountered while reading args for macro %s", sMacro.c_str());
                        bDone = true;
                        return -1;
                    }

                    bDone = true;
                }               
                else
                {
                    sTemp += c;
                }

                if (bAdd) // Add the param into the structure
                {
                    // Eat trailing whitespace
                    iTempPos = sTemp.length() - 1;
            
                    if (iTempPos >= 0)
                    {
                        for (; ((iTempPos >= 0) && isspace(sTemp[iTempPos])); iTempPos--);

                        sTemp = sTemp.substr(0, iTempPos+1);
                    }

                    // See if this was a valid argument
                    if  (
                        ((iType == mPAREN) && (sTemp.length() == 0)) ||
                        ((iType == mNOPAREN) && (iNum > 0) && (sTemp.length() == 0))
                        )
                    {
                        ThrowError("No argument specified in macro %s", sMacro.c_str());
                        bDone = true;
                    }
                    else
                    {
                        if  (
                            (iType == mNOPAREN) && (sTemp.length() != 0) ||
                            (iType == mPAREN)
                            )
                        {
                            argList.AddNode(sTemp);
                            iNum++;
                        }
                    }// else
                } // if
            } while (!bAdd); // Loop until we have added a param
        }// while (sMacro
    }

    return iNum;
}

int Parser::StripReturn(string &s)
{
    string::size_type iPos = s.find('\r');

    if (iPos != string::npos)
        s = s.substr(0, iPos);

    iPos = s.find('\n');

    if (iPos != string::npos)
        s = s.substr(0, iPos);

    return 1;
}

int Parser::StripReturn(char *s)
{
    char *pC = strchr(s, '\r');

    if (pC != NULL)
        *pC = NUL;

    pC = strchr(s, '\n');

    if (pC != NULL)
        *pC = NUL;

    return 1;
}

