/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
#include <sal/macros.h>
#include <sal/log.hxx>
#include <rtl/math.hxx>
#include <formula/FormulaCompiler.hxx>
#include <formula/errorcodes.hxx>
#include <formula/token.hxx>
#include <formula/tokenarray.hxx>
#include <o3tl/string_view.hxx>
#include <core_resource.hxx>
#include <core_resource.hrc>
 
#include <svl/zforlist.hxx>
#include <unotools/charclass.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp>
#include <com/sun/star/sheet/FormulaMapGroup.hpp>
#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp>
#include <algorithm>
#include <mutex>
 
namespace formula
{
    using namespace ::com::sun::star;
 
    static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" };
 
namespace {
 
class FormulaCompilerRecursionGuard
{
    private:
        short& rRecursion;
    public:
        explicit FormulaCompilerRecursionGuard( short& rRec )
            : rRecursion( rRec ) { ++rRecursion; }
        ~FormulaCompilerRecursionGuard() { --rRecursion; }
};
 
SvNumFormatType lcl_GetRetFormat( OpCode eOpCode )
{
    switch (eOpCode)
    {
        case ocEqual:
        case ocNotEqual:
        case ocLess:
        case ocGreater:
        case ocLessEqual:
        case ocGreaterEqual:
        case ocAnd:
        case ocOr:
        case ocXor:
        case ocNot:
        case ocTrue:
        case ocFalse:
        case ocIsEmpty:
        case ocIsString:
        case ocIsNonString:
        case ocIsLogical:
        case ocIsRef:
        case ocIsValue:
        case ocIsFormula:
        case ocIsNA:
        case ocIsErr:
        case ocIsError:
        case ocIsEven:
        case ocIsOdd:
        case ocExact:
            return SvNumFormatType::LOGICAL;
        case ocGetActDate:
        case ocGetDate:
        case ocEasterSunday :
            return SvNumFormatType::DATE;
        case ocGetActTime:
            return SvNumFormatType::DATETIME;
        case ocGetTime:
            return SvNumFormatType::TIME;
        case ocNPV:
        case ocPV:
        case ocSYD:
        case ocDDB:
        case ocDB:
        case ocVBD:
        case ocSLN:
        case ocPMT:
        case ocFV:
        case ocIpmt:
        case ocPpmt:
        case ocCumIpmt:
        case ocCumPrinc:
            return SvNumFormatType::CURRENCY;
        case ocRate:
        case ocIRR:
        case ocMIRR:
        case ocRRI:
        case ocEffect:
        case ocNominal:
        case ocPercentSign:
            return SvNumFormatType::PERCENT;
        default:
            return SvNumFormatType::NUMBER;
    }
}
 
void lclPushOpCodeMapEntry( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
        const OUString* pTable, sal_uInt16 nOpCode )
{
    sheet::FormulaOpCodeMapEntry aEntry;
    aEntry.Token.OpCode = nOpCode;
    aEntry.Name = pTable[nOpCode];
    rVec.push_back( aEntry);
}
 
void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
        const OUString* pTable, sal_uInt16 nOpCodeBeg, sal_uInt16 nOpCodeEnd )
{
    for (sal_uInt16 nOpCode = nOpCodeBeg; nOpCode < nOpCodeEnd; ++nOpCode)
        lclPushOpCodeMapEntry( rVec, pTable, nOpCode );
}
 
void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
        const OUString* pTable, const sal_uInt16* pnOpCodes, size_t nCount )
{
    for (const sal_uInt16* pnEnd = pnOpCodes + nCount; pnOpCodes < pnEnd; ++pnOpCodes)
        lclPushOpCodeMapEntry( rVec, pTable, *pnOpCodes );
}
 
CharClass* createCharClassIfNonEnglishUI()
{
    const LanguageTag& rLanguageTag( Application::GetSettings().GetUILanguageTag());
    if (rLanguageTag.getLanguage() == "en")
        return nullptr;
    return new CharClass( ::comphelper::getProcessComponentContext(), rLanguageTag);
}
 
class OpCodeList
{
public:
 
    OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&,
            FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE );
    OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&,
            FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE );
 
private:
    bool getOpCodeString( OUString& rStr, sal_uInt16 nOp );
    void putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp, const CharClass* pCharClass );
 
private:
    FormulaCompiler::SeparatorType meSepType;
    const std::pair<const char*, int>* mpSymbols1;
    const std::pair<TranslateId, int>* mpSymbols2;
};
 
OpCodeList::OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap,
        FormulaCompiler::SeparatorType eSepType)
    : meSepType(eSepType)
    , mpSymbols1(pSymbols)
    , mpSymbols2(nullptr)
{
    std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
    const CharClass* pCharClass = xCharClass.get();
    if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE)
    {
        for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
        {
            putDefaultOpCode( xMap, i, pCharClass);
        }
    }
    else
    {
        for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
        {
            OUString aOpStr;
            if ( getOpCodeString( aOpStr, i) )
                xMap->putOpCode( aOpStr, OpCode(i), pCharClass);
            else
                putDefaultOpCode( xMap, i, pCharClass);
        }
    }
}
 
OpCodeList::OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap,
        FormulaCompiler::SeparatorType eSepType)
    : meSepType(eSepType)
    , mpSymbols1(nullptr)
    , mpSymbols2(pSymbols)
{
    std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
    const CharClass* pCharClass = xCharClass.get();
    if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE)
    {
        for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
        {
            putDefaultOpCode( xMap, i, pCharClass);
        }
    }
    else
    {
        for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
        {
            OUString aOpStr;
            if ( getOpCodeString( aOpStr, i) )
                xMap->putOpCode( aOpStr, OpCode(i), pCharClass);
            else
                putDefaultOpCode( xMap, i, pCharClass);
        }
    }
}
 
bool OpCodeList::getOpCodeString( OUString& rStr, sal_uInt16 nOp )
{
    switch (nOp)
    {
        case SC_OPCODE_SEP:
        {
            if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
            {
                rStr = ";";
                return true;
            }
        }
        break;
        case SC_OPCODE_ARRAY_COL_SEP:
        {
            if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
            {
                rStr = ";";
                return true;
            }
        }
        break;
        case SC_OPCODE_ARRAY_ROW_SEP:
        {
            if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
            {
                rStr = "|";
                return true;
            }
        }
        break;
    }
 
    return false;
}
 
void OpCodeList::putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp,
        const CharClass* pCharClass )
{
    OUString sKey;
    if (mpSymbols1)
    {
        const char* pKey = nullptr;
        for (const std::pair<const char*, int>* pSymbol = mpSymbols1; pSymbol->first; ++pSymbol)
        {
            if (nOp == pSymbol->second)
            {
                pKey = pSymbol->first;
                break;
            }
        }
        if (!pKey)
            return;
        sKey = OUString::createFromAscii(pKey);
    }
    else if (mpSymbols2)
    {
        TranslateId pKey;
        for (const std::pair<TranslateId, int>* pSymbol = mpSymbols2; pSymbol->first; ++pSymbol)
        {
            if (nOp == pSymbol->second)
            {
                pKey = pSymbol->first;
                break;
            }
        }
        if (!pKey)
            return;
        sKey = ForResId(pKey);
    }
    xMap->putOpCode(sKey, OpCode(nOp), pCharClass);
}
 
// static
const sal_Unicode* lcl_UnicodeStrChr( const sal_Unicode* pStr, sal_Unicode c )
{
    if ( !pStr )
        return nullptr;
    while ( *pStr )
    {
        if ( *pStr == c )
            return pStr;
        pStr++;
    }
    return nullptr;
}
 
struct OpCodeMapData
{
    FormulaCompiler::NonConstOpCodeMapPtr mxSymbolMap;
    std::mutex maMtx;
};
 
 
bool isPotentialRangeLeftOp( OpCode eOp )
{
    switch (eOp)
    {
        case ocClose:
            return true;
        default:
            return false;
    }
}
 
bool isRangeResultFunction( OpCode eOp )
{
    switch (eOp)
    {
        case ocIndirect:
        case ocOffset:
            return true;
        default:
            return false;
    }
}
 
bool isRangeResultOpCode( OpCode eOp )
{
    switch (eOp)
    {
        case ocRange:
        case ocUnion:
        case ocIntersect:
        case ocIndirect:
        case ocOffset:
            return true;
        default:
            return false;
    }
}
 
/**
    @param  pToken
            MUST be a valid token, caller has to ensure.
 
    @param  bRight
            If bRPN==false, bRight==false means opcodes for left side are
            checked, bRight==true means opcodes for right side. If bRPN==true
            it doesn't matter except for the ocSep converted to ocUnion case.
 */
bool isPotentialRangeType( FormulaToken const * pToken, bool bRPN, bool bRight )
{
    switch (pToken->GetType())
    {
        case svByte:                // could be range result, but only a few
            if (bRPN)
                return isRangeResultOpCode( pToken->GetOpCode());
            else if (bRight)
                return isRangeResultFunction( pToken->GetOpCode());
            else
                return isPotentialRangeLeftOp( pToken->GetOpCode());
        case svSingleRef:
        case svDoubleRef:
        case svIndex:               // could be range
        //case svRefList:           // um..what?
        case svExternalSingleRef:
        case svExternalDoubleRef:
        case svExternalName:        // could be range
            return true;
        case svSep:
            // A special case if a previous ocSep was converted to ocUnion it
            // stays svSep instead of svByte.
            return bRPN && !bRight && pToken->GetOpCode() == ocUnion;
        default:
            // Separators are not part of RPN and right opcodes need to be
            // other StackVar types or functions and thus svByte.
            return !bRPN && !bRight && isPotentialRangeLeftOp( pToken->GetOpCode());
    }
}
 
bool isIntersectable( FormulaToken** pCode1, FormulaToken** pCode2 )
{
    FormulaToken* pToken1 = *pCode1;
    FormulaToken* pToken2 = *pCode2;
    if (pToken1 && pToken2)
        return isPotentialRangeType( pToken1, true, false) && isPotentialRangeType( pToken2, true, true);
    return false;
}
 
bool isAdjacentRpnEnd( sal_uInt16 nPC,
        FormulaToken const * const * const pCode,
        FormulaToken const * const * const pCode1,
        FormulaToken const * const * const pCode2 )
{
    return nPC >= 2 && pCode1 && pCode2 &&
            (pCode2 - pCode1 == 1) && (pCode - pCode2 == 1) &&
            (*pCode1 != nullptr) && (*pCode2 != nullptr);
}
 
bool isAdjacentOrGapRpnEnd( sal_uInt16 nPC,
        FormulaToken const * const * const pCode,
        FormulaToken const * const * const pCode1,
        FormulaToken const * const * const pCode2 )
{
    return nPC >= 2 && pCode1 && pCode2 &&
            (pCode2 > pCode1) && (pCode - pCode2 == 1) &&
            (*pCode1 != nullptr) && (*pCode2 != nullptr);
}
 
 
} // namespace
 
 
void FormulaCompiler::OpCodeMap::putExternal( const OUString & rSymbol, const OUString & rAddIn )
{
    // Different symbols may map to the same AddIn, but the same AddIn may not
    // map to different symbols, the first pair wins. Same symbol of course may
    // not map to different AddIns, again the first pair wins and also the
    // AddIn->symbol mapping is not inserted in other cases.
    bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
    SAL_WARN_IF( !bOk, "formula.core", "OpCodeMap::putExternal: symbol not inserted, " << rSymbol << " -> " << rAddIn);
    if (bOk)
    {
        bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
        // Failed insertion of the AddIn is ok for different symbols mapping to
        // the same AddIn. Make this INFO only.
        SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternal: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
    }
}
 
void FormulaCompiler::OpCodeMap::putExternalSoftly( const OUString & rSymbol, const OUString & rAddIn )
{
    // Same as putExternal() but no warning, instead info whether inserted or not.
    bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
    SAL_INFO( "formula.core", "OpCodeMap::putExternalSoftly: symbol " << (bOk ? "" : "not ") << "inserted, " << rSymbol << " -> " << rAddIn);
    if (bOk)
    {
        bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
        SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternalSoftly: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
    }
}
 
