/****************************************************************************
    Kalkulon - A programmable calculator for programmers

    This file is part of the Kalkulon core.

    If you find this source code useful, your are encouraged to use it in your own projects.
    Please give me credit!

    COPYRIGHT by Juergen Holetzeck 2003 - 2008
    e-mail:		contact@kalkulon.de
    homepage:	www.kalkulon.de
****************************************************************************/


// RPNStackEval.cpp: implementation of the CRPNStackEval class.
//
//////////////////////////////////////////////////////////////////////


#include "stdafx.h"

#include <math.h>

#include "RPNStackEval.h"
#include "ErrBase.h"
#include "serialize.h"

using namespace Kalkulon;

#ifdef USE_WIN32_LIB
    typedef __declspec( dllimport ) int  (* LPGETINT) ();
    typedef __declspec( dllimport ) const fn_list_t*  (* LPGETFNLIST) ();
#endif

const unsigned int CRPNStackEval::CALLBACK_INTERVAL = 64;
const int CRPNStackEval::SERIALIZATION_MAGICPATTERN = 0xc0cad05e;
const int CRPNStackEval::SERIALIZATION_VERSION = 1;


// Be very careful setting this value, it is the limit for the recursion depth of eval() calls.
// For a given stack size defined at compile time this value must be small enough that no stack
// overflow can occur; the limit can be tested with "recursion_limit.k" script from examples directory
int CRPNStackEval::recursion_limit = 150;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

#if _MSC_VER >= 1400
#pragma warning (disable: 4706) // warning C4706: assignment within conditional expression
#endif
CRPNStackEval::CRPNStackEval()
{
    clearAll();

    callbackOb = NULL;
    breaksig = false;

#ifdef USE_WIN32_LIB
    // load funcdll.dll on WIN32 systems
    FnDll_fn_list = NULL;
    FnDllValid = false;

    int FnDllInterfaceVersion = 0;

    hDLL = ::LoadLibraryA("funcdll");
    if(hDLL != NULL)
    {
        LPGETINT GetInterfaceVersion = (LPGETINT)GetProcAddress(hDLL, "GetInterfaceVersion");
        if( GetInterfaceVersion &&
            (FnDllInterfaceVersion = GetInterfaceVersion())
          )
          {
            FnDllValid = ((FnDllInterfaceVersion & 0xff00) == (FUNCDLLINTERFACEVERSION & 0xff00) &&
                          (FnDllInterfaceVersion & 0x00ff) <= (FUNCDLLINTERFACEVERSION & 0x00ff)
                         );
            if(FnDllValid)
            {
                LPGETFNLIST GetFnList = (LPGETFNLIST)GetProcAddress(hDLL, "GetFnList");
                if(GetFnList) FnDll_fn_list = GetFnList();
                GetLastError = (LPGETLASTERROR)GetProcAddress(hDLL, "GetLastError");
                GetErrorMessage = (LPGETERRORMESSAGE)GetProcAddress(hDLL, "GetErrorMessage");
            }
        }
    }
#endif

}
#if _MSC_VER >= 1400
#pragma warning (default: 4706)
#endif

CRPNStackEval::~CRPNStackEval()
{
#ifdef USE_WIN32_LIB
    if(hDLL != NULL) ::FreeLibrary(hDLL);
#endif
}


DataObject CRPNStackEval::eval(RPNStack_t &rpn_stack, unsigned int uid, RPNStack_t::iterator *pIt)
{
    // count number of recursions
    static int rec_depth=0;
    // count total number of evaluated tokens
    static unsigned int loopCtr=0;

    if(++rec_depth>recursion_limit)
        throw ErrMathInternal("recursion", "RECURSION LIMIT REACHED");

    DataObject dob_result;

    RPNStack_t::iterator it=(pIt ? (*pIt) : rpn_stack.begin() );
    RPNStack_t::iterator itend = rpn_stack.end();

    while(it != itend)
    {
        // callback UI for event processing
        if(callbackOb && 0==(++loopCtr % CALLBACK_INTERVAL)) callbackOb->processEvents();

        Token &t=*it;

        try{

            if(breaksig) {breaksig=false; throw ErrBreakSig();}

            switch(t.tid)
            {
                case Token::DATAOBJECT:
                    datastack.push(t.data);
                    break;
                case Token::PLUS:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop()+par0);
                    break;
                    }
                case Token::MINUS:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop()-par0);
                    break;
                    }
                case Token::MUL:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop()*par0);
                    break;
                    }
                case Token::POW:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(pow(static_cast<double> (datastack.pop()),static_cast<double> (par0)));
                    break;
                    }
                case Token::DIV:
                    {
                    const DataObject& par0 = datastack.pop();
                    if(static_cast<double> (par0)==0) throw ErrMath(t,"SINGULARITY");
                    datastack.push(datastack.pop()/par0);
                    break;
                    }
                case Token::MOD:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push((S32)datastack.pop()%(S32)par0);
                    break;
                    }
                case Token::FUNC:
                    func(t);
                    break;
                case Token::CAST_U08:
                    datastack.push(cast_u08(datastack.pop()));
                    break;
                case Token::CAST_U16:
                    datastack.push(cast_u16(datastack.pop()));
                    break;
                case Token::CAST_U32:
                    datastack.push(cast_u32(datastack.pop()));
                    break;
                case Token::CAST_S08:
                    datastack.push(cast_s08(datastack.pop()));
                    break;
                case Token::CAST_S16:
                    datastack.push(cast_s16(datastack.pop()));
                    break;
                case Token::CAST_S32:
                    datastack.push(cast_s32(datastack.pop()));
                    break;
                case Token::CAST_STRING:
                    datastack.push(static_cast<std::string>(datastack.pop()));
                    break;
                case Token::NOT:
                    datastack.push(static_cast<U32>(~static_cast<U32>(datastack.pop())));
                    break;
                case Token::NEG:
                    datastack.push(-datastack.pop());
                    break;
                case Token::LOGICAL_NOT:
                    datastack.push(!datastack.pop());
                    break;
                case Token::LSHIFT:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(static_cast<U32>(datastack.pop())<<static_cast<U32>(par0));
                    break;
                    }
                case Token::RSHIFT:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(static_cast<U32>(datastack.pop())>>static_cast<U32>(par0));
                    break;
                    }
                case Token::GREATER:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() > par0);
                    break;
                    }
                case Token::GREATER_EQUAL:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() >= par0);
                    break;
                    }
                case Token::LESSER:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() < par0);
                    break;
                    }
                case Token::LESSER_EQUAL:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() <= par0);
                    break;
                    }
                case Token::EQUAL:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() == par0);
                    break;
                    }
                case Token::NOT_EQUAL:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(datastack.pop() != par0);
                    break;
                    }
                case Token::AND:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(static_cast<U32>(datastack.pop())&static_cast<U32>(par0));
                    break;
                    }
                case Token::XOR:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(static_cast<U32>(datastack.pop())^static_cast<U32>(par0));
                    break;
                    }
                case Token::OR:
                    {
                    const DataObject& par0 = datastack.pop();
                    datastack.push(static_cast<U32>(datastack.pop())|static_cast<U32>(par0));
                    break;
                    }
                case Token::LOGICAL_OR:
                    {	// logical OR defines sequence point! -> expr1 OR expr2 BLOCKEND
                        if(!datastack.pop())
                        {
                            // second expression has to be evaluated
                            datastack.push(0 != eval(rpn_stack, t.uid, &(++it)));
                        }
                        else
                        {
                            // true, so skip second expression
                            while ((++it)->tid!=Token::BLOCKEND || it->uid != t.uid) ;
                            datastack.push(1);	// OR expression evaluates to TRUE
                        }
                        break;
                    }
                case Token::LOGICAL_AND:
                    {	// logical AND defines sequence point! -> expr1 AND expr2 BLOCKEND
                        if(datastack.pop())
                        {
                            // second expression has to be evaluated
                            datastack.push(0 != eval(rpn_stack, t.uid, &(++it)));
                        }
                        else
                        {
                            // false, so skip second expression
                            while ((++it)->tid!=Token::BLOCKEND || it->uid != t.uid) ;
                            datastack.push(0);	// AND expression evaluates to FALSE
                        }
                        break;
                    }
                case Token::NAME:
                case Token::POSTINC:
                case Token::POSTDEC:
                case Token::PREINC:
                case Token::PREDEC:
                    {	// try to find from local to global scope
                        bool ok=false;
                        size_t nested_blocks;
                        if(t.token_flags & Token::FLAG_GLOBALSCOPE)
                            nested_blocks = 1;						// global scope only
                        else
                            nested_blocks = symbols_list.size();	// start at local scope

                        while(nested_blocks--)
                        {
                            symbols_t::iterator it;
                            if((it=(*symbols_list[nested_blocks]).find(t.token_str)) != (*symbols_list[nested_blocks]).end())
                            {
                                DataObject *pdob = &it->second;

                                if(t.token_flags&Token::FLAG_INDEXED)
                                    pdob = getIndexedDob(pdob, t.token_int);

                                // pre increment or decrement
                                if(t.tid==Token::PREINC)
                                    *pdob=*pdob+DataObject(1);
                                else
                                    if(t.tid==Token::PREDEC) *pdob=*pdob-1;

                                datastack.push(*pdob);

                                // post increment or decrement
                                if(t.tid==Token::POSTINC)
                                    *pdob=*pdob+DataObject(1);
                                else
                                    if(t.tid==Token::POSTDEC) *pdob=*pdob-1;
                                ok=true;
                                break;
                            }
                        }
                        if(!ok) throw ErrUndefSym(t);
                    }
                    break;
                case Token::LSNAME:
                    {
                        DataObject rvalue;
                        DataObject *pdob=NULL;
                        size_t nested_blocks;

                        if(t.token_flags & Token::FLAG_GLOBALSCOPE)
                            nested_blocks = 1;						// global scope
                        else
                            nested_blocks = symbols_list.size();	// local scope

                        if(t.token_flags & Token::FLAG_INDEXED)
                        {

                            while(nested_blocks--)
                            {
                                symbols_t::iterator it;
                                if((it=(*symbols_list[nested_blocks]).find(t.token_str)) != (*symbols_list[nested_blocks]).end())
                                {
                                    pdob = &it->second;
                                    break;
                                }
                            }
                            if(pdob == NULL) throw ErrUndefSym(t);

                            rvalue=datastack.pop();
                            pdob = getIndexedDob(pdob, t.token_int);
                        }
                        else
                        {
                            rvalue = datastack.pop();
                            pdob = &(*symbols_list[nested_blocks-1])[t.token_str];
                        }
                        switch(t.sid)
                        {
                            case Token::PLUS:
                                *pdob = *pdob + rvalue;
                                break;
                            case Token::MINUS:
                                *pdob = *pdob - rvalue;
                                break;
                            case Token::MUL:
                                *pdob = *pdob * rvalue;
                                break;
                            case Token::DIV:
                                *pdob = *pdob / rvalue;
                                break;
                            case Token::MOD:
                                *pdob = static_cast<S32>(*pdob) % static_cast<S32>(rvalue);
                                break;
                            case Token::LSHIFT:
                                *pdob = static_cast<U32>(*pdob) << static_cast<U32>(rvalue);
                                break;
                            case Token::RSHIFT:
                                *pdob = static_cast<U32>(*pdob) >> static_cast<U32>(rvalue);
                                break;
                            case Token::POW:
                                *pdob = pow(static_cast<double> (*pdob), static_cast<double> (rvalue));
                                break;
                            case Token::OR:
                                *pdob = static_cast<U32>(*pdob) | static_cast<U32>(rvalue);
                                break;
                            case Token::AND:
                                *pdob = static_cast<U32>(*pdob) & static_cast<U32>(rvalue);
                                break;
                            case Token::XOR:
                                *pdob = static_cast<U32>(*pdob) ^ static_cast<U32>(rvalue);
                                break;
                            default:
                                *pdob = rvalue;
                        }
                        datastack.push(*pdob);
                    }
                    break;
                case Token::LISTEND:
                    {
                        // build list DataObject
                        int size = t.token_int;
                        DataObject dob(DataObject::LIST);
                        dob.getPtrListValue()->resize(size);
                        for(int i=size-1; i>=0; --i)
                            (*dob.getPtrListValue())[i] = datastack.pop();
                        datastack.push(dob);
                    }
                    break;
                case Token::INDEXEND:
                    {
                        int i;
                        std::vector<unsigned int> index;
                        int dim=t.token_int;

                        index.resize(dim);
                        for(i=dim-1; i>=0; --i)
                        {
                            index[i] = (unsigned int)(datastack.pop());
                        }
                        DataObject dob=datastack.pop();

                        DataObject *ptmp, *pdob = &dob;
                        for(i=0; i<dim; ++i)
                        {
                            ptmp=&((*pdob)[index[i]]);
                            pdob = ptmp;
                        }
                        datastack.push(*pdob);
                    }
                    break;
                case Token::LSFUNC:
                    { 	// new function definition
                        FuncObject fo;
                        Token tok;
                        // copy parameter names
                        for(int i=0; i<t.token_int; ++i)
                        {
                            tok = *(++it);
                            if(tok.tid==Token::NAME)
                                fo.param_list.push_back(tok.token_str);
                            else
                                throw ErrWrongDef(t);
                        }
                        // next token must be ASSIGN
                        tok = *(++it);
                        if(tok.tid!=Token::ASSIGN) throw ErrWrongDef(t);

                        // copy function name
                        fo.name = t.token_str;

                        unsigned int uid = t.uid;
                        // copy rhs of definition
                        do
                            { tok=*(++it); fo.rpn_stack.push_back(tok); }
                        while(tok.tid!=Token::BLOCKEND || tok.uid != uid);

                        // add number of arguments to function name, eg. f(x,y) -> f@2
                        char buf[32];
                        buf[0] = '@';
                    #if _MSC_VER >= 1400
                        _itoa_s((unsigned int) t.token_int, buf+1, 31, 10);
                    #else
                        itoa((unsigned int) t.token_int,buf+1,10);
                    #endif
                        // and store function name + definition
                        functions[t.token_str+buf] = fo;

                        // datastack.push special MESSAGE data object
                        DataObject dob("function defined", DataObject::MESSAGE);
                        datastack.push(dob);
                        break;
                    }
                case Token::PUREFUNC:
                    {
                        DataObject dob(DataObject::PUREFUNC);
                        while ((++it)->tid != Token::PUREFUNC)
                        {
                            dob.getPtrRPNStack()->push_back(*it);
                        }
                        datastack.push(dob);
                        break;
                    }
                case Token::EVALPF:
                    {
                        eval_purefn(t);
                        break;
                    }
                case Token::IF:
                    {
                        bool result_pushed=false;

                        if(eval(rpn_stack, t.uid, &(++it)))
                        {
                            datastack.push(eval(rpn_stack, t.uid, &(++it)));
                            result_pushed = true;
                            // skip 'else' part
                            if(t.token_int == 3) while ((++it)->tid!=Token::BLOCKEND || it->uid != t.uid) ;
                        }
                        else
                        {
                            // skip 'then' part
                            while (((++it)->tid!=Token::DELIM && it->tid!=Token::BLOCKEND) || it->uid != t.uid) ;
                            if(t.token_int == 3)
                            {	// evaluate 'else' part
                                datastack.push(eval(rpn_stack, t.uid, &(++it)));
                                result_pushed = true;
                            }
                        }

                        if(!result_pushed)
                            datastack.push(DataObject("if evaluated", DataObject::MESSAGE));

                        break;
                    }
                case Token::WHILE:
                    {
                        DataObject result("while finished", DataObject::MESSAGE);

                        RPNStack_t::iterator startblock = ++it;

                        while(eval(rpn_stack, t.uid, &it))
                        {
                            result = eval(rpn_stack, t.uid, &(++it));
                            it = startblock;
                        }
                        // move iter to end
                        while ((++it)->tid != Token::BLOCKEND || it->uid != t.uid) ;

                        datastack.push(result);

                        break;
                    }
                case Token::DO:
                    {
                        DataObject result("do finished", DataObject::MESSAGE);

                        RPNStack_t::iterator startblock = it;

                        do
                        {
                            it = startblock;
                            result = eval(rpn_stack, t.uid, &(++it));
                        }
                        while(eval(rpn_stack, t.uid, &(++it)));

                        datastack.push(result);

                        break;
                    }
                case Token::FOR:
                    {
                        DataObject result("for finished", DataObject::MESSAGE);

                        // calculate all iter positions first
                        RPNStack_t::iterator condblock = it++;
                        while ((++condblock)->tid != Token::DELIM || condblock->uid != t.uid) ;

                        RPNStack_t::iterator iterblock = ++condblock;
                        while ((++iterblock)->tid != Token::DELIM || iterblock->uid != t.uid) ;

                        RPNStack_t::iterator bodyblock = ++iterblock;
                        while ((++bodyblock)->tid != Token::DELIM || bodyblock->uid != t.uid) ;

                        RPNStack_t::iterator endblock = ++bodyblock;
                        while ((++endblock)->tid != Token::BLOCKEND || endblock->uid != t.uid) ;

                        for( eval(rpn_stack, t.uid, &it);
                             it = condblock, eval(rpn_stack, t.uid, &it);
                             it = iterblock, eval(rpn_stack, t.uid, &it)
                           )
                            {
                                it = bodyblock;
                                result = eval(rpn_stack, t.uid, &it);
                            }
                        it = endblock;

                        datastack.push(result);

                        break;
                    }
                case Token::SEP:
                    datastack.pop();	// remove argument
                    break;
                case Token::BLOCKEND:
                case Token::DELIM:
                case Token::END:
                    {
                        dob_result = datastack.pop();
                        if(uid == t.uid) {*pIt = it; --rec_depth; return dob_result;};
                        break;
                    }
                default:
                    throw ErrInternal(__LINE__, __FILE__);
            }// end of switch
        }// end of try
        catch(const ErrMathInternal& err)
        {
            rec_depth=0;
            datastack.clear();
            throw ErrMath(t, err.reason);
        }
        catch(...)
        {
            rec_depth=0;
            datastack.clear();
            throw;
        }
        ++it;		// next token
    } // end of while

    if(--rec_depth == 0)
    {
        // now user stack should be empty
        if(!datastack.empty())
        {
            datastack.clear();
            throw ErrInternal(__LINE__, __FILE__);
        }

        if(dob_result.getDid() != DataObject::MESSAGE)
        {
            // add each result to symbol table in form "out[0], out[1], ..."
            if(!(++out_idx))
            {
                DataObject tmp(DataObject::LIST);
                tmp.getPtrListValue()->push_back(dob_result);
                (*symbols_list[0])[OUT_LIST] = tmp;
            }
            else
            {
                if( (*symbols_list[0])[OUT_LIST].getDid() == DataObject::LIST       &&
                    (*symbols_list[0])[OUT_LIST].getPtrListValue()->size() == (size_t) out_idx
                  )
                {
                    (*symbols_list[0])[OUT_LIST].getPtrListValue()->push_back(dob_result);
                }
                else
                {
                    //OUT_LIST corrupted by user, so build new one
                    out_idx=0;
                    DataObject tmp(DataObject::LIST);
                    tmp.getPtrListValue()->push_back(dob_result);
                    (*symbols_list[0])[OUT_LIST] = tmp;
                }
            }
            (*symbols_list[0])[OUT_STR] = dob_result;
        }
    }

    return dob_result;
}