uno::Sequence< sheet::FormulaToken > FormulaCompiler::OpCodeMap::createSequenceOfFormulaTokens(
        const FormulaCompiler& rCompiler, const uno::Sequence< OUString >& rNames ) const
{
    const sal_Int32 nLen = rNames.getLength();
    uno::Sequence< sheet::FormulaToken > aTokens( nLen);
    sheet::FormulaToken* pToken = aTokens.getArray();
    OUString const * pName = rNames.getConstArray();
    OUString const * const pStop = pName + nLen;
    for ( ; pName < pStop; ++pName, ++pToken)
    {
        OpCodeHashMap::const_iterator iLook( maHashMap.find( *pName));
        if (iLook != maHashMap.end())
            pToken->OpCode = (*iLook).second;
        else
        {
            OUString aIntName;
            if (hasExternals())
            {
                ExternalHashMap::const_iterator iExt( maExternalHashMap.find( *pName));
                if (iExt != maExternalHashMap.end())
                    aIntName = (*iExt).second;
                // Check for existence not needed here, only name-mapping is of
                // interest.
            }
            if (aIntName.isEmpty())
                aIntName = rCompiler.FindAddInFunction(*pName, !isEnglish());    // bLocalFirst=false for english
            if (aIntName.isEmpty())
                pToken->OpCode = getOpCodeUnknown();
            else
            {
                pToken->OpCode = ocExternal;
                pToken->Data <<= aIntName;
            }
        }
    }
    return aTokens;
}
 
uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::createSequenceOfAvailableMappings(
        const FormulaCompiler& rCompiler, const sal_Int32 nGroups ) const
{
    using namespace sheet;
 
    // Unfortunately uno::Sequence can't grow without cumbersome reallocs. As
    // we don't know in advance how many elements it will have we use a
    // temporary vector to add elements and then copy to Sequence :-(
    ::std::vector< FormulaOpCodeMapEntry > aVec;
 
    if (nGroups == FormulaMapGroup::SPECIAL)
    {
        // Use specific order, keep in sync with
        // offapi/com/sun/star/sheet/FormulaMapGroupSpecialOffset.idl
        static const struct
        {
            sal_Int32 nOff;
            OpCode    eOp;
        } aMap[] = {
            { FormulaMapGroupSpecialOffset::PUSH              , ocPush }           ,
            { FormulaMapGroupSpecialOffset::CALL              , ocCall }           ,
            { FormulaMapGroupSpecialOffset::STOP              , ocStop }           ,
            { FormulaMapGroupSpecialOffset::EXTERNAL          , ocExternal }       ,
            { FormulaMapGroupSpecialOffset::NAME              , ocName }           ,
            { FormulaMapGroupSpecialOffset::NO_NAME           , ocNoName }         ,
            { FormulaMapGroupSpecialOffset::MISSING           , ocMissing }        ,
            { FormulaMapGroupSpecialOffset::BAD               , ocBad }            ,
            { FormulaMapGroupSpecialOffset::SPACES            , ocSpaces }         ,
            { FormulaMapGroupSpecialOffset::MAT_REF           , ocMatRef }         ,
            { FormulaMapGroupSpecialOffset::DB_AREA           , ocDBArea }         ,
            { FormulaMapGroupSpecialOffset::MACRO             , ocMacro }          ,
            { FormulaMapGroupSpecialOffset::COL_ROW_NAME      , ocColRowName }     ,
            { FormulaMapGroupSpecialOffset::WHITESPACE        , ocWhitespace }     ,
            { FormulaMapGroupSpecialOffset::TABLE_REF         , ocTableRef }
        };
        const size_t nCount = SAL_N_ELEMENTS(aMap);
        // Preallocate vector elements.
        FormulaOpCodeMapEntry aEntry;
        aEntry.Token.OpCode = getOpCodeUnknown();
        aVec.resize(nCount, aEntry);
 
        for (auto& i : aMap)
        {
            size_t nIndex = static_cast< size_t >( i.nOff );
            if (aVec.size() <= nIndex)
            {
                // The offsets really should be aligned with the size, so if
                // the vector was preallocated above this code to resize it is
                // just a measure in case the table isn't in sync with the API,
                // usually it isn't executed.
                aEntry.Token.OpCode = getOpCodeUnknown();
                aVec.resize( nIndex + 1, aEntry );
            }
            aEntry.Token.OpCode = i.eOp;
            aVec[nIndex] = aEntry;
        }
    }
    else
    {
        /* FIXME: Once we support error constants in formulas we'll need a map
         * group for that, e.g. FormulaMapGroup::ERROR_CONSTANTS, and fill
         * SC_OPCODE_START_ERRORS to SC_OPCODE_STOP_ERRORS. */
 
        // Anything else but SPECIAL.
        if ((nGroups & FormulaMapGroup::SEPARATORS) != 0)
        {
            static const sal_uInt16 aOpCodes[] = {
                SC_OPCODE_OPEN,
                SC_OPCODE_CLOSE,
                SC_OPCODE_SEP,
            };
            lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
        }
        if ((nGroups & FormulaMapGroup::ARRAY_SEPARATORS) != 0)
        {
            static const sal_uInt16 aOpCodes[] = {
                SC_OPCODE_ARRAY_OPEN,
                SC_OPCODE_ARRAY_CLOSE,
                SC_OPCODE_ARRAY_ROW_SEP,
                SC_OPCODE_ARRAY_COL_SEP
            };
            lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
        }
        if ((nGroups & FormulaMapGroup::UNARY_OPERATORS) != 0)
        {
            // Due to the nature of the percent operator following its operand
            // it isn't sorted into unary operators for compiler interna.
            lclPushOpCodeMapEntry( aVec, mpTable.get(), ocPercentSign );
            // "+" can be used as unary operator too, push only if binary group is not set
            if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) == 0)
                lclPushOpCodeMapEntry( aVec, mpTable.get(), ocAdd );
            // regular unary operators
            for (sal_uInt16 nOp = SC_OPCODE_START_UN_OP; nOp < SC_OPCODE_STOP_UN_OP && nOp < mnSymbols; ++nOp)
            {
                lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
            }
        }
        if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) != 0)
        {
            for (sal_uInt16 nOp = SC_OPCODE_START_BIN_OP; nOp < SC_OPCODE_STOP_BIN_OP && nOp < mnSymbols; ++nOp)
            {
                switch (nOp)
                {
                    // AND and OR in fact are functions but for legacy reasons
                    // are sorted into binary operators for compiler interna.
                    case SC_OPCODE_AND :
                    case SC_OPCODE_OR :
                        break;   // nothing,
                    default:
                        lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
                }
            }
        }
        if ((nGroups & FormulaMapGroup::FUNCTIONS) != 0)
        {
            // Function names are not consecutive, skip the gaps between
            // functions with no parameter, functions with 1 parameter
            lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_NO_PAR,
                    ::std::min< sal_uInt16 >( SC_OPCODE_STOP_NO_PAR, mnSymbols ) );
            lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_1_PAR,
                    ::std::min< sal_uInt16 >( SC_OPCODE_STOP_1_PAR, mnSymbols ) );
            // Additional functions not within range of functions.
            static const sal_uInt16 aOpCodes[] = {
                SC_OPCODE_IF,
                SC_OPCODE_IF_ERROR,
                SC_OPCODE_IF_NA,
                SC_OPCODE_CHOOSE,
                SC_OPCODE_LET,
                SC_OPCODE_AND,
                SC_OPCODE_OR
            };
            lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
            // functions with 2 or more parameters.
            for (sal_uInt16 nOp = SC_OPCODE_START_2_PAR; nOp < SC_OPCODE_STOP_2_PAR && nOp < mnSymbols; ++nOp)
            {
                switch (nOp)
                {
                    // NO_NAME is in SPECIAL.
                    case SC_OPCODE_NO_NAME :
                        break;   // nothing,
                    default:
                        lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
                }
            }
            // If AddIn functions are present in this mapping, use them, and only those.
            if (hasExternals())
            {
                for (auto const& elem : maExternalHashMap)
                {
                    FormulaOpCodeMapEntry aEntry;
                    aEntry.Name = elem.first;
                    aEntry.Token.Data <<= elem.second;
                    aEntry.Token.OpCode = ocExternal;
                    aVec.push_back( aEntry);
                }
            }
            else
            {
                rCompiler.fillAddInToken( aVec, isEnglish());
            }
        }
    }
    return uno::Sequence< FormulaOpCodeMapEntry >(aVec.data(), aVec.size());
}
 
 
void FormulaCompiler::OpCodeMap::putOpCode( const OUString & rStr, const OpCode eOp, const CharClass* pCharClass )
{
    if (0 < eOp && sal_uInt16(eOp) < mnSymbols)
    {
        bool bPutOp = mpTable[eOp].isEmpty();
        bool bRemoveFromMap = false;
        if (!bPutOp)
        {
            switch (eOp)
            {
                // These OpCodes are meant to overwrite and also remove an
                // existing mapping.
                case ocCurrency:
                    bPutOp = true;
                    bRemoveFromMap = true;
                break;
                // These separator OpCodes are meant to overwrite and also
                // remove an existing mapping if it is not used for one of the
                // other separators.
                case ocArrayColSep:
                    bPutOp = true;
                    bRemoveFromMap = (mpTable[ocArrayRowSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]);
                break;
                case ocArrayRowSep:
                    bPutOp = true;
                    bRemoveFromMap = (mpTable[ocArrayColSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]);
                break;
                // For ocSep keep the ";" in map but remove any other if it is
                // not used for ocArrayColSep or ocArrayRowSep.
                case ocSep:
                    bPutOp = true;
                    bRemoveFromMap = (mpTable[eOp] != ";" &&
                            mpTable[ocArrayColSep] != mpTable[eOp] &&
                            mpTable[ocArrayRowSep] != mpTable[eOp]);
                break;
                // These OpCodes are known to be duplicates in the Excel
                // external API mapping because of different parameter counts
                // in different BIFF versions. Names are identical and entries
                // are ignored.
                case ocLinest:
                case ocTrend:
                case ocLogest:
                case ocGrowth:
                case ocTrunc:
                case ocFixed:
                case ocGetDayOfWeek:
                case ocHLookup:
                case ocVLookup:
                case ocGetDiffDate360:
                    if (rStr == mpTable[eOp])
                        return;
                    [[fallthrough]];
                // These OpCodes are known to be added to an existing mapping,
                // but only for the OOXML external API mapping. This is *not*
                // FormulaLanguage::OOXML. Keep the first
                // (correct) definition for the OpCode, all following are
                // additional alias entries in the map.
                case ocErrorType:
                case ocMultiArea:
                case ocBackSolver:
                case ocEasterSunday:
                case ocCurrent:
                case ocStyle:
                    if (mbEnglish &&
                            FormulaGrammar::extractFormulaLanguage( meGrammar) == FormulaGrammar::GRAM_EXTERNAL)
                    {
                        // Both bPutOp and bRemoveFromMap stay false.
                        break;
                    }
                    [[fallthrough]];
                default:
                    SAL_WARN("formula.core",
                            "OpCodeMap::putOpCode: reusing OpCode " << static_cast<sal_uInt16>(eOp)
                            << ", replacing '" << mpTable[eOp] << "' with '" << rStr << "' in "
                            << (mbEnglish ? "" : "non-") << "English map 0x" << ::std::hex << meGrammar);
            }
        }
 
        // Case preserving opcode -> string, upper string -> opcode
        if (bRemoveFromMap)
        {
            OUString aUpper( pCharClass ? pCharClass->uppercase( mpTable[eOp]) : rStr.toAsciiUpperCase());
            // Ensure we remove a mapping only for the requested OpCode.
            OpCodeHashMap::const_iterator it( maHashMap.find( aUpper));
            if (it != maHashMap.end() && (*it).second == eOp)
                maHashMap.erase( it);
        }
        if (bPutOp)
            mpTable[eOp] = rStr;
        OUString aUpper( pCharClass ? pCharClass->uppercase( rStr) : rStr.toAsciiUpperCase());
        maHashMap.emplace(aUpper, eOp);
    }
    else
    {
        SAL_WARN( "formula.core", "OpCodeMap::putOpCode: OpCode out of range");
    }
}
 
 
FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr, bool bComputeII, bool bMatrixFlag )
        :
        nCurrentFactorParam(0),
        pArr( &rArr ),
        maArrIterator( rArr ),
        pCode( nullptr ),
        pStack( nullptr ),
        eLastOp( ocPush ),
        nRecursion( 0 ),
        nNumFmt( SvNumFormatType::UNDEFINED ),
        pc( 0 ),
        meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
        bAutoCorrect( false ),
        bCorrected( false ),
        glSubTotal( false ),
        needsRPNTokenCheck( false ),
        mbJumpCommandReorder(true),
        mbStopOnError(true),
        mbComputeII(bComputeII),
        mbMatrixFlag(bMatrixFlag)
{
}
 
FormulaTokenArray FormulaCompiler::smDummyTokenArray;
 
FormulaCompiler::FormulaCompiler(bool bComputeII, bool bMatrixFlag)
        :
        nCurrentFactorParam(0),
        pArr( nullptr ),
        maArrIterator( smDummyTokenArray ),
        pCode( nullptr ),
        pStack( nullptr ),
        eLastOp( ocPush ),
        nRecursion(0),
        nNumFmt( SvNumFormatType::UNDEFINED ),
        pc( 0 ),
        meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
        bAutoCorrect( false ),
        bCorrected( false ),
        glSubTotal( false ),
        needsRPNTokenCheck( false ),
        mbJumpCommandReorder(true),
        mbStopOnError(true),
        mbComputeII(bComputeII),
        mbMatrixFlag(bMatrixFlag)
{
}
 