int CRPNStackEval::SetRecursionLimit(int new_limit)
{
    int old=recursion_limit;
    recursion_limit=new_limit;
    return old;
}


DataObject* CRPNStackEval::getIndexedDob(DataObject *pdob, int dim)
{
    int i;
    DataObject *ptmp;
    std::vector<unsigned int> index;

    index.resize(dim);
    for(i=dim-1; i>=0; --i)
    {
        index[i] = (unsigned int)(datastack.pop());
    }
    for(i=0; i<dim; ++i)
    {
        ptmp=&((*pdob)[index[i]]);
        pdob = ptmp;
    }
    return pdob;
}


bool CRPNStackEval::serialize(const std::string& filename) const
{
  bool success = false;
  std::ofstream os(filename.c_str(), std::ios_base::binary);
  if(os)
  {
    os.exceptions ( std::ios_base::failbit | std::ios_base::badbit);
    try
    {
      trySerialize(os);
      success = true;
    }
    catch (std::ios_base::failure e)
    {
      success = false;
    }
  }

  os.close();
  return success;
}


bool CRPNStackEval::deserialize(const std::string& filename)
{
  bool success = false;

  clearAll();

  std::ifstream is(filename.c_str(), std::ios_base::binary);
  if(is)
  {
    is.exceptions ( std::ios_base::failbit | std::ios_base::badbit | std::ios_base::eofbit);
    try
    {
      success = tryDeserialize(is);
    }
    catch (std::ios_base::failure e)
    {
      success = false;
    }
  }

  if(!success)
    clearAll();

  is.close();

  return success;
}