FormulaCompiler::~FormulaCompiler()
{
}
 
FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetOpCodeMap( const sal_Int32 nLanguage ) const
{
    const bool bTemporary = !HasOpCodeMap(nLanguage);
    OpCodeMapPtr xMap = GetFinalOpCodeMap(nLanguage);
    if (bTemporary)
        const_cast<FormulaCompiler*>(this)->DestroyOpCodeMap(nLanguage);
    return xMap;
}
 
FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetFinalOpCodeMap( const sal_Int32 nLanguage ) const
{
    FormulaCompiler::OpCodeMapPtr xMap;
    using namespace sheet;
    switch (nLanguage)
    {
        case FormulaLanguage::ODFF :
            if (!mxSymbolsODFF)
                InitSymbolsODFF( InitSymbols::INIT);
            xMap = mxSymbolsODFF;
            break;
        case FormulaLanguage::ODF_11 :
            if (!mxSymbolsPODF)
                InitSymbolsPODF( InitSymbols::INIT);
            xMap = mxSymbolsPODF;
            break;
        case FormulaLanguage::ENGLISH :
            if (!mxSymbolsEnglish)
                InitSymbolsEnglish( InitSymbols::INIT);
            xMap = mxSymbolsEnglish;
            break;
        case FormulaLanguage::NATIVE :
            if (!mxSymbolsNative)
                InitSymbolsNative( InitSymbols::INIT);
            xMap = mxSymbolsNative;
            break;
        case FormulaLanguage::XL_ENGLISH:
            if (!mxSymbolsEnglishXL)
                InitSymbolsEnglishXL( InitSymbols::INIT);
            xMap = mxSymbolsEnglishXL;
            break;
        case FormulaLanguage::OOXML:
            if (!mxSymbolsOOXML)
                InitSymbolsOOXML( InitSymbols::INIT);
            xMap = mxSymbolsOOXML;
            break;
        case FormulaLanguage::API :
            if (!mxSymbolsAPI)
                InitSymbolsAPI( InitSymbols::INIT);
            xMap = mxSymbolsAPI;
            break;
        default:
            ;   // nothing, NULL map returned
    }
    return xMap;
}
 
void FormulaCompiler::DestroyOpCodeMap( const sal_Int32 nLanguage )
{
    using namespace sheet;
    switch (nLanguage)
    {
        case FormulaLanguage::ODFF :
            InitSymbolsODFF( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::ODF_11 :
            InitSymbolsPODF( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::ENGLISH :
            InitSymbolsEnglish( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::NATIVE :
            InitSymbolsNative( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::XL_ENGLISH:
            InitSymbolsEnglishXL( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::OOXML:
            InitSymbolsOOXML( InitSymbols::DESTROY);
            break;
        case FormulaLanguage::API :
            InitSymbolsAPI( InitSymbols::DESTROY);
            break;
        default:
            ;   // nothing
    }
}
 
bool FormulaCompiler::HasOpCodeMap( const sal_Int32 nLanguage ) const
{
    using namespace sheet;
    switch (nLanguage)
    {
        case FormulaLanguage::ODFF :
            return InitSymbolsODFF( InitSymbols::ASK);
        case FormulaLanguage::ODF_11 :
            return InitSymbolsPODF( InitSymbols::ASK);
        case FormulaLanguage::ENGLISH :
            return InitSymbolsEnglish( InitSymbols::ASK);
        case FormulaLanguage::NATIVE :
            return InitSymbolsNative( InitSymbols::ASK);
        case FormulaLanguage::XL_ENGLISH:
            return InitSymbolsEnglishXL( InitSymbols::ASK);
        case FormulaLanguage::OOXML:
            return InitSymbolsOOXML( InitSymbols::ASK);
        case FormulaLanguage::API :
            return InitSymbolsAPI( InitSymbols::ASK);
        default:
            ;   // nothing
    }
    return false;
}
 
OUString FormulaCompiler::FindAddInFunction( const OUString& /*rUpperName*/, bool /*bLocalFirst*/ ) const
{
    return OUString();
}
 
FormulaCompiler::OpCodeMapPtr FormulaCompiler::CreateOpCodeMap(
        const uno::Sequence<
        const sheet::FormulaOpCodeMapEntry > & rMapping,
        bool bEnglish )
{
    using sheet::FormulaOpCodeMapEntry;
    // Filter / API maps are never Core
    NonConstOpCodeMapPtr xMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, false,
                FormulaGrammar::mergeToGrammar( FormulaGrammar::setEnglishBit(
                        FormulaGrammar::GRAM_EXTERNAL, bEnglish), FormulaGrammar::CONV_UNSPECIFIED));
    std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
    const CharClass* pCharClass = xCharClass.get();
    for (auto const& rMapEntry : rMapping)
    {
        OpCode eOp = OpCode(rMapEntry.Token.OpCode);
        if (eOp != ocExternal)
            xMap->putOpCode( rMapEntry.Name, eOp, pCharClass);
        else
        {
            OUString aExternalName;
            if (rMapEntry.Token.Data >>= aExternalName)
                xMap->putExternal( rMapEntry.Name, aExternalName);
            else
            {
                SAL_WARN( "formula.core", "FormulaCompiler::CreateOpCodeMap: no Token.Data external name");
            }
        }
    }
    return xMap;
}
 
static bool lcl_fillNativeSymbols( FormulaCompiler::NonConstOpCodeMapPtr& xMap, FormulaCompiler::InitSymbols eWhat = FormulaCompiler::InitSymbols::INIT )
{
    static OpCodeMapData aSymbolMap;
    static std::map<OUString, OpCodeMapData> aLocaleSymbolMap;
    std::unique_lock aGuard(aSymbolMap.maMtx);
 
    if (comphelper::LibreOfficeKit::isActive())
    {
        OUString language = comphelper::LibreOfficeKit::getLanguageTag().getLanguage();
        if (eWhat == FormulaCompiler::InitSymbols::ASK)
        {
            return aLocaleSymbolMap.contains(language)
                   && bool(aLocaleSymbolMap[language].mxSymbolMap);
        }
        else if (eWhat == FormulaCompiler::InitSymbols::DESTROY)
        {
            aLocaleSymbolMap[language].mxSymbolMap.reset();
        }
        else if (!aLocaleSymbolMap[language].mxSymbolMap)
        {
            // Core
            aLocaleSymbolMap[language].mxSymbolMap = std::make_shared<FormulaCompiler::OpCodeMap>(
                SC_OPCODE_LAST_OPCODE_ID + 1, true, FormulaGrammar::GRAM_NATIVE_UI);
            OpCodeList aOpCodeListSymbols(RID_STRLIST_FUNCTION_NAMES_SYMBOLS,
                                          aLocaleSymbolMap[language].mxSymbolMap);
            OpCodeList aOpCodeListNative(RID_STRLIST_FUNCTION_NAMES,
                                         aLocaleSymbolMap[language].mxSymbolMap);
            // No AddInMap for native core mapping.
        }
 
        xMap = aLocaleSymbolMap[language].mxSymbolMap;
    }
    else
    {
        if (eWhat == FormulaCompiler::InitSymbols::ASK)
        {
            return bool(aSymbolMap.mxSymbolMap);
        }
        else if (eWhat == FormulaCompiler::InitSymbols::DESTROY)
        {
            aSymbolMap.mxSymbolMap.reset();
        }
        else if (!aSymbolMap.mxSymbolMap)
        {
            // Core
            aSymbolMap.mxSymbolMap = std::make_shared<FormulaCompiler::OpCodeMap>(
                SC_OPCODE_LAST_OPCODE_ID + 1, true, FormulaGrammar::GRAM_NATIVE_UI);
            OpCodeList aOpCodeListSymbols(RID_STRLIST_FUNCTION_NAMES_SYMBOLS,
                                          aSymbolMap.mxSymbolMap);
            OpCodeList aOpCodeListNative(RID_STRLIST_FUNCTION_NAMES, aSymbolMap.mxSymbolMap);
            // No AddInMap for native core mapping.
        }
 
        xMap = aSymbolMap.mxSymbolMap;
    }
 
    return true;
}
 
const OUString& FormulaCompiler::GetNativeSymbol( OpCode eOp )
{
    NonConstOpCodeMapPtr xSymbolsNative;
    lcl_fillNativeSymbols( xSymbolsNative);
    return xSymbolsNative->getSymbol( eOp );
}
 
sal_Unicode FormulaCompiler::GetNativeSymbolChar( OpCode eOp )
{
    return GetNativeSymbol(eOp)[0];
}
 
bool FormulaCompiler::InitSymbolsNative( FormulaCompiler::InitSymbols eWhat ) const
{
    return lcl_fillNativeSymbols( mxSymbolsNative, eWhat);
}
 
bool FormulaCompiler::InitSymbolsEnglish( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
    mxSymbolsEnglish = aMap.mxSymbolMap;
    return true;
}
 
bool FormulaCompiler::InitSymbolsPODF( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF, FormulaGrammar::GRAM_PODF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
    mxSymbolsPODF = aMap.mxSymbolMap;
    return true;
}
 
bool FormulaCompiler::InitSymbolsAPI( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_API, FormulaGrammar::GRAM_API, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
    mxSymbolsAPI = aMap.mxSymbolMap;
    return true;
}
 
bool FormulaCompiler::InitSymbolsODFF( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF, FormulaGrammar::GRAM_ODFF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
    mxSymbolsODFF = aMap.mxSymbolMap;
    return true;
}
 
bool FormulaCompiler::InitSymbolsEnglishXL( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
    mxSymbolsEnglishXL = aMap.mxSymbolMap;
    if (eWhat != InitSymbols::INIT)
        return true;
 
    // TODO: For now, just replace the separators to the Excel English
    // variants. Later, if we want to properly map Excel functions with Calc
    // functions, we'll need to do a little more work here.
    mxSymbolsEnglishXL->putOpCode( OUString(','), ocSep, nullptr);
    mxSymbolsEnglishXL->putOpCode( OUString(','), ocArrayColSep, nullptr);
    mxSymbolsEnglishXL->putOpCode( OUString(';'), ocArrayRowSep, nullptr);
 
    return true;
}
 
bool FormulaCompiler::InitSymbolsOOXML( FormulaCompiler::InitSymbols eWhat ) const
{
    static OpCodeMapData aMap;
    std::unique_lock aGuard(aMap.maMtx);
    if (eWhat == InitSymbols::ASK)
        return bool(aMap.mxSymbolMap);
    else if (eWhat == InitSymbols::DESTROY)
        aMap.mxSymbolMap.reset();
    else if (!aMap.mxSymbolMap)
        loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML, FormulaGrammar::GRAM_OOXML, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
    mxSymbolsOOXML = aMap.mxSymbolMap;
    return true;
}
 
 
void FormulaCompiler::loadSymbols(const std::pair<const char*, int>* pSymbols, FormulaGrammar::Grammar eGrammar,
        NonConstOpCodeMapPtr& rxMap, SeparatorType eSepType) const
{
    if ( rxMap )
        return;
 
    // not Core
    rxMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, eGrammar != FormulaGrammar::GRAM_ODFF, eGrammar );
    OpCodeList aOpCodeList(pSymbols, rxMap, eSepType);
 
    fillFromAddInMap( rxMap, eGrammar);
    // Fill from collection for AddIns not already present.
    if (FormulaGrammar::GRAM_ENGLISH == eGrammar)
        fillFromAddInCollectionEnglishName( rxMap);
    else
    {
        fillFromAddInCollectionUpperName( rxMap);
        if (FormulaGrammar::GRAM_API == eGrammar)
        {
            // Add known but not in AddInMap English names, e.g. from the
            // PricingFunctions AddIn or any user supplied AddIn.
            fillFromAddInCollectionEnglishName( rxMap);
        }
        else if (FormulaGrammar::GRAM_OOXML == eGrammar)
        {
            // Add specified Add-In compatibility name.
            fillFromAddInCollectionExcelName( rxMap);
        }
    }
}
 
void FormulaCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& /*xMap */) const
{
}
 
void FormulaCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& /*xMap */) const
{
}
 
void FormulaCompiler::fillFromAddInCollectionExcelName( const NonConstOpCodeMapPtr& /*xMap */) const
{
}
 
void FormulaCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& /*xMap*/, FormulaGrammar::Grammar /*_eGrammar */) const
{
}
 
OpCode FormulaCompiler::GetEnglishOpCode( const OUString& rName ) const
{
    FormulaCompiler::OpCodeMapPtr xMap = GetOpCodeMap( sheet::FormulaLanguage::ENGLISH);
 
    formula::OpCodeHashMap::const_iterator iLook( xMap->getHashMap().find( rName ) );
    bool bFound = (iLook != xMap->getHashMap().end());
    return bFound ? (*iLook).second : ocNone;
}
 
bool FormulaCompiler::IsOpCodeVolatile( OpCode eOp )
{
    bool bRet = false;
    switch (eOp)
    {
        // no parameters:
        case ocRandom:
        case ocGetActDate:
        case ocGetActTime:
        // one parameter:
        case ocFormula:
        case ocInfo:
        // more than one parameters:
            // ocIndirect otherwise would have to do
            // StopListening and StartListening on a reference for every
            // interpreted value.
        case ocIndirect:
            // ocOffset results in indirect references.
        case ocOffset:
            // ocDebugVar shows internal value that may change as the internal state changes.
        case ocDebugVar:
            // ocRandArray is a volatile function.
        case ocRandArray:
            bRet = true;
            break;
        default:
            bRet = false;
            break;
    }
    return bRet;
}
 
bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp )
{
    switch (eOp)
    {
        case ocIf:
        case ocIfError:
        case ocIfNA:
        case ocChoose:
        case ocLet:
            return true;
        default:
            ;
    }
    return false;
}
 
// Remove quotes, escaped quotes are unescaped.
bool FormulaCompiler::DeQuote( OUString& rStr )
{
    sal_Int32 nLen = rStr.getLength();
    if ( nLen > 1 && rStr[0] == '\'' && rStr[ nLen-1 ] == '\'' )
    {
        rStr = rStr.copy( 1, nLen-2 );
        rStr = rStr.replaceAll( "''", "'" );
        return true;
    }
    return false;
}
 
void FormulaCompiler::fillAddInToken(
        ::std::vector< sheet::FormulaOpCodeMapEntry >& /*_rVec*/,
        bool /*_bIsEnglish*/) const
{
}
 
bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode )
{
    switch (eOpCode)
    {
        case ocDde :
        case ocGrowth :
        case ocTrend :
        case ocLogest :
        case ocLinest :
        case ocFrequency :
        case ocMatSequence :
        case ocMatTrans :
        case ocMatMult :
        case ocMatInv :
        case ocMatrixUnit :
        case ocModalValue_Multi :
        case ocFourier :
        case ocFilter :
        case ocSort :
        case ocSortBy :
        case ocRandArray :
        case ocUnique :
        case ocLet :
            return true;
        default:
        {
            // added to avoid warnings
        }
    }
    return false;
}
 
 
void FormulaCompiler::OpCodeMap::putCopyOpCode( const OUString& rSymbol, OpCode eOp, const CharClass* pCharClass )
{
    SAL_WARN_IF( !mpTable[eOp].isEmpty() && rSymbol.isEmpty(), "formula.core",
            "OpCodeMap::putCopyOpCode: NOT replacing OpCode " << static_cast<sal_uInt16>(eOp)
            << " '" << mpTable[eOp] << "' with empty name!");
    if (!mpTable[eOp].isEmpty() && rSymbol.isEmpty())
    {
        OUString aUpper( pCharClass ? pCharClass->uppercase( mpTable[eOp]) : mpTable[eOp].toAsciiUpperCase());
        maHashMap.emplace(aUpper, eOp);
    }
    else
    {
        OUString aUpper( pCharClass ? pCharClass->uppercase( rSymbol) : rSymbol.toAsciiUpperCase());
        mpTable[eOp] = rSymbol;
        maHashMap.emplace(aUpper, eOp);
    }
}
 
void FormulaCompiler::OpCodeMap::copyFrom( const OpCodeMap& r )
{
    maHashMap = OpCodeHashMap( mnSymbols);
 
    sal_uInt16 n = r.getSymbolCount();
    SAL_WARN_IF( n != mnSymbols, "formula.core",
            "OpCodeMap::copyFrom: unequal size, this: " << mnSymbols << "  that: " << n);
    if (n > mnSymbols)
        n = mnSymbols;
 
    // OpCode 0 (ocPush) should never be in a map.
    SAL_WARN_IF( !mpTable[0].isEmpty() || !r.mpTable[0].isEmpty(), "formula.core",
            "OpCodeMap::copyFrom: OpCode 0 assigned, this: '"
            << mpTable[0] << "'  that: '" << r.mpTable[0] << "'");
 
    std::unique_ptr<CharClass> xCharClass( r.mbEnglish ? nullptr : createCharClassIfNonEnglishUI());
    const CharClass* pCharClass = xCharClass.get();
 
    // For bOverrideKnownBad when copying from the English core map (ODF 1.1
    // and API) to the native map (UI "use English function names") replace the
    // known bad legacy function names with correct ones.
    if (r.mbCore &&
            FormulaGrammar::extractFormulaLanguage( meGrammar) == sheet::FormulaLanguage::NATIVE &&
            FormulaGrammar::extractFormulaLanguage( r.meGrammar) == sheet::FormulaLanguage::ENGLISH)
    {
        for (sal_uInt16 i = 1; i < n; ++i)
        {
            OUString aSymbol;
            OpCode eOp = OpCode(i);
            switch (eOp)
            {
                case ocRRI:
                    aSymbol = "RRI";
                    break;
                case ocTableOp:
                    aSymbol = "MULTIPLE.OPERATIONS";
                    break;
                default:
                    aSymbol = r.mpTable[i];
            }
            putCopyOpCode( aSymbol, eOp, pCharClass);
        }
    }
    else
    {
        for (sal_uInt16 i = 1; i < n; ++i)
        {
            OpCode eOp = OpCode(i);
            const OUString& rSymbol = r.mpTable[i];
            putCopyOpCode( rSymbol, eOp, pCharClass);
        }
    }
 
    // This was meant to copy to native map that does not have AddIn symbols
    // but needs them from the source map. It is unclear what should happen if
    // the destination already had externals, so do it only if it doesn't.
    if (!hasExternals())
    {
        maExternalHashMap = r.maExternalHashMap;
        maReverseExternalHashMap = r.maReverseExternalHashMap;
        mbCore = r.mbCore;
        if (mbEnglish != r.mbEnglish)
        {
            // For now keep mbEnglishLocale setting, which is false for a
            // non-English native map we're copying to.
            /* TODO:
            if (!mbEnglish && r.mbEnglish)
                mbEnglishLocale = "getUseEnglishLocaleFromConfiguration()";
            or set from outside i.e. via ScCompiler.
            */
            mbEnglish = r.mbEnglish;
        }
    }
}
 
 
FormulaError FormulaCompiler::GetErrorConstant( const OUString& rName ) const
{
    FormulaError nError = FormulaError::NONE;
    OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
    if (iLook != mxSymbols->getHashMap().end())
    {
        switch ((*iLook).second)
        {
            // Not all may make sense in a formula, but these we know as
            // opcodes.
            case ocErrNull:
                nError = FormulaError::NoCode;
                break;
            case ocErrDivZero:
                nError = FormulaError::DivisionByZero;
                break;
            case ocErrValue:
                nError = FormulaError::NoValue;
                break;
            case ocErrRef:
                nError = FormulaError::NoRef;
                break;
            case ocErrName:
                nError = FormulaError::NoName;
                break;
            case ocErrNum:
                nError = FormulaError::IllegalFPOperation;
                break;
            case ocErrNA:
                nError = FormulaError::NotAvailable;
                break;
            default:
                ;   // nothing
        }
    }
    else
    {
        // Per convention recognize detailed "#ERRxxx!" constants, always
        // untranslated. Error numbers are sal_uInt16 so at most 5 decimal
        // digits.
        if (rName.startsWithIgnoreAsciiCase("#ERR") && rName.getLength() <= 10 && rName[rName.getLength()-1] == '!')
        {
            sal_uInt32 nErr = o3tl::toUInt32(rName.subView( 4, rName.getLength() - 5));
            if (0 < nErr && nErr <= SAL_MAX_UINT16 && isPublishedFormulaError(static_cast<FormulaError>(nErr)))
                nError = static_cast<FormulaError>(nErr);
        }
    }
    return nError;
}
 
void FormulaCompiler::EnableJumpCommandReorder( bool bEnable )
{
    mbJumpCommandReorder = bEnable;
}
 
void FormulaCompiler::EnableStopOnError( bool bEnable )
{
    mbStopOnError = bEnable;
}
 
void FormulaCompiler::AppendErrorConstant( OUStringBuffer& rBuffer, FormulaError nError ) const
{
    OpCode eOp;
    switch (nError)
    {
        case FormulaError::NoCode:
            eOp = ocErrNull;
            break;
        case FormulaError::DivisionByZero:
            eOp = ocErrDivZero;
            break;
        case FormulaError::NoValue:
            eOp = ocErrValue;
            break;
        case FormulaError::NoRef:
            eOp = ocErrRef;
            break;
        case FormulaError::NoName:
            eOp = ocErrName;
            break;
        case FormulaError::IllegalFPOperation:
            eOp = ocErrNum;
            break;
        case FormulaError::NotAvailable:
            eOp = ocErrNA;
            break;
        default:
            {
                // Per convention create detailed "#ERRxxx!" constants, always
                // untranslated.
                rBuffer.append("#ERR");
                rBuffer.append(static_cast<sal_Int32>(nError));
                rBuffer.append('!');
                return;
            }
    }
    rBuffer.append( mxSymbols->getSymbol( eOp));
}
 
constexpr short nRecursionMax = 100;
 