void CRPNStackEval::trySerialize(std::ostream &os) const
{
  // write header
  serialize::put(os, (char) sizeof(int));
  serialize::put(os, (char) sizeof(size_t));
  serialize::put(os, SERIALIZATION_MAGICPATTERN);
  serialize::put(os, SERIALIZATION_VERSION);

  // serialize data

  serialize::put(os, out_idx);

  {
    // symbols
    serialize::put(os, symbols.size());
    symbols_t::const_iterator it, b, e;
    b = symbols.begin();
    e = symbols.end();
    for(it=b; it!=e; ++it)
    {
      serialize::put(os, it->first);
      it->second.serialize(os);
    }
  }

  {
    // functions
    serialize::put(os, functions.size());
    functions_t::const_iterator it, b, e;
    b = functions.begin();
    e = functions.end();
    for(it=b; it!=e; ++it)
    {
      serialize::put(os, it->first);
      it->second.serialize(os);
    }
  }

  serialize::put(os, SERIALIZATION_MAGICPATTERN);
}


bool CRPNStackEval::tryDeserialize(std::istream &is)
{

  // read header
  char sizeOfInt = serialize::getChar(is);
  if(!is || sizeof(int) != sizeOfInt) return false;

  char sizeOfSize_t = serialize::getChar(is);
  if(!is || sizeof(size_t) != sizeOfSize_t) return false;

  int magicPattern = serialize::getInt(is);
  if(!is || SERIALIZATION_MAGICPATTERN != magicPattern) return false;

  int version = serialize::getInt(is);
  if(!is || SERIALIZATION_VERSION != version) return false;


  // deserialize data

  out_idx = serialize::getInt(is);

  {
    // symbols
    size_t len = serialize::getSize_t(is);
    for(size_t i=0; i<len; ++i)
    {
      std::string name = serialize::getString(is);
      if(!is) return false;
      DataObject dob = DataObject::deserialize(is);
      symbols[name] = dob;
    }
  }

  {
    // functions
    size_t len = serialize::getSize_t(is);
    for(size_t i=0; i<len; ++i)
    {
      std::string name = serialize::getString(is);
      if(!is) return false;
      FuncObject fob = FuncObject::deserialize(is);
      functions[name] = fob;
    }
  }

  int magicPattern2 = serialize::getInt(is);
  if(!is || SERIALIZATION_MAGICPATTERN != magicPattern2) return false;

  return true;
}