bool FormulaCompiler::GetToken()
{
    FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
    if ( nRecursion > nRecursionMax )
    {
        SetError( FormulaError::StackOverflow );
        mpLastToken = mpToken = new FormulaByteToken( ocStop );
        return false;
    }
    if ( bAutoCorrect && !pStack )
    {   // don't merge stacked subroutine code into entered formula
        aCorrectedFormula += aCorrectedSymbol;
        aCorrectedSymbol.clear();
    }
    bool bStop = false;
    if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
        bStop = true;
    else
    {
        FormulaTokenRef pSpacesToken;
        short nWasColRowName;
        if ( pArr->OpCodeBefore( maArrIterator.GetIndex() ) == ocColRowName )
             nWasColRowName = 1;
        else
             nWasColRowName = 0;
        OpCode eTmpOp;
        mpToken = maArrIterator.Next();
        while (mpToken && ((eTmpOp = mpToken->GetOpCode()) == ocSpaces || eTmpOp == ocWhitespace))
        {
            if (eTmpOp == ocSpaces)
            {
                // For significant whitespace remember last ocSpaces token.
                // Usually there's only one even for multiple spaces.
                pSpacesToken = mpToken;
                if ( nWasColRowName )
                    nWasColRowName++;
            }
            if ( bAutoCorrect && !pStack )
                CreateStringFromToken( aCorrectedFormula, mpToken.get() );
            mpToken = maArrIterator.Next();
        }
        if ( bAutoCorrect && !pStack && mpToken )
            CreateStringFromToken( aCorrectedSymbol, mpToken.get() );
        if( !mpToken )
        {
            if( pStack )
            {
                PopTokenArray();
                // mpLastToken was popped as well and corresponds to the
                // then current last token during PushTokenArray(), e.g. for
                // HandleRange().
                return GetToken();
            }
            else
                bStop = true;
        }
        else
        {
            if ( nWasColRowName >= 2 && mpToken->GetOpCode() == ocColRowName )
            {   // convert an ocSpaces to ocIntersect in RPN
                mpLastToken = mpToken = new FormulaByteToken( ocIntersect );
                maArrIterator.StepBack();     // we advanced to the second ocColRowName, step back
            }
            else if (pSpacesToken && FormulaGrammar::isExcelSyntax( meGrammar) &&
                    mpLastToken && mpToken &&
                    isPotentialRangeType( mpToken.get(), false, true) &&
                    (mpLastToken->GetOpCode() == ocClose || isPotentialRangeType( mpLastToken.get(), false, false)))
            {
                // Let IntersectionLine() <- Factor() decide how to treat this,
                // once the actual arguments are determined in RPN.
                mpLastToken = mpToken = std::move(pSpacesToken);
                maArrIterator.StepBack();     // step back from next non-spaces token
                return true;
            }
        }
    }
    if( bStop )
    {
        mpLastToken = mpToken = new FormulaByteToken( ocStop );
        return false;
    }
 
    // Remember token for next round and any PushTokenArray() calls that may
    // occur in handlers.
    mpLastToken = mpToken;
 
    if ( mpToken->IsExternalRef() )
    {
        return HandleExternalReference(*mpToken);
    }
    else
    {
        switch (mpToken->GetOpCode())
        {
            case ocSubTotal:
            case ocAggregate:
                glSubTotal = true;
                break;
            case ocStringName:
                if( HandleStringName())
                    return true;
                else
                    return false;
            case ocName:
                if( HandleRange())
                {
                    // Expanding ocName might have introduced tokens such as ocStyle that prevent formula threading,
                    // but those wouldn't be present in the raw tokens array, so ensure RPN tokens will be checked too.
                    needsRPNTokenCheck = true;
                    return true;
                }
                return false;
            case ocColRowName:
                return HandleColRowName();
            case ocDBArea:
                return HandleDbData();
            case ocTableRef:
                return HandleTableRef();
            case ocPush:
                if( mbComputeII )
                    HandleIIOpCode(mpToken.get(), nullptr, 0);
                break;
            default:
                ;   // nothing
        }
    }
    return true;
}
 
 
// RPN creation by recursion
void FormulaCompiler::Factor()
{
    if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
        return;
 
    CurrentFactor pFacToken( this );
 
    OpCode eOp = mpToken->GetOpCode();
    if (eOp == ocPush || eOp == ocColRowNameAuto || eOp == ocMatRef || eOp == ocDBArea
        || eOp == ocTableRef
        || (!mbJumpCommandReorder && ((eOp == ocName) || (eOp == ocColRowName) || (eOp == ocBad)))
       )
    {
        PutCode( mpToken );
        eOp = NextToken();
        if( eOp == ocOpen )
        {
            // PUSH( is an error that may be caused by an unknown function.
            SetError(
                ( mpToken->GetType() == svString
               || mpToken->GetType() == svSingleRef )
               ? FormulaError::NoName : FormulaError::OperatorExpected );
            if ( bAutoCorrect && !pStack )
            {   // assume multiplication
                aCorrectedFormula += mxSymbols->getSymbol( ocMul);
                bCorrected = true;
                NextToken();
                eOp = Expression();
                if( eOp != ocClose )
                    SetError( FormulaError::PairExpected);
                else
                    NextToken();
            }
        }
    }
    else if( eOp == ocOpen )
    {
        NextToken();
        eOp = Expression();
        while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
        {   // range list  (A1;A2)  converted to  (A1~A2)
            pFacToken = mpToken;
            NextToken();
            CheckSetForceArrayParameter( mpToken, 0);
            eOp = Expression();
            // Do not ignore error here, regardless of mbStopOnError, to not
            // change the formula expression in case of an unexpected state.
            if (pArr->GetCodeError() == FormulaError::NONE && pc >= 2)
            {
                // Left and right operands must be reference or function
                // returning reference to form a range list.
                const FormulaToken* p = pCode[-2];
                if (p && isPotentialRangeType( p, true, false))
                {
                    p = pCode[-1];
                    if (p && isPotentialRangeType( p, true, true))
                    {
                        pFacToken->NewOpCode( ocUnion, FormulaToken::PrivateAccess());
                        // XXX NOTE: the token's eType is still svSep here!
                        PutCode( pFacToken);
                    }
                }
            }
        }
        if (eOp != ocClose)
            SetError( FormulaError::PairExpected);
        else
            NextToken();
 
        /* TODO: if no conversion to ocUnion is involved this could collect
         * such expression as a list or (matrix) vector to be passed as
         * argument for one parameter (which in fact the ocUnion svRefList is a
         * special case of), which would require a new StackVar type and needed
         * to be handled by the interpreter for functions that could support it
         * (i.e. already handle VAR_ARGS or svRefList parameters). This is also
         * not defined by ODF.
         * Does Excel handle =SUM((1;2))?
         * As is, the interpreter catches extraneous uncalculated
         * subexpressions like 1 of (1;2) as error. */
    }
    else
    {
        if( nNumFmt == SvNumFormatType::UNDEFINED )
            nNumFmt = lcl_GetRetFormat( eOp );
 
        if ( IsOpCodeVolatile( eOp) )
            pArr->SetExclusiveRecalcModeAlways();
        else
        {
            switch( eOp )
            {
                    // Functions recalculated on every document load.
                    // ONLOAD_LENIENT here to be able to distinguish and not
                    // force a recalc (if not in an ALWAYS or ONLOAD_MUST
                    // context) but keep an imported result from for example
                    // OOXML a DDE call. Will be recalculated for ODFF.
                case ocConvertOOo :
                case ocDde:
                case ocMacro:
                case ocWebservice:
                    pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
                break;
                    // RANDBETWEEN() is volatile like RAND(). Other Add-In
                    // functions may have to be recalculated or not, we don't
                    // know, classify as ONLOAD_LENIENT.
                case ocExternal:
                    if (mpToken->GetExternal() == "com.sun.star.sheet.addin.Analysis.getRandbetween")
                        pArr->SetExclusiveRecalcModeAlways();
                    else
                        pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
                break;
                    // If the referred cell is moved the value changes.
                case ocColumn :
                case ocRow :
                    pArr->SetRecalcModeOnRefMove();
                break;
                    // ocCell needs recalc on move for some possible type values.
                    // And recalc mode on load, tdf#60645
                case ocCell :
                    pArr->SetRecalcModeOnRefMove();
                    pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST );
                break;
                case ocHyperLink :
                    // Cell with hyperlink needs to be calculated on load to
                    // get its matrix result generated.
                    pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST );
                    pArr->SetHyperLink( true);
                break;
                default:
                    ;   // nothing
            }
        }
        if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR)
        {
            pFacToken = mpToken;
            eOp = NextToken();
            if (eOp != ocOpen)
            {
                SetError( FormulaError::PairExpected);
                PutCode( pFacToken );
            }
            else
            {
                eOp = NextToken();
                if (eOp != ocClose)
                    SetError( FormulaError::PairExpected);
                PutCode( pFacToken);
                NextToken();
            }
        }
        else if (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR)
        {
            if (eOp == ocIsoWeeknum && FormulaGrammar::isODFF( meGrammar ))
            {
                // tdf#50950 ocIsoWeeknum can have 2 arguments when saved by older versions of Calc;
                // the opcode then has to be changed to ocWeek for backward compatibility
                pFacToken = mpToken;
                eOp = NextToken();
                bool bNoParam = false;
                if (eOp == ocOpen)
                {
                    eOp = NextToken();
                    if (eOp == ocClose)
                        bNoParam = true;
                    else
                    {
                        CheckSetForceArrayParameter( mpToken, 0);
                        eOp = Expression();
                    }
                }
                else
                    SetError( FormulaError::PairExpected);
                sal_uInt32 nSepCount = 0;
                const sal_uInt16 nSepPos = maArrIterator.GetIndex() - 1;    // separator position, if any
                if( !bNoParam )
                {
                    nSepCount++;
                    while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
                    {
                        NextToken();
                        CheckSetForceArrayParameter( mpToken, nSepCount);
                        nSepCount++;
                        if (nSepCount > FORMULA_MAXPARAMS)
                            SetError( FormulaError::CodeOverflow);
                        eOp = Expression();
                    }
                }
                if (eOp != ocClose)
                    SetError( FormulaError::PairExpected);
                else
                    NextToken();
                pFacToken->SetByte( nSepCount );
                if (nSepCount == 2)
                {
                    // An old mode!=1 indicates ISO week, remove argument if
                    // literal double value and keep function. Anything else
                    // can not be resolved, there exists no "like ISO but week
                    // starts on Sunday" mode in WEEKNUM and for an expression
                    // we can't determine.
                    // Current index is nSepPos+3 if expression stops, or
                    // nSepPos+4 if expression continues after the call because
                    // we just called NextToken() to move away from it.
                    if (pc >= 2 && (maArrIterator.GetIndex() == nSepPos + 3 || maArrIterator.GetIndex() == nSepPos + 4) &&
                            pArr->TokenAt(nSepPos+1)->GetType() == svDouble &&
                            pArr->TokenAt(nSepPos+1)->GetDouble() != 1.0 &&
                            pArr->TokenAt(nSepPos+2)->GetOpCode() == ocClose &&
                            pArr->RemoveToken( nSepPos, 2) == 2)
                    {
                        maArrIterator.AfterRemoveToken( nSepPos, 2);
                        // Remove the ocPush/svDouble just removed also from
                        // the compiler local RPN array.
                        --pCode; --pc;
                        (*pCode)->DecRef(); // may be dead now
                        pFacToken->SetByte( nSepCount - 1 );
                    }
                    else
                    {
                        // For the remaining two arguments cases use the
                        // compatibility function.
                        pFacToken->NewOpCode( ocWeeknumOOo, FormulaToken::PrivateAccess());
                    }
                }
                PutCode( pFacToken );
            }
            else
            {
                // standard handling of 1-parameter opcodes
                pFacToken = mpToken;
                eOp = NextToken();
                if( nNumFmt == SvNumFormatType::UNDEFINED && eOp == ocNot )
                    nNumFmt = SvNumFormatType::LOGICAL;
                if (eOp == ocOpen)
                {
                    NextToken();
                    CheckSetForceArrayParameter( mpToken, 0);
                    eOp = Expression();
                }
                else
                    SetError( FormulaError::PairExpected);
                if (eOp != ocClose)
                    SetError( FormulaError::PairExpected);
                else if ( pArr->GetCodeError() == FormulaError::NONE )
                {
                    pFacToken->SetByte( 1 );
                    if (mbComputeII)
                    {
                        FormulaToken** pArg = pCode - 1;
                        HandleIIOpCode(pFacToken, &pArg, 1);
                    }
                }
                PutCode( pFacToken );
                NextToken();
            }
        }
        else if ((SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR)
                || eOp == ocExternal
                || eOp == ocMacro
                || eOp == ocAnd
                || eOp == ocOr
                || eOp == ocBad
                || ( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
                || (!mbJumpCommandReorder && IsOpCodeJumpCommand(eOp)))
        {
            pFacToken = mpToken;
            OpCode eMyLastOp = eOp;
            eOp = NextToken();
            bool bNoParam = false;
            bool bBadName = false;
            if (eOp == ocOpen)
            {
                eOp = NextToken();
                if (eOp == ocClose)
                    bNoParam = true;
                else
                {
                    CheckSetForceArrayParameter( mpToken, 0);
                    eOp = Expression();
                }
            }
            else if (eMyLastOp == ocBad)
            {
                // Just a bad name, not an unknown function, no parameters, no
                // closing expected.
                bBadName = true;
                bNoParam = true;
            }
            else
                SetError( FormulaError::PairExpected);
            sal_uInt32 nSepCount = 0;
            if( !bNoParam )
            {
                bool bDoIICompute = mbComputeII;
                // Array of FormulaToken double pointers to collect the parameters of II opcodes.
                FormulaToken*** pArgArray = nullptr;
                if (bDoIICompute)
                {
                    pArgArray = static_cast<FormulaToken***>(alloca(sizeof(FormulaToken**)*FORMULA_MAXPARAMSII));
                    if (!pArgArray)
                        bDoIICompute = false;
                }
 
                nSepCount++;
 
                if (bDoIICompute)
                    pArgArray[nSepCount-1] = pCode - 1; // Add first argument
 
                while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
                {
                    NextToken();
                    CheckSetForceArrayParameter( mpToken, nSepCount);
                    nSepCount++;
                    if (nSepCount > FORMULA_MAXPARAMS)
                        SetError( FormulaError::CodeOverflow);
                    eOp = Expression();
                    if (bDoIICompute && nSepCount <= FORMULA_MAXPARAMSII)
                        pArgArray[nSepCount - 1] = pCode - 1; // Add rest of the arguments
                }
                if (bDoIICompute)
                    HandleIIOpCode(pFacToken, pArgArray,
                                   std::min(nSepCount, static_cast<sal_uInt32>(FORMULA_MAXPARAMSII)));
            }
            bool bDone = false;
            if (bBadName)
                ;   // nothing, keep current token for return
            else if (eOp != ocClose)
                SetError( FormulaError::PairExpected);
            else
            {
                NextToken();
                bDone = true;
            }
            // Jumps are just normal functions for the FunctionAutoPilot tree view
            if (!mbJumpCommandReorder && pFacToken->GetType() == svJump)
                pFacToken = new FormulaFAPToken( pFacToken->GetOpCode(), nSepCount, pFacToken );
            else
                pFacToken->SetByte( nSepCount );
            PutCode( pFacToken );
 
            if (bDone)
                AnnotateOperands();
        }
        else if (IsOpCodeJumpCommand(eOp))
        {
            // the PC counters are -1
            pFacToken = mpToken;
            switch (eOp)
            {
                case ocIf:
                    pFacToken->GetJump()[ 0 ] = 3;  // if, else, behind
                    break;
                case ocChoose:
                    pFacToken->GetJump()[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
                    break;
                case ocLet:
                    pFacToken->GetJump()[ 0 ] = FORMULA_MAXPARAMS + 1;
                    break;
                case ocIfError:
                case ocIfNA:
                    pFacToken->GetJump()[ 0 ] = 2;  // if, behind
                    break;
                default:
                    SAL_WARN("formula.core","Jump OpCode: " << +eOp);
                    assert(!"FormulaCompiler::Factor: someone forgot to add a jump count case");
            }
            eOp = NextToken();
            if (eOp == ocOpen)
            {
                NextToken();
                CheckSetForceArrayParameter( mpToken, 0);
                eOp = Expression();
            }
            else
                SetError( FormulaError::PairExpected);
            PutCode( pFacToken );
            // During AutoCorrect (since pArr->GetCodeError() is
            // ignored) an unlimited ocIf would crash because
            // ScRawToken::Clone() allocates the JumpBuffer according to
            // nJump[0]*2+2, which is 3*2+2 on ocIf and 2*2+2 ocIfError and ocIfNA.
            short nJumpMax;
            OpCode eFacOpCode = pFacToken->GetOpCode();
            switch (eFacOpCode)
            {
                case ocIf:
                    nJumpMax = 3;
                    break;
                case ocChoose:
                    nJumpMax = FORMULA_MAXJUMPCOUNT;
                    break;
                case ocLet:
                    nJumpMax = FORMULA_MAXPARAMS;
                    break;
                case ocIfError:
                case ocIfNA:
                    nJumpMax = 2;
                    break;
                case ocStop:
                    // May happen only if PutCode(pFacToken) ran into overflow.
                    nJumpMax = 0;
                    assert(pc == FORMULA_MAXTOKENS && pArr->GetCodeError() != FormulaError::NONE);
                    break;
                default:
                    nJumpMax = 0;
                    SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
                    assert(!"FormulaCompiler::Factor: someone forgot to add a jump max case");
            }
            short nJumpCount = 0;
            while ( (nJumpCount < (FORMULA_MAXPARAMS - 1)) && (eOp == ocSep)
                    && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
            {
                if ( ++nJumpCount <= nJumpMax )
                    pFacToken->GetJump()[nJumpCount] = pc-1;
                NextToken();
                CheckSetForceArrayParameter( mpToken, nJumpCount - 1);
                eOp = Expression();
                // ocSep or ocClose terminate the subexpression
                PutCode( mpToken );
            }
            if (eOp != ocClose)
                SetError( FormulaError::PairExpected);
            else
            {
                NextToken();
                // always limit to nJumpMax, no arbitrary overwrites
                if ( ++nJumpCount <= nJumpMax )
                    pFacToken->GetJump()[ nJumpCount ] = pc-1;
                eFacOpCode = pFacToken->GetOpCode();
                bool bLimitOk;
                switch (eFacOpCode)
                {
                    case ocIf:
                        bLimitOk = (nJumpCount <= 3);
                        break;
                    case ocChoose:
                        bLimitOk = (nJumpCount < FORMULA_MAXJUMPCOUNT);
                        break;
                    case ocLet:
                        bLimitOk = (nJumpCount < FORMULA_MAXPARAMS);
                        break;
                    case ocIfError:
                    case ocIfNA:
                        bLimitOk = (nJumpCount <= 2);
                        break;
                    case ocStop:
                        // May happen only if PutCode(pFacToken) ran into overflow.
                        // This may had resulted from a stacked token array and
                        // error wasn't propagated so assert only the program
                        // counter.
                        bLimitOk = false;
                        assert(pc == FORMULA_MAXTOKENS);
                        break;
                    default:
                        bLimitOk = false;
                        SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
                        assert(!"FormulaCompiler::Factor: someone forgot to add a jump limit case");
                }
                if (bLimitOk)
                    pFacToken->GetJump()[ 0 ] = nJumpCount;
                else
                    SetError( FormulaError::IllegalParameter);
            }
        }
        else if ( eOp == ocMissing )
        {
            PutCode( mpToken );
            NextToken();
        }
        else if ( eOp == ocClose )
        {
            SetError( FormulaError::ParameterExpected );
        }
        else if ( eOp == ocSep )
        {   // Subsequent ocSep
            SetError( FormulaError::ParameterExpected );
            if ( bAutoCorrect && !pStack )
            {
                aCorrectedSymbol.clear();
                bCorrected = true;
            }
        }
        else if ( mpToken->IsExternalRef() )
        {
            PutCode( mpToken);
            NextToken();
        }
        else
        {
            SetError( FormulaError::UnknownToken );
            if ( bAutoCorrect && !pStack )
            {
                if ( eOp == ocStop )
                {   // trailing operator w/o operand
                    sal_Int32 nLen = aCorrectedFormula.getLength();
                    if ( nLen )
                        aCorrectedFormula = aCorrectedFormula.copy( 0, nLen - 1 );
                    aCorrectedSymbol.clear();
                    bCorrected = true;
                }
            }
        }
    }
}
 
void FormulaCompiler::RangeLine()
{
    Factor();
    while (mpToken->GetOpCode() == ocRange)
    {
        FormulaToken** pCode1 = pCode - 1;
        FormulaTokenRef p = mpToken;
        NextToken();
        Factor();
        FormulaToken** pCode2 = pCode - 1;
        if (!MergeRangeReference( pCode1, pCode2))
            PutCode(p);
    }
}
 
void FormulaCompiler::IntersectionLine()
{
    RangeLine();
    while (mpToken->GetOpCode() == ocIntersect || mpToken->GetOpCode() == ocSpaces)
    {
        sal_uInt16 nCodeIndex = maArrIterator.GetIndex() - 1;
        FormulaToken** pCode1 = pCode - 1;
        FormulaTokenRef p = mpToken;
        NextToken();
        RangeLine();
        FormulaToken** pCode2 = pCode - 1;
        if (p->GetOpCode() == ocSpaces)
        {
            // Convert to intersection if both left and right are references or
            // functions (potentially returning references, if not then a space
            // or no space would be a syntax error anyway), not other operators
            // or operands. Else discard.
            if (isAdjacentOrGapRpnEnd( pc, pCode, pCode1, pCode2) && isIntersectable( pCode1, pCode2))
            {
                FormulaTokenRef pIntersect( new FormulaByteToken( ocIntersect));
                // Replace ocSpaces with ocIntersect so that when switching
                // formula syntax the correct operator string is created.
                // coverity[freed_arg : FALSE] - FormulaTokenRef has a ref so ReplaceToken won't delete pIntersect
                pArr->ReplaceToken( nCodeIndex, pIntersect.get(), FormulaTokenArray::ReplaceMode::CODE_ONLY);
                PutCode( pIntersect);
            }
        }
        else
        {
            PutCode(p);
        }
    }
}
 
void FormulaCompiler::UnionLine()
{
    IntersectionLine();
    while (mpToken->GetOpCode() == ocUnion)
    {
        FormulaTokenRef p = mpToken;
        NextToken();
        IntersectionLine();
        PutCode(p);
    }
}
 
void FormulaCompiler::UnaryLine()
{
    if( mpToken->GetOpCode() == ocAdd )
        GetToken();
    else if (SC_OPCODE_START_UN_OP <= mpToken->GetOpCode() &&
            mpToken->GetOpCode() < SC_OPCODE_STOP_UN_OP)
    {
        FormulaTokenRef p = mpToken;
        NextToken();
        UnaryLine();
        if (mbComputeII)
        {
            FormulaToken** pArg = pCode - 1;
            HandleIIOpCode(p.get(), &pArg, 1);
        }
        PutCode( p );
    }
    else
        UnionLine();
}
 
void FormulaCompiler::PostOpLine()
{
    UnaryLine();
    while ( mpToken->GetOpCode() == ocPercentSign )
    {   // this operator _follows_ its operand
        if (mbComputeII)
        {
            FormulaToken** pArg = pCode - 1;
            HandleIIOpCode(mpToken.get(), &pArg, 1);
        }
        PutCode( mpToken );
        NextToken();
    }
}
 
void FormulaCompiler::PowLine()
{
    PostOpLine();
    while (mpToken->GetOpCode() == ocPow)
    {
        FormulaTokenRef p = mpToken;
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        PostOpLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
}
 
void FormulaCompiler::MulDivLine()
{
    PowLine();
    while (mpToken->GetOpCode() == ocMul || mpToken->GetOpCode() == ocDiv)
    {
        FormulaTokenRef p = mpToken;
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        PowLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
}
 
void FormulaCompiler::AddSubLine()
{
    MulDivLine();
    while (mpToken->GetOpCode() == ocAdd || mpToken->GetOpCode() == ocSub)
    {
        FormulaTokenRef p = mpToken;
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        MulDivLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
}
 
void FormulaCompiler::ConcatLine()
{
    AddSubLine();
    while (mpToken->GetOpCode() == ocAmpersand)
    {
        FormulaTokenRef p = mpToken;
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        AddSubLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
}
 
void FormulaCompiler::CompareLine()
{
    ConcatLine();
    while (mpToken->GetOpCode() >= ocEqual && mpToken->GetOpCode() <= ocGreaterEqual)
    {
        FormulaTokenRef p = mpToken;
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        ConcatLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
}
 
OpCode FormulaCompiler::Expression()
{
    FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
    if ( nRecursion > nRecursionMax )
    {
        SetError( FormulaError::StackOverflow );
        return ocStop;      //! generate token instead?
    }
    CompareLine();
    while (mpToken->GetOpCode() == ocAnd || mpToken->GetOpCode() == ocOr)
    {
        FormulaTokenRef p = mpToken;
        mpToken->SetByte( 2 );       // 2 parameters!
        FormulaToken** pArgArray[2];
        if (mbComputeII)
            pArgArray[0] = pCode - 1; // Add first argument
        NextToken();
        CompareLine();
        if (mbComputeII)
        {
            pArgArray[1] = pCode - 1; // Add second argument
            HandleIIOpCode(p.get(), pArgArray, 2);
        }
        PutCode(p);
    }
    return mpToken->GetOpCode();
}
 
 
void FormulaCompiler::SetError( FormulaError /*nError*/ )
{
}
 
FormulaTokenRef FormulaCompiler::ExtendRangeReference( FormulaToken & /*rTok1*/, FormulaToken & /*rTok2*/ )
{
    return FormulaTokenRef();
}
 
bool FormulaCompiler::MergeRangeReference( FormulaToken * * const pCode1, FormulaToken * const * const pCode2 )
{
    if (!isAdjacentRpnEnd( pc, pCode, pCode1, pCode2))
        return false;
 
    FormulaToken *p1 = *pCode1, *p2 = *pCode2;
    FormulaTokenRef p = ExtendRangeReference( *p1, *p2);
    if (!p)
        return false;
 
    p->IncRef();
    p1->DecRef();
    p2->DecRef();
    *pCode1 = p.get();
    --pCode;
    --pc;
 
    return true;
}
 
bool FormulaCompiler::CompileTokenArray()
{
    glSubTotal = false;
    bCorrected = false;
    needsRPNTokenCheck = false;
    if (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)
    {
        if ( bAutoCorrect )
        {
            aCorrectedFormula.clear();
            aCorrectedSymbol.clear();
        }
        pArr->DelRPN();
        maArrIterator.Reset();
        pStack = nullptr;
        FormulaToken* pDataArray[ FORMULA_MAXTOKENS + 1 ];
        // Code in some places refers to the last token as 'pCode - 1', which may
        // point before the first element if the expression is bad. So insert a dummy
        // node in that place which will make that token be nullptr.
        pDataArray[ 0 ] = nullptr;
        FormulaToken** pData = pDataArray + 1;
        pCode = pData;
        bool bWasForced = pArr->IsRecalcModeForced();
        if ( bWasForced && bAutoCorrect )
            aCorrectedFormula = "=";
        pArr->ClearRecalcMode();
        maArrIterator.Reset();
        eLastOp = ocOpen;
        pc = 0;
        NextToken();
        OpCode eOp = Expression();
        // Some trailing garbage that doesn't form an expression?
        if (eOp != ocStop)
            SetError( FormulaError::OperatorExpected);
        PostProcessCode();
 
        FormulaError nErrorBeforePop = pArr->GetCodeError();
 
        while( pStack )
            PopTokenArray();
        if( pc )
        {
            pArr->CreateNewRPNArrayFromData( pData, pc );
            if( needsRPNTokenCheck )
                pArr->CheckAllRPNTokens();
        }
 
        // once an error, always an error
        if( pArr->GetCodeError() == FormulaError::NONE && nErrorBeforePop != FormulaError::NONE )
            pArr->SetCodeError( nErrorBeforePop);
 
        if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
        {
            pArr->DelRPN();
            maArrIterator.Reset();
            pArr->SetHyperLink( false);
        }
 
        if ( bWasForced )
            pArr->SetRecalcModeForced();
    }
    if( nNumFmt == SvNumFormatType::UNDEFINED )
        nNumFmt = SvNumFormatType::NUMBER;
    return glSubTotal;
}
 
void FormulaCompiler::PopTokenArray()
{
    if( !pStack )
        return;
 
    FormulaArrayStack* p = pStack;
    pStack = p->pNext;
    // obtain special RecalcMode from SharedFormula
    if ( pArr->IsRecalcModeAlways() )
        p->pArr->SetExclusiveRecalcModeAlways();
    else if ( !pArr->IsRecalcModeNormal() && p->pArr->IsRecalcModeNormal() )
        p->pArr->SetMaskedRecalcMode( pArr->GetRecalcMode() );
    p->pArr->SetCombinedBitsRecalcMode( pArr->GetRecalcMode() );
    if ( pArr->IsHyperLink() )  // fdo 87534
        p->pArr->SetHyperLink( true );
    if( p->bTemp )
        delete pArr;
    pArr = p->pArr;
    maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
    maArrIterator.Jump(p->nIndex);
    mpLastToken = p->mpLastToken;
    delete p;
}
 
void FormulaCompiler::CreateStringFromTokenArray( OUString& rFormula )
{
    OUStringBuffer aBuffer( pArr->GetLen() * 5 );
    CreateStringFromTokenArray( aBuffer );
    rFormula = aBuffer.makeStringAndClear();
}
 
void FormulaCompiler::CreateStringFromTokenArray( OUStringBuffer& rBuffer )
{
    rBuffer.setLength(0);
    if( !pArr->GetLen() )
        return;
 
    FormulaTokenArray* pSaveArr = pArr;
    int nSaveIndex = maArrIterator.GetIndex();
    bool bODFF = FormulaGrammar::isODFF( meGrammar);
    if (bODFF || FormulaGrammar::isPODF( meGrammar) )
    {
        // Scan token array for missing args and re-write if present.
        MissingConventionODF aConv( bODFF);
        if (pArr->NeedsPodfRewrite( aConv))
        {
            pArr = pArr->RewriteMissing( aConv );
            maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
        }
    }
    else if ( FormulaGrammar::isOOXML( meGrammar ) )
    {
        // Scan token array for missing args and rewrite if present.
        if (pArr->NeedsOoxmlRewrite())
        {
            MissingConventionOOXML aConv;
            pArr = pArr->RewriteMissing( aConv );
            maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
        }
    }
 
    // At least one character per token, plus some are references, some are
    // function names, some are numbers, ...
    rBuffer.ensureCapacity( pArr->GetLen() * 5 );
 
    if ( pArr->IsRecalcModeForced() )
        rBuffer.append( '=');
    const FormulaToken* t = maArrIterator.First();
    while( t )
        t = CreateStringFromToken( rBuffer, t, true );
 
    if (pSaveArr != pArr)
    {
        delete pArr;
        pArr = pSaveArr;
        maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
        maArrIterator.Jump(nSaveIndex);
    }
}
 
const FormulaToken* FormulaCompiler::CreateStringFromToken( OUString& rFormula, const FormulaToken* pTokenP )
{
    OUStringBuffer aBuffer;
    const FormulaToken* p = CreateStringFromToken( aBuffer, pTokenP );
    rFormula += aBuffer;
    return p;
}
 
const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuffer, const FormulaToken* pTokenP,
        bool bAllowArrAdvance )
{
    bool bNext = true;
    bool bSpaces = false;
    const FormulaToken* t = pTokenP;
    OpCode eOp = t->GetOpCode();
    if( eOp >= ocAnd && eOp <= ocOr )
    {
        // AND, OR infix?
        if ( bAllowArrAdvance )
            t = maArrIterator.Next();
        else
            t = maArrIterator.PeekNext();
        bNext = false;
        bSpaces = ( !t || t->GetOpCode() != ocOpen );
    }
    if( bSpaces )
        rBuffer.append( ' ');
 
    if (eOp == ocSpaces || eOp == ocWhitespace)
    {
        bool bWriteSpaces = true;
        if (eOp == ocSpaces && mxSymbols->isODFF())
        {
            const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
            bool bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
            if (bIntersectionOp)
            {
                p = maArrIterator.PeekNextNoSpaces();
                bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
            }
            if (bIntersectionOp)
            {
                rBuffer.append( "!!");
                bWriteSpaces = false;
            }
        }
        if (bWriteSpaces)
        {
            // ODF v1.3 OpenFormula 5.14 Whitespace states "whitespace shall
            // not separate a function name from its initial opening
            // parenthesis".
            //
            // ECMA-376-1:2016 18.17.2 Syntax states "that no space characters
            // shall separate a function-name from the left parenthesis (()
            // that follows it." and Excel even chokes on it.
            //
            // Suppress/remove it in any case also in UI, it will not be
            // preserved.
            const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
            if (p && p->IsFunction())
            {
                p = maArrIterator.PeekNextNoSpaces();
                if (p && p->GetOpCode() == ocOpen)
                    bWriteSpaces = false;
            }
        }
        if (bWriteSpaces)
        {
            // most times it's just one blank
            sal_uInt8 n = t->GetByte();
            for ( sal_uInt8 j=0; j<n; ++j )
            {
                if (eOp == ocWhitespace)
                    rBuffer.append( t->GetChar());
                else
                    rBuffer.append( ' ');
            }
        }
    }
    else if( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
        rBuffer.appendAscii( pInternal[ eOp - ocInternalBegin ] );
    else if (eOp == ocIntersect)
    {
        // Nasty, ugly, horrific, terrifying...
        if (FormulaGrammar::isExcelSyntax( meGrammar))
            rBuffer.append(' ');
        else
            rBuffer.append( mxSymbols->getSymbol( eOp));
    }
    else if ( eOp == ocEasterSunday)
    {
        // EASTERSUNDAY belongs to ODFF since ODF 1.4
        if (m_oODFSavingVersion.has_value()
            && m_oODFSavingVersion.value() >= SvtSaveOptions::ODFSVER_012
            && m_oODFSavingVersion.value() < SvtSaveOptions::ODFSVER_014)
            rBuffer.append(u"ORG.OPENOFFICE." + mxSymbols->getSymbol(eOp));
        else
            rBuffer.append(mxSymbols->getSymbol(eOp));
    }
    else if( static_cast<sal_uInt16>(eOp) < mxSymbols->getSymbolCount())        // Keyword:
        rBuffer.append( mxSymbols->getSymbol( eOp));
    else
    {
        SAL_WARN( "formula.core","unknown OpCode");
        rBuffer.append( GetNativeSymbol( ocErrName ));
    }
    if( bNext )
    {
        if (t->IsExternalRef())
        {
            CreateStringFromExternal( rBuffer, pTokenP);
        }
        else
        {
            switch( t->GetType() )
            {
            case svDouble:
                AppendDouble( rBuffer, t->GetDouble() );
            break;
 
            case svString:
                if( eOp == ocBad || eOp == ocStringXML || eOp == ocStringName )
                    rBuffer.append( t->GetString().getString());
                else
                    AppendString( rBuffer, t->GetString().getString() );
                break;
            case svSingleRef:
                CreateStringFromSingleRef( rBuffer, t);
                break;
            case svDoubleRef:
                CreateStringFromDoubleRef( rBuffer, t);
                break;
            case svMatrix:
            case svMatrixCell:
                CreateStringFromMatrix( rBuffer, t );
                break;
 
            case svIndex:
                CreateStringFromIndex( rBuffer, t );
                if (t->GetOpCode() == ocTableRef && bAllowArrAdvance && NeedsTableRefTransformation())
                {
                    // Suppress all TableRef related tokens, the resulting
                    // range was written by CreateStringFromIndex().
                    const FormulaToken* const p = maArrIterator.PeekNext();
                    if (p && p->GetOpCode() == ocTableRefOpen)
                    {
                        int nLevel = 0;
                        do
                        {
                            t = maArrIterator.Next();
                            if (!t)
                                break;
 
                            // Switch cases correspond with those in
                            // ScCompiler::HandleTableRef()
                            switch (t->GetOpCode())
                            {
                                case ocTableRefOpen:
                                    ++nLevel;
                                    break;
                                case ocTableRefClose:
                                    --nLevel;
                                    break;
                                case ocTableRefItemAll:
                                case ocTableRefItemHeaders:
                                case ocTableRefItemData:
                                case ocTableRefItemTotals:
                                case ocTableRefItemThisRow:
                                case ocSep:
                                case ocPush:
                                case ocRange:
                                case ocSpaces:
                                case ocWhitespace:
                                    break;
                                default:
                                    nLevel = 0;
                                    bNext = false;
                            }
                        } while (nLevel);
                    }
                }
                break;
            case svExternal:
            {
                // mapped or translated name of AddIns
                OUString aAddIn( t->GetExternal() );
                bool bMapped = mxSymbols->isPODF();     // ODF 1.1 directly uses programmatical name
                if (!bMapped && mxSymbols->hasExternals())
                {
                    if (mxSymbols->isOOXML())
                    {
                        // Write compatibility name, if any.
                        if (GetExcelName( aAddIn))
                            bMapped = true;
                    }
                    if (!bMapped)
                    {
                        ExternalHashMap::const_iterator iLook = mxSymbols->getReverseExternalHashMap().find( aAddIn);
                        if (iLook != mxSymbols->getReverseExternalHashMap().end())
                        {
                            aAddIn = (*iLook).second;
                            bMapped = true;
                        }
                    }
                }
                if (!bMapped && !mxSymbols->isEnglish())
                    LocalizeString( aAddIn );
                rBuffer.append( aAddIn);
            }
            break;
            case svError:
                AppendErrorConstant( rBuffer, t->GetError());
            break;
            case svByte:
            case svJump:
            case svFAP:
            case svMissing:
            case svSep:
                break;      // Opcodes
            default:
                SAL_WARN("formula.core", "FormulaCompiler::GetStringFromToken: unknown token type " << t->GetType());
            } // of switch
        }
    }
    if( bSpaces )
        rBuffer.append( ' ');
    if ( bAllowArrAdvance )
    {
        if( bNext )
            t = maArrIterator.Next();
        return t;
    }
    return pTokenP;
}
 
 
void FormulaCompiler::AppendDouble( OUStringBuffer& rBuffer, double fVal ) const
{
    if ( mxSymbols->isEnglishLocale() )
    {
        ::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
                rtl_math_StringFormat_Automatic,
                rtl_math_DecimalPlaces_Max, '.', true );
    }
    else
    {
        SvtSysLocale aSysLocale;
        ::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
                rtl_math_StringFormat_Automatic,
                rtl_math_DecimalPlaces_Max,
                aSysLocale.GetLocaleData().getNumDecimalSep()[0],
                true );
    }
}
 
void FormulaCompiler::AppendBoolean( OUStringBuffer& rBuffer, bool bVal ) const
{
    rBuffer.append( mxSymbols->getSymbol( bVal ? ocTrue : ocFalse ) );
}
 
void FormulaCompiler::AppendString( OUStringBuffer& rBuffer, const OUString & rStr )
{
    rBuffer.append( '"');
    if ( lcl_UnicodeStrChr( rStr.getStr(), '"' ) == nullptr )
        rBuffer.append( rStr );
    else
    {
        OUString aStr = rStr.replaceAll( "\"", "\"\"" );
        rBuffer.append(aStr);
    }
    rBuffer.append( '"');
}
 
bool FormulaCompiler::NeedsTableRefTransformation() const
{
    // Currently only UI representations and OOXML export use Table structured
    // references. Not defined in ODFF.
    // Unnecessary to explicitly check for ODFF grammar as the ocTableRefOpen
    // symbol is not defined there.
    return mxSymbols->getSymbol( ocTableRefOpen).isEmpty() || FormulaGrammar::isPODF( meGrammar);
}
 
void FormulaCompiler::UpdateSeparatorsNative(
    const OUString& rSep, const OUString& rArrayColSep, const OUString& rArrayRowSep )
{
    NonConstOpCodeMapPtr xSymbolsNative;
    lcl_fillNativeSymbols( xSymbolsNative);
    xSymbolsNative->putOpCode( rSep, ocSep, nullptr);
    xSymbolsNative->putOpCode( rArrayColSep, ocArrayColSep, nullptr);
    xSymbolsNative->putOpCode( rArrayRowSep, ocArrayRowSep, nullptr);
}
 
void FormulaCompiler::ResetNativeSymbols()
{
    NonConstOpCodeMapPtr xSymbolsNative;
    lcl_fillNativeSymbols( xSymbolsNative, InitSymbols::DESTROY);
    lcl_fillNativeSymbols( xSymbolsNative);
}
 
void FormulaCompiler::SetNativeSymbols( const OpCodeMapPtr& xMap )
{
    NonConstOpCodeMapPtr xSymbolsNative;
    lcl_fillNativeSymbols( xSymbolsNative);
    xSymbolsNative->copyFrom( *xMap );
}
 
 
OpCode FormulaCompiler::NextToken()
{
    if( !GetToken() )
        return ocStop;
    OpCode eOp = mpToken->GetOpCode();
    // There must be an operator before a push
    if ( (eOp == ocPush || eOp == ocColRowNameAuto) &&
            !( (eLastOp == ocOpen) || (eLastOp == ocSep) ||
                (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)) )
        SetError( FormulaError::OperatorExpected);
    // Operator and Plus => operator
    if (eOp == ocAdd && (eLastOp == ocOpen || eLastOp == ocSep ||
                (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
    {
        FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
        eOp = NextToken();
    }
    else
    {
        // Before an operator there must not be another operator, with the
        // exception of AND and OR.
        if ( eOp != ocAnd && eOp != ocOr &&
                (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP )
                && (eLastOp == ocOpen || eLastOp == ocSep ||
                    (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
        {
            SetError( FormulaError::VariableExpected);
            if ( bAutoCorrect && !pStack )
            {
                if ( eOp == eLastOp || eLastOp == ocOpen )
                {   // throw away duplicated operator
                    aCorrectedSymbol.clear();
                    bCorrected = true;
                }
                else
                {
                    sal_Int32 nPos = aCorrectedFormula.getLength();
                    if ( nPos )
                    {
                        nPos--;
                        sal_Unicode c = aCorrectedFormula[ nPos ];
                        switch ( eOp )
                        {   // swap operators
                            case ocGreater:
                                if ( c == mxSymbols->getSymbolChar( ocEqual) )
                                {   // >= instead of =>
                                    aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
                                        rtl::OUStringChar( mxSymbols->getSymbolChar(ocGreater) ) );
                                    aCorrectedSymbol = OUString(c);
                                    bCorrected = true;
                                }
                            break;
                            case ocLess:
                                if ( c == mxSymbols->getSymbolChar( ocEqual) )
                                {   // <= instead of =<
                                    aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
                                        rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) );
                                    aCorrectedSymbol = OUString(c);
                                    bCorrected = true;
                                }
                                else if ( c == mxSymbols->getSymbolChar( ocGreater) )
                                {   // <> instead of ><
                                    aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
                                        rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) );
                                    aCorrectedSymbol = OUString(c);
                                    bCorrected = true;
                                }
                            break;
                            case ocMul:
                                if ( c == mxSymbols->getSymbolChar( ocSub) )
                                {   // *- instead of -*
                                    aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
                                        rtl::OUStringChar( mxSymbols->getSymbolChar(ocMul) ) );
                                    aCorrectedSymbol = OUString(c);
                                    bCorrected = true;
                                }
                            break;
                            case ocDiv:
                                if ( c == mxSymbols->getSymbolChar( ocSub) )
                                {   // /- instead of -/
                                    aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
                                        rtl::OUStringChar( mxSymbols->getSymbolChar(ocDiv) ) );
                                    aCorrectedSymbol = OUString(c);
                                    bCorrected = true;
                                }
                            break;
                            default:
                                ;   // nothing
                        }
                    }
                }
            }
        }
        // Nasty, ugly, horrific, terrifying... significant whitespace...
        if (eOp == ocSpaces && FormulaGrammar::isExcelSyntax( meGrammar))
        {
            // Fake an intersection op as last op for the next round, but at
            // least roughly check if it could make sense at all.
            FormulaToken* pPrev = maArrIterator.PeekPrevNoSpaces();
            if (pPrev && isPotentialRangeType( pPrev, false, false))
            {
                FormulaToken* pNext = maArrIterator.PeekNextNoSpaces();
                if (pNext && isPotentialRangeType( pNext, false, true))
                    eLastOp = ocIntersect;
                else
                    eLastOp = eOp;
            }
            else
                eLastOp = eOp;
        }
        else
            eLastOp = eOp;
    }
    return eOp;
}
 
void FormulaCompiler::PutCode( FormulaTokenRef& p )
{
    if( pc >= FORMULA_MAXTOKENS - 1 )
    {
        if ( pc == FORMULA_MAXTOKENS - 1 )
        {
            SAL_WARN("formula.core", "FormulaCompiler::PutCode - CodeOverflow with OpCode " << +p->GetOpCode());
            p = new FormulaByteToken( ocStop );
            p->IncRef();
            *pCode++ = p.get();
            ++pc;
        }
        SetError( FormulaError::CodeOverflow);
        return;
    }
    if (pArr->GetCodeError() != FormulaError::NONE && mbJumpCommandReorder)
        return;
    ForceArrayOperator( p);
    p->IncRef();
    *pCode++ = p.get();
    pc++;
}
 
 
bool FormulaCompiler::HandleExternalReference( const FormulaToken& /*_aToken*/)
{
    return true;
}
 
bool FormulaCompiler::HandleStringName()
{
    return true;
}
 
bool FormulaCompiler::HandleRange()
{
    return true;
}
 
bool FormulaCompiler::HandleColRowName()
{
    return true;
}
 
bool FormulaCompiler::HandleDbData()
{
    return true;
}
 
bool FormulaCompiler::HandleTableRef()
{
    return true;
}
 
void FormulaCompiler::CreateStringFromSingleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
 
void FormulaCompiler::CreateStringFromDoubleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
 
void FormulaCompiler::CreateStringFromIndex( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
 
void FormulaCompiler::CreateStringFromMatrix( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
 
void FormulaCompiler::CreateStringFromExternal( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
 
void FormulaCompiler::LocalizeString( OUString& /*rName*/ ) const
{
}
 
bool FormulaCompiler::GetExcelName( OUString& /*rName*/ ) const
{
    return false;
}
 
formula::ParamClass FormulaCompiler::GetForceArrayParameter( const FormulaToken* /*pToken*/, sal_uInt16 /*nParam*/ ) const
{
    return ParamClass::Unknown;
}
 
void FormulaCompiler::ForceArrayOperator( FormulaTokenRef const & rCurr )
{
    if (pCurrentFactorToken.get() == rCurr.get())
        return;
 
    const OpCode eOp = rCurr->GetOpCode();
    const StackVar eType = rCurr->GetType();
    const bool bInlineArray = (eOp == ocPush && eType == svMatrix);
 
    if (!bInlineArray)
    {
        if (rCurr->GetInForceArray() != ParamClass::Unknown)
            // Already set, unnecessary to evaluate again. This happens by calls to
            // CurrentFactor::operator=() while descending through Factor() and
            // then ascending back (and down and up, ...),
            // CheckSetForceArrayParameter() and later PutCode().
            return;
 
        if (!(eOp != ocPush && (eType == svByte || eType == svJump)))
            return;
    }
 
    // Return class for inline arrays and functions returning array/matrix.
    // It's somewhat unclear what Excel actually does there and in
    // ECMA-376-1:2016 OOXML mentions "call to ... shall be an array formula"
    // only for FREQUENCY() and TRANSPOSE() but not for any other function
    // returning array/matrix or inline arrays, though for the latter has one
    // example in 18.17.2 Syntax:
    // "SUM(SQRT({1,2,3,4})) returns 6.14 when entered normally". However,
    // these need to be treated similar but not as ParamClass::ForceArray
    // (which would contradict the example in
    // https://bugs.documentfoundation.org/show_bug.cgi?id=122301#c19 and A6 of
    // https://bugs.documentfoundation.org/show_bug.cgi?id=133260#c10 ).
    // See also
    // commit d0ded163d8e93dc5b10d7a7c9bdab1d0a6a50bac
    // commit 5413c8871dec08eff19f514f5f391b946a45c86c
    constexpr ParamClass eArrayReturn = ParamClass::ForceArrayReturn;
 
    if (bInlineArray)
    {
        // rCurr->SetInForceArray() can not be used with ocPush, but ocPush
        // with svMatrix has an implicit ParamClass::ForceArrayReturn.
        if (nCurrentFactorParam > 0 && pCurrentFactorToken
                && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown
                && GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1))
                == ParamClass::Value)
        {
            // Propagate to caller as if a function returning an array/matrix
            // was called (see also below).
            pCurrentFactorToken->SetInForceArray( eArrayReturn);
        }
        return;
    }
 
    if (!pCurrentFactorToken)
    {
        if (mbMatrixFlag)
        {
            // An array/matrix formula acts as ForceArray on all top level
            // operators and function calls, so that can be inherited properly
            // below.
            rCurr->SetInForceArray( ParamClass::ForceArray);
        }
        else if (pc >= 2 && SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP)
        {
            // Binary operators are not functions followed by arguments
            // and need some peeking into RPN to inspect their operands.
            // Note that array context is not forced if only one
            // of the operands is an array like "={1;2}+A1:A2" returns #VALUE!
            // if entered in column A and not input in array mode, because it
            // involves a range reference with an implicit intersection. Check
            // both arguments are arrays, or the other is ocPush without ranges
            // for "={1;2}+3" or "={1;2}+A1".
            // Note this does not catch "={1;2}+ABS(A1)" that could be forced
            // to array, user still has to close in array mode.
            // The IsMatrixFunction() is only necessary because not all
            // functions returning matrix have ForceArrayReturn (yet?), see
            // OOXML comment above.
 
            const OpCode eOp1 = pCode[-1]->GetOpCode();
            const OpCode eOp2 = pCode[-2]->GetOpCode();
            const bool b1 = (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp1));
            const bool b2 = (pCode[-2]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp2));
            if ((b1 && b2)
                    || (b1 && eOp2 == ocPush && pCode[-2]->GetType() != svDoubleRef)
                    || (b2 && eOp1 == ocPush && pCode[-1]->GetType() != svDoubleRef))
            {
                rCurr->SetInForceArray( eArrayReturn);
            }
        }
        else if (pc >= 1 && SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP)
        {
            // Similar for unary operators.
            if (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(pCode[-1]->GetOpCode()))
            {
                rCurr->SetInForceArray( eArrayReturn);
            }
        }
        return;
    }
 
    // Inherited parameter class.
    const formula::ParamClass eForceType = pCurrentFactorToken->GetInForceArray();
    if (eForceType == ParamClass::ForceArray || eForceType == ParamClass::ReferenceOrRefArray)
    {
        // ReferenceOrRefArray was set only if in ForceArray context already,
        // it is valid for the one function only to indicate the preferred
        // return type. Propagate as ForceArray if not another parameter
        // handling ReferenceOrRefArray.
        if (nCurrentFactorParam > 0
                && (GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1))
                    == ParamClass::ReferenceOrRefArray))
            rCurr->SetInForceArray( ParamClass::ReferenceOrRefArray);
        else
            rCurr->SetInForceArray( ParamClass::ForceArray);
        return;
    }
    else if (eForceType == ParamClass::ReferenceOrForceArray)
    {
        // Inherit further only if the return class of the nested function is
        // not Reference. Else flag as suppressed.
        if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
            rCurr->SetInForceArray( eForceType);
        else
            rCurr->SetInForceArray( ParamClass::SuppressedReferenceOrForceArray);
        return;
    }
 
    if (nCurrentFactorParam <= 0)
        return;
 
    // Actual current parameter's class.
    const formula::ParamClass eParamType = GetForceArrayParameter(
            pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1));
    if (eParamType == ParamClass::ForceArray)
        rCurr->SetInForceArray( eParamType);
    else if (eParamType == ParamClass::ReferenceOrForceArray)
    {
        if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
            rCurr->SetInForceArray( eParamType);
        else
            rCurr->SetInForceArray( formula::ParamClass::SuppressedReferenceOrForceArray);
    }
 
    // Propagate a ForceArrayReturn to caller if the called function
    // returns one and the caller so far does not have a stronger array
    // mode set and expects a scalar value for this parameter.
    if (eParamType == ParamClass::Value && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown)
    {
        if (IsMatrixFunction( eOp))
            pCurrentFactorToken->SetInForceArray( eArrayReturn);
        else if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) == ParamClass::ForceArrayReturn)
            pCurrentFactorToken->SetInForceArray( ParamClass::ForceArrayReturn);
    }
}
 
void FormulaCompiler::CheckSetForceArrayParameter( FormulaTokenRef const & rCurr, sal_uInt8 nParam )
{
    if (!pCurrentFactorToken)
        return;
 
    nCurrentFactorParam = nParam + 1;
 
    ForceArrayOperator( rCurr);
}
 
void FormulaCompiler::PushTokenArray( FormulaTokenArray* pa, bool bTemp )
{
    if ( bAutoCorrect && !pStack )
    {   // don't merge stacked subroutine code into entered formula
        aCorrectedFormula += aCorrectedSymbol;
        aCorrectedSymbol.clear();
    }
    FormulaArrayStack* p = new FormulaArrayStack;
    p->pNext      = pStack;
    p->pArr       = pArr;
    p->nIndex     = maArrIterator.GetIndex();
    p->mpLastToken = mpLastToken;
    p->bTemp      = bTemp;
    pStack        = p;
    pArr          = pa;
    maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
}
 
} // namespace formula
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'appendAscii' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V547 Expression is always false.

V547 Expression is always false.

V547 Expression is always false.

V1037 Two or more case-branches perform the same actions. Check lines: 237, 246