/* -*- 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 <config_features.h>
 
#include <compiler.hxx>
 
#include <mutex>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <sfx2/app.hxx>
#include <sfx2/objsh.hxx>
#include <basic/sbmeth.hxx>
#include <basic/sbstar.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/sharedstringpool.hxx>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <osl/diagnose.h>
#include <rtl/character.hxx>
#include <unotools/charclass.hxx>
#include <unotools/configmgr.hxx>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp>
#include <com/sun/star/sheet/FormulaLanguage.hpp>
#include <com/sun/star/i18n/KParseTokens.hpp>
#include <com/sun/star/i18n/KParseType.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/string.hxx>
#include <unotools/transliterationwrapper.hxx>
#include <tools/urlobj.hxx>
#include <rtl/math.hxx>
#include <rtl/ustring.hxx>
#include <stdlib.h>
#include <rangenam.hxx>
#include <dbdata.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <callform.hxx>
#include <addincol.hxx>
#include <refupdat.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <formulacell.hxx>
#include <dociter.hxx>
#include <docoptio.hxx>
#include <formula/errorcodes.hxx>
#include <parclass.hxx>
#include <autonamecache.hxx>
#include <externalrefmgr.hxx>
#include <rangeutl.hxx>
#include <convuno.hxx>
#include <tokenuno.hxx>
#include <formulaparserpool.hxx>
#include <tokenarray.hxx>
#include <scmatrix.hxx>
#include <tokenstringcontext.hxx>
#include <officecfg/Office/Common.hxx>
#include <sfx2/linkmgr.hxx>
#include <interpre.hxx>
 
using namespace formula;
using namespace ::com::sun::star;
using ::std::vector;
 
const CharClass*                    ScCompiler::pCharClassEnglish = nullptr;
const CharClass*                    ScCompiler::pCharClassLocalized = nullptr;
const ScCompiler::Convention*       ScCompiler::pConventions[ ]   = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
 
namespace {
 
enum ScanState
{
    ssGetChar,
    ssGetBool,
    ssGetValue,
    ssGetString,
    ssSkipString,
    ssGetIdent,
    ssGetReference,
    ssSkipReference,
    ssGetErrorConstant,
    ssGetTableRefItem,
    ssGetTableRefColumn,
    ssStop
};
 
}
 
static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" };
 
using namespace ::com::sun::star::i18n;
 
void ScCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& xMap,FormulaGrammar::Grammar _eGrammar  ) const
{
    size_t nSymbolOffset;
    switch( _eGrammar )
    {
        // XFunctionAccess and XCell::setFormula()/getFormula() API always used
        // PODF grammar symbols, keep it.
        case FormulaGrammar::GRAM_API:
        case FormulaGrammar::GRAM_PODF:
            nSymbolOffset = offsetof( AddInMap, pUpper);
            break;
        default:
        case FormulaGrammar::GRAM_ODFF:
            nSymbolOffset = offsetof( AddInMap, pODFF);
            break;
        case FormulaGrammar::GRAM_ENGLISH:
            nSymbolOffset = offsetof( AddInMap, pEnglish);
            break;
    }
    const AddInMap* const pStop = g_aAddInMap + GetAddInMapCount();
    for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap)
    {
        char const * const * ppSymbol =
            reinterpret_cast< char const * const * >(
                    reinterpret_cast< char const * >(pMap) + nSymbolOffset);
        xMap->putExternal( OUString::createFromAscii( *ppSymbol),
                OUString::createFromAscii( pMap->pOriginal));
    }
    if (_eGrammar == FormulaGrammar::GRAM_API)
    {
        // Add English names additionally to programmatic names, so they
        // can be used in XCell::setFormula() non-localized API calls.
        // Note the reverse map will still deliver programmatic names for
        // XCell::getFormula().
        nSymbolOffset = offsetof( AddInMap, pEnglish);
        for (const AddInMap* pMap = g_aAddInMap; pMap < pStop; ++pMap)
        {
            char const * const * ppSymbol =
                reinterpret_cast< char const * const * >(
                        reinterpret_cast< char const * >(pMap) + nSymbolOffset);
            xMap->putExternal( OUString::createFromAscii( *ppSymbol),
                    OUString::createFromAscii( pMap->pOriginal));
        }
    }
}
 
void ScCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& xMap ) const
{
    ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
    tools::Long nCount = pColl->GetFuncCount();
    for (tools::Long i=0; i < nCount; ++i)
    {
        const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
        if (pFuncData)
            xMap->putExternalSoftly( pFuncData->GetUpperName(),
                    pFuncData->GetOriginalName());
    }
}
 
void ScCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& xMap ) const
{
    ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
    tools::Long nCount = pColl->GetFuncCount();
    for (tools::Long i=0; i < nCount; ++i)
    {
        const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
        if (pFuncData)
        {
            const OUString aName( pFuncData->GetUpperEnglish());
            if (!aName.isEmpty())
                xMap->putExternalSoftly( aName, pFuncData->GetOriginalName());
            else
                xMap->putExternalSoftly( pFuncData->GetUpperName(),
                        pFuncData->GetOriginalName());
        }
    }
}
 
void ScCompiler::fillFromAddInCollectionExcelName( const NonConstOpCodeMapPtr& xMap ) const
{
    const LanguageTag aDestLang(LANGUAGE_ENGLISH_US);
    ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
    tools::Long nCount = pColl->GetFuncCount();
    for (tools::Long i=0; i < nCount; ++i)
    {
        OUString aExcelName;
        const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
        if (pFuncData && pFuncData->GetExcelName( aDestLang, aExcelName, true))
        {
            // Note that function names not defined in OOXML but implemented by
            // Excel should have the "_xlfn." prefix. We have no way to check
            // though what an Add-In actually implements.
            xMap->putExternalSoftly( GetCharClassEnglish()->uppercase(aExcelName), pFuncData->GetOriginalName());
        }
    }
}
 
static std::mutex gCharClassMutex;
 
void ScCompiler::DeInit()
{
    std::scoped_lock aGuard(gCharClassMutex);
    if (pCharClassEnglish)
    {
        delete pCharClassEnglish;
        pCharClassEnglish = nullptr;
    }
    if (pCharClassLocalized)
    {
        delete pCharClassLocalized;
        pCharClassLocalized = nullptr;
    }
}
 
bool ScCompiler::IsEnglishSymbol( const OUString& rName )
{
    // function names are always case-insensitive
    OUString aUpper = GetCharClassEnglish()->uppercase(rName);
 
    // 1. built-in function name
    formula::FormulaCompiler aCompiler;
    OpCode eOp = aCompiler.GetEnglishOpCode( aUpper );
    if ( eOp != ocNone )
    {
        return true;
    }
    // 2. old add in functions
    if (ScGlobal::GetLegacyFuncCollection()->findByName(aUpper))
    {
        return true;
    }
 
    // 3. new (uno) add in functions
    OUString aIntName = ScGlobal::GetAddInCollection()->FindFunction(aUpper, false);
    return !aIntName.isEmpty();       // no valid function name
}
 
const CharClass* ScCompiler::GetCharClassEnglish()
{
    std::scoped_lock aGuard(gCharClassMutex);
    if (!pCharClassEnglish)
    {
        pCharClassEnglish = new CharClass( ::comphelper::getProcessComponentContext(),
                LanguageTag( LANGUAGE_ENGLISH_US));
    }
    return pCharClassEnglish;
}
 
const CharClass* ScCompiler::GetCharClassLocalized()
{
    // Switching UI language requires restart; if not, we would have to
    // keep track of that.
    std::scoped_lock aGuard(gCharClassMutex);
    if (!pCharClassLocalized)
    {
        pCharClassLocalized = new CharClass( ::comphelper::getProcessComponentContext(),
                Application::GetSettings().GetUILanguageTag());
    }
    return pCharClassLocalized;
}
 
void ScCompiler::SetGrammar( const FormulaGrammar::Grammar eGrammar )
{
    assert( eGrammar != FormulaGrammar::GRAM_UNSPECIFIED && "ScCompiler::SetGrammar: don't pass FormulaGrammar::GRAM_UNSPECIFIED");
    if (eGrammar == GetGrammar())
        return;     // nothing to be done
 
    if( eGrammar == FormulaGrammar::GRAM_EXTERNAL )
    {
        meGrammar = eGrammar;
        mxSymbols = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
    }
    else
    {
        FormulaGrammar::Grammar eMyGrammar = eGrammar;
        const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( eMyGrammar);
        OpCodeMapPtr xMap = GetFinalOpCodeMap( nFormulaLanguage);
        OSL_ENSURE( xMap, "ScCompiler::SetGrammar: unknown formula language");
        if (!xMap)
        {
            xMap = GetFinalOpCodeMap( css::sheet::FormulaLanguage::NATIVE);
            eMyGrammar = xMap->getGrammar();
        }
 
        // Save old grammar for call to SetGrammarAndRefConvention().
        FormulaGrammar::Grammar eOldGrammar = GetGrammar();
        // This also sets the grammar associated with the map!
        SetFormulaLanguage( xMap);
 
        // Override if necessary.
        if (eMyGrammar != GetGrammar())
            SetGrammarAndRefConvention( eMyGrammar, eOldGrammar);
    }
}
 
// Unclear how this was intended to be refreshed when the
// grammar or sheet count is changed ? Ideally this would be
// a method on Document that would globally cache these.
std::vector<OUString> &ScCompiler::GetSetupTabNames() const
{
    std::vector<OUString> &rTabNames = const_cast<ScCompiler *>(this)->maTabNames;
 
    if (rTabNames.empty())
    {
        rTabNames = rDoc.GetAllTableNames();
        for (auto& rTabName : rTabNames)
            ScCompiler::CheckTabQuotes(rTabName, formula::FormulaGrammar::extractRefConvention(meGrammar));
    }
 
    return rTabNames;
}
 
void ScCompiler::SetFormulaLanguage( const ScCompiler::OpCodeMapPtr & xMap )
{
    if (!xMap)
        return;
 
    mxSymbols = xMap;
    if (mxSymbols->isEnglish())
        pCharClass = GetCharClassEnglish();
    else
        pCharClass = GetCharClassLocalized();
 
    // The difference is needed for an uppercase() call that usually does not
    // result in different strings but for a few languages like Turkish;
    // though even de-DE and de-CH may differ in ß/SS handling..
    // At least don't care if both are English.
    // The current locale is more likely to not be "en" so check first.
    const LanguageTag& rLT1 = ScGlobal::getCharClass().getLanguageTag();
    const LanguageTag& rLT2 = pCharClass->getLanguageTag();
    mbCharClassesDiffer = (rLT1 != rLT2 && (rLT1.getLanguage() != "en" || rLT2.getLanguage() != "en"));
 
    SetGrammarAndRefConvention( mxSymbols->getGrammar(), GetGrammar());
}
 
void ScCompiler::SetGrammarAndRefConvention(
        const FormulaGrammar::Grammar eNewGrammar, const FormulaGrammar::Grammar eOldGrammar )
{
    meGrammar = eNewGrammar;    // SetRefConvention needs the new grammar set!
    FormulaGrammar::AddressConvention eConv = FormulaGrammar::extractRefConvention( meGrammar);
    if (eConv == FormulaGrammar::CONV_UNSPECIFIED && eOldGrammar == FormulaGrammar::GRAM_UNSPECIFIED)
        SetRefConvention( rDoc.GetAddressConvention());
    else
        SetRefConvention( eConv );
}
 
OUString ScCompiler::FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const
{
    return ScGlobal::GetAddInCollection()->FindFunction(rUpperName, bLocalFirst);    // bLocalFirst=false for english
}
 
ScCompiler::Convention::~Convention()
{
}
 
ScCompiler::Convention::Convention( FormulaGrammar::AddressConvention eConv )
        :
    meConv( eConv )
{
    int i;
    ScCharFlags *t= new ScCharFlags [128];
 
    ScCompiler::pConventions[ meConv ] = this;
    mpCharTable.reset( t );
 
    for (i = 0; i < 128; i++)
        t[i] = ScCharFlags::Illegal;
 
// Allow tabs/newlines.
// Allow saving whitespace as is (as per OpenFormula specification v.1.2, clause 5.14 "Whitespace").
/* tab */   t[ 9] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* lf  */   t[10] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* cr  */   t[13] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
 
/*   */     t[32] = ScCharFlags::CharDontCare | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* ! */     t[33] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
            if (FormulaGrammar::CONV_ODF == meConv)
/* ! */         t[33] |= ScCharFlags::OdfLabelOp;
/* " */     t[34] = ScCharFlags::CharString | ScCharFlags::StringSep;
/* # */     t[35] = ScCharFlags::WordSep | ScCharFlags::CharErrConst;
/* $ */     t[36] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident;
            if (FormulaGrammar::CONV_ODF == meConv)
/* $ */         t[36] |= ScCharFlags::OdfNameMarker;
/* % */     t[37] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* & */     t[38] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* ' */     t[39] = ScCharFlags::NameSep;
/* ( */     t[40] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* ) */     t[41] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* * */     t[42] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* + */     t[43] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
/* , */     t[44] = ScCharFlags::CharValue | ScCharFlags::Value;
/* - */     t[45] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueExp | ScCharFlags::ValueSign;
/* . */     t[46] = ScCharFlags::Word | ScCharFlags::CharValue | ScCharFlags::Value | ScCharFlags::Ident | ScCharFlags::Name;
/* / */     t[47] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
 
            for (i = 48; i < 58; i++)
/* 0-9 */       t[i] = ScCharFlags::CharValue | ScCharFlags::Word | ScCharFlags::Value | ScCharFlags::ValueExp | ScCharFlags::ValueValue | ScCharFlags::Ident | ScCharFlags::Name;
 
/* : */     t[58] = ScCharFlags::Char | ScCharFlags::Word;
/* ; */     t[59] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* < */     t[60] = ScCharFlags::CharBool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* = */     t[61] = ScCharFlags::Char | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* > */     t[62] = ScCharFlags::CharBool | ScCharFlags::Bool | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* ? */     t[63] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::Name;
/* @ */     // FREE
 
    for (i = 65; i < 91; i++)
/* A-Z */   t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
 
    if (FormulaGrammar::CONV_ODF == meConv)
    {
/* [ */     t[91] = ScCharFlags::OdfLBracket;
/* \ */     // FREE
/* ] */     t[93] = ScCharFlags::OdfRBracket;
    }
    else if (FormulaGrammar::CONV_OOO == meConv)
    {
/* [ */     t[91] = ScCharFlags::Char;
/* \ */     // FREE
/* ] */     t[93] = ScCharFlags::Char;
    }
    else if (FormulaGrammar::CONV_XL_OOX == meConv)
    {
/* [ */     t[91] = ScCharFlags::Char | ScCharFlags::CharIdent;
/* \ */     // FREE
/* ] */     t[93] = ScCharFlags::Char | ScCharFlags::Ident;
    }
    else if (FormulaGrammar::CONV_XL_A1 == meConv)
    {
/* [ */     t[91] = ScCharFlags::Char;
/* \ */     // FREE
/* ] */     t[93] = ScCharFlags::Char;
    }
    else if( FormulaGrammar::CONV_XL_R1C1 == meConv )
    {
/* [ */     t[91] = ScCharFlags::Ident;
/* \ */     // FREE
/* ] */     t[93] = ScCharFlags::Ident;
    }
    else
    {
/* [ */     // FREE
/* \ */     // FREE
/* ] */     // FREE
    }
 
/* ^ */     t[94] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
/* _ */     t[95] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
/* ` */     // FREE
 
            for (i = 97; i < 123; i++)
/* a-z */       t[i] = ScCharFlags::CharWord | ScCharFlags::Word | ScCharFlags::CharIdent | ScCharFlags::Ident | ScCharFlags::CharName | ScCharFlags::Name;
 
/* { */     t[123] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array open
/* | */     t[124] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array row sep (Should be OOo specific)
/* } */     t[125] = ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep; // array close
/* ~ */     t[126] = ScCharFlags::Char;        // OOo specific
/* 127 */   // FREE
 
    if( !(FormulaGrammar::CONV_XL_A1 == meConv || FormulaGrammar::CONV_XL_R1C1 == meConv || FormulaGrammar::CONV_XL_OOX == meConv) )
return;
 
/*   */     t[32] |=   ScCharFlags::Word;
/* ! */     t[33] |=   ScCharFlags::Ident | ScCharFlags::Word;
/* " */     t[34] |=   ScCharFlags::Word;
/* # */     t[35] &=  ~ScCharFlags::WordSep;
/* # */     t[35] |=   ScCharFlags::Word;
/* % */     t[37] |=   ScCharFlags::Word;
/* & */     t[38] |=   ScCharFlags::Word;
/* ' */     t[39] |=   ScCharFlags::Word;
/* ( */     t[40] |=   ScCharFlags::Word;
/* ) */     t[41] |=   ScCharFlags::Word;
/* * */     t[42] |=   ScCharFlags::Word;
/* + */     t[43] |=   ScCharFlags::Word;
#if 0 /* this really needs to be locale specific. */
/* , */     t[44]  =   ScCharFlags::Char | ScCharFlags::WordSep | ScCharFlags::ValueSep;
#else
/* , */     t[44] |=   ScCharFlags::Word;
#endif
/* - */     t[45] |=   ScCharFlags::Word;
 
/* ; */     t[59] |=   ScCharFlags::Word;
/* < */     t[60] |=   ScCharFlags::Word;
/* = */     t[61] |=   ScCharFlags::Word;
/* > */     t[62] |=   ScCharFlags::Word;
/* ? */     // question really is not permitted in sheet name
/* @ */     t[64] |=   ScCharFlags::Word;
/* [ */     t[91] |=   ScCharFlags::Word;
/* ] */     t[93] |=   ScCharFlags::Word;
/* { */     t[123]|=   ScCharFlags::Word;
/* | */     t[124]|=   ScCharFlags::Word;
/* } */     t[125]|=   ScCharFlags::Word;
/* ~ */     t[126]|=   ScCharFlags::Word;
}
 
static bool lcl_isValidQuotedText( std::u16string_view rFormula, size_t nSrcPos, ParseResult& rRes )
{
    // Tokens that start at ' can have anything in them until a final '
    // but '' marks an escaped '
    // We've earlier guaranteed that a string containing '' will be
    // surrounded by '
    if (nSrcPos < rFormula.size() && rFormula[nSrcPos] == '\'')
    {
        size_t nPos = nSrcPos+1;
        while (nPos < rFormula.size())
        {
            if (rFormula[nPos] == '\'')
            {
                if ( (nPos+1 == rFormula.size()) || (rFormula[nPos+1] != '\'') )
                {
                    rRes.TokenType = KParseType::SINGLE_QUOTE_NAME;
                    rRes.EndPos = nPos+1;
                    return true;
                }
                ++nPos;
            }
            ++nPos;
        }
    }
 
    return false;
}
 
static bool lcl_parseExternalName(
        const OUString& rSymbol,
        OUString& rFile,
        OUString& rName,
        const sal_Unicode cSep,
        const ScDocument& rDoc,
        const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
{
    /* TODO: future versions will have to support sheet-local names too, thus
     * return a possible sheet name as well. */
    const sal_Unicode* const pStart = rSymbol.getStr();
    const sal_Unicode* p = pStart;
    sal_Int32 nLen = rSymbol.getLength();
    OUString aTmpFile;
    OUStringBuffer aTmpName;
    sal_Int32 i = 0;
    bool bInName = false;
    if (cSep == '!')
    {
        // For XL use existing parser that resolves bracketed and quoted and
        // indexed external document names.
        ScRange aRange;
        OUString aStartTabName, aEndTabName;
        ScRefFlags nFlags = ScRefFlags::ZERO;
        p = aRange.Parse_XL_Header( p, rDoc, aTmpFile, aStartTabName,
                aEndTabName, nFlags, true, pExternalLinks );
        if (!p || p == pStart)
            return false;
        i = sal_Int32(p - pStart);
    }
    for ( ; i < nLen; ++i, ++p)
    {
        sal_Unicode c = *p;
        if (i == 0)
        {
            if (c == '.' || c == cSep)
                return false;
 
            if (c == '\'')
            {
                // Move to the next char and loop until the second single
                // quote.
                sal_Unicode cPrev = c;
                ++i; ++p;
                for (sal_Int32 j = i; j < nLen; ++j, ++p)
                {
                    c = *p;
                    if (c == '\'')
                    {
                        if (j == i)
                        {
                            // empty quote e.g. (=''!Name)
                            return false;
                        }
 
                        if (cPrev == '\'')
                        {
                            // two consecutive quotes equal a single quote in
                            // the file name.
                            aTmpFile += OUStringChar(c);
                            cPrev = 'a';
                        }
                        else
                            cPrev = c;
 
                        continue;
                    }
 
                    if (cPrev == '\'' && j != i)
                    {
                        // this is not a quote but the previous one is.  This
                        // ends the parsing of the quoted segment.  At this
                        // point, the current char must equal the separator
                        // char.
 
                        i = j;
                        bInName = true;
                        aTmpName.append(c); // Keep the separator as part of the name.
                        break;
                    }
                    aTmpFile += OUStringChar(c);
                    cPrev = c;
                }
 
                if (!bInName)
                {
                    // premature ending of the quoted segment.
                    return false;
                }
 
                if (c != cSep)
                {
                    // only the separator is allowed after the closing quote.
                    return false;
                }
 
                continue;
            }
        }
 
        if (bInName)
        {
            if (c == cSep)
            {
                // A second separator ?  Not a valid external name.
                return false;
            }
            aTmpName.append(c);
        }
        else
        {
            if (c == cSep)
            {
                bInName = true;
                aTmpName.append(c); // Keep the separator as part of the name.
            }
            else
            {
                do
                {
                    if (rtl::isAsciiAlphanumeric(c))
                        // allowed.
                        break;
 
                    if (c > 128)
                        // non-ASCII character is allowed.
                        break;
 
                    bool bValid = false;
                    switch (c)
                    {
                        case '_':
                        case '-':
                        case '.':
                            // these special characters are allowed.
                            bValid = true;
                            break;
                    }
                    if (bValid)
                        break;
 
                    return false;
                }
                while (false);
                aTmpFile += OUStringChar(c);
            }
        }
    }
 
    if (!bInName)
    {
        // No name found - most likely the symbol has no '!'s.
        return false;
    }
 
    sal_Int32 nNameLen = aTmpName.getLength();
    if (nNameLen < 2)
    {
        // Name must be at least 2-char long (separator plus name).
        return false;
    }
 
    if (aTmpName[0] != cSep)
    {
        // 1st char of the name must equal the separator.
        return false;
    }
 
    if (aTmpName[nNameLen-1] == '!')
    {
        // Check against #REF!.
        if (OUString::unacquired(aTmpName).equalsIgnoreAsciiCase("#REF!"))
            return false;
    }
 
    rFile = aTmpFile;
    rName = aTmpName.makeStringAndClear().copy(1); // Skip the first char as it is always the separator.
    return true;
}
 
static OUString lcl_makeExternalNameStr(const OUString& rFile, const OUString& rName,
        const sal_Unicode cSep, bool bODF )
{
    OUString aEscQuote(u"''"_ustr);
    OUString aFile(rFile.replaceAll("'", aEscQuote));
    OUString aName(rName);
    if (bODF)
        aName = aName.replaceAll("'", aEscQuote);
    OUStringBuffer aBuf(aFile.getLength() + aName.getLength() + 9);
    if (bODF)
        aBuf.append( '[');
    aBuf.append( "'" + aFile + "'" + OUStringChar(cSep) );
    if (bODF)
        aBuf.append( "$$'" );
    aBuf.append( aName);
    if (bODF)
        aBuf.append( "']" );
    return aBuf.makeStringAndClear();
}
 
static bool lcl_getLastTabName( OUString& rTabName2, const OUString& rTabName1,
                                const vector<OUString>& rTabNames, const ScRange& rRef )
{
    SCTAB nTabSpan = rRef.aEnd.Tab() - rRef.aStart.Tab();
    if (nTabSpan > 0)
    {
        size_t nCount = rTabNames.size();
        vector<OUString>::const_iterator itrBeg = rTabNames.begin(), itrEnd = rTabNames.end();
        vector<OUString>::const_iterator itr = ::std::find(itrBeg, itrEnd, rTabName1);
        if (itr == rTabNames.end())
        {
            rTabName2 = ScResId(STR_NO_REF_TABLE);
            return false;
        }
 
        size_t nDist = ::std::distance(itrBeg, itr);
        if (nDist + static_cast<size_t>(nTabSpan) >= nCount)
        {
            rTabName2 = ScResId(STR_NO_REF_TABLE);
            return false;
        }
 
        rTabName2 = rTabNames[nDist+nTabSpan];
    }
    else
        rTabName2 = rTabName1;
 
    return true;
}
 
namespace {
 
struct Convention_A1 : public ScCompiler::Convention
{
    explicit Convention_A1( FormulaGrammar::AddressConvention eConv ) : ScCompiler::Convention( eConv ) { }
    static void MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol );
    static void MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow );
 
    ParseResult parseAnyToken( const OUString& rFormula,
                               sal_Int32 nSrcPos,
                               const CharClass* pCharClass,
                               bool bGroupSeparator) const override
    {
        ParseResult aRet;
        if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
            return aRet;
 
        constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
            KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
        constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
        // '?' allowed in range names because of Xcl :-/
        static constexpr OUString aAddAllowed(u"?#"_ustr);
        return pCharClass->parseAnyToken( rFormula,
                nSrcPos, nStartFlags, aAddAllowed,
                (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
                aAddAllowed );
    }
 
    virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode /*cLast*/ ) const override
    {
        return mpCharTable[static_cast<sal_uInt8>(c)];
    }
};
 
}
 
void Convention_A1::MakeColStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCCOL nCol )
{
    if ( !rLimits.ValidCol(nCol) )
        rBuffer.append(ScResId(STR_NO_REF_TABLE));
    else
        ::ScColToAlpha( rBuffer, nCol);
}
 
void Convention_A1::MakeRowStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuffer, SCROW nRow )
{
    if ( !rLimits.ValidRow(nRow) )
        rBuffer.append(ScResId(STR_NO_REF_TABLE));
    else
        rBuffer.append(sal_Int32(nRow + 1));
}
 
namespace {
 
struct ConventionOOO_A1 : public Convention_A1
{
    ConventionOOO_A1() : Convention_A1 (FormulaGrammar::CONV_OOO) { }
    explicit ConventionOOO_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1 (eConv) { }
 
    static void MakeTabStr( OUStringBuffer &rBuf, const std::vector<OUString>& rTabNames, SCTAB nTab )
    {
        if (o3tl::make_unsigned(nTab) >= rTabNames.size())
            rBuf.append(ScResId(STR_NO_REF_TABLE));
        else
            rBuf.append(rTabNames[nTab]);
        rBuf.append('.');
    }
 
    enum SingletonDisplay
    {
        SINGLETON_NONE,
        SINGLETON_COL,
        SINGLETON_ROW
    };
 
    static void MakeOneRefStrImpl(
        const ScSheetLimits& rLimits, OUStringBuffer& rBuffer,
        std::u16string_view rErrRef, const std::vector<OUString>& rTabNames,
        const ScSingleRefData& rRef, const ScAddress& rAbsRef,
        bool bForceTab, bool bODF, SingletonDisplay eSingletonDisplay )
    {
        if( rRef.IsFlag3D() || bForceTab )
        {
            if (!ValidTab(rAbsRef.Tab()) || rRef.IsTabDeleted())
            {
                if (!rRef.IsTabRel())
                    rBuffer.append('$');
                rBuffer.append(rErrRef);
                rBuffer.append('.');
            }
            else
            {
                if (!rRef.IsTabRel())
                    rBuffer.append('$');
                MakeTabStr(rBuffer, rTabNames, rAbsRef.Tab());
            }
        }
        else if (bODF)
            rBuffer.append('.');
 
        if (eSingletonDisplay != SINGLETON_ROW)
        {
            if (!rRef.IsColRel())
                rBuffer.append('$');
            if (!rLimits.ValidCol(rAbsRef.Col()) || rRef.IsColDeleted())
                rBuffer.append(rErrRef);
            else
                MakeColStr(rLimits, rBuffer, rAbsRef.Col());
        }
 
        if (eSingletonDisplay != SINGLETON_COL)
        {
            if (!rRef.IsRowRel())
                rBuffer.append('$');
            if (!rLimits.ValidRow(rAbsRef.Row()) || rRef.IsRowDeleted())
                rBuffer.append(rErrRef);
            else
                MakeRowStr(rLimits, rBuffer, rAbsRef.Row());
        }
    }
 
    static SingletonDisplay getSingletonDisplay( const ScSheetLimits& rLimits, const ScAddress& rAbs1, const ScAddress& rAbs2,
            const ScComplexRefData& rRef, bool bFromRangeName )
    {
        // If any part is error, display as such.
        if (!rLimits.ValidCol(rAbs1.Col()) || rRef.Ref1.IsColDeleted() || !rLimits.ValidRow(rAbs1.Row()) || rRef.Ref1.IsRowDeleted() ||
            !rLimits.ValidCol(rAbs2.Col()) || rRef.Ref2.IsColDeleted() || !rLimits.ValidRow(rAbs2.Row()) || rRef.Ref2.IsRowDeleted())
            return SINGLETON_NONE;
 
        // A:A or $A:$A or A:$A or $A:A
        if (rRef.IsEntireCol(rLimits))
            return SINGLETON_COL;
 
        // Same if not in named expression and both rows of entire columns are
        // relative references.
        if (!bFromRangeName && rAbs1.Row() == 0 && rAbs2.Row() == rLimits.mnMaxRow &&
                rRef.Ref1.IsRowRel() && rRef.Ref2.IsRowRel())
            return SINGLETON_COL;
 
        // 1:1 or $1:$1 or 1:$1 or $1:1
        if (rRef.IsEntireRow(rLimits))
            return SINGLETON_ROW;
 
        // Same if not in named expression and both columns of entire rows are
        // relative references.
        if (!bFromRangeName && rAbs1.Col() == 0 && rAbs2.Col() == rLimits.mnMaxCol &&
                rRef.Ref1.IsColRel() && rRef.Ref2.IsColRel())
            return SINGLETON_ROW;
 
        return SINGLETON_NONE;
    }
 
    virtual void makeRefStr(
                     ScSheetLimits& rLimits,
                     OUStringBuffer&   rBuffer,
                     formula::FormulaGrammar::Grammar /*eGram*/,
                     const ScAddress& rPos,
                     const OUString& rErrRef, const std::vector<OUString>& rTabNames,
                     const ScComplexRefData& rRef,
                     bool bSingleRef,
                     bool bFromRangeName ) const override
    {
        // In case absolute/relative positions weren't separately available:
        // transform relative to absolute!
        ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
        if( !bSingleRef )
            aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
 
        SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
            getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
        MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, false, eSingleton);
        if (!bSingleRef)
        {
            rBuffer.append(':');
            MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), false,
                    eSingleton);
        }
    }
 
    virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
    {
        switch (eSymType)
        {
            case ScCompiler::Convention::ABS_SHEET_PREFIX:
                return '$';
            case ScCompiler::Convention::SHEET_SEPARATOR:
                return '.';
        }
 
        return u'\0';
    }
 
    virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
            const ScDocument& rDoc,
            const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
    {
        return lcl_parseExternalName(rSymbol, rFile, rName, '#', rDoc, pExternalLinks);
    }
 
    virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
            const OUString& rName ) const override
    {
        return lcl_makeExternalNameStr( rFile, rName, '#', false);
    }
 
    static bool makeExternalSingleRefStr(
        const ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const OUString& rFileName, const OUString& rTabName,
        const ScSingleRefData& rRef, const ScAddress& rPos, bool bDisplayTabName, bool bEncodeUrl )
    {
        ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
        if (bDisplayTabName)
        {
            OUString aFile;
            if (bEncodeUrl)
                aFile = rFileName;
            else
                aFile = INetURLObject::decode(rFileName, INetURLObject::DecodeMechanism::Unambiguous);
 
            rBuffer.append("'" + aFile.replaceAll("'", "''") + "'#");
 
            if (!rRef.IsTabRel())
                rBuffer.append('$');
            ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
 
            rBuffer.append('.');
        }
 
        if (!rRef.IsColRel())
            rBuffer.append('$');
        MakeColStr( rLimits, rBuffer, aAbsRef.Col());
        if (!rRef.IsRowRel())
            rBuffer.append('$');
        MakeRowStr( rLimits, rBuffer, aAbsRef.Row());
 
        return true;
    }
 
    static void makeExternalRefStrImpl(
        const ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
        const OUString& rTabName, const ScSingleRefData& rRef, bool bODF )
    {
        if (bODF)
            rBuffer.append( '[');
 
        bool bEncodeUrl = bODF;
        makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef, rPos, true, bEncodeUrl);
        if (bODF)
            rBuffer.append( ']');
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const OUString& rTabName, const ScSingleRefData& rRef ) const override
    {
        makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, false);
    }
 
    static void makeExternalRefStrImpl(
        const ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, const OUString& rFileName,
        const std::vector<OUString>& rTabNames, const OUString& rTabName,
        const ScComplexRefData& rRef, bool bODF )
    {
        ScRange aAbsRange = rRef.toAbs(rLimits, rPos);
 
        if (bODF)
            rBuffer.append( '[');
        // Ensure that there's always a closing bracket, no premature returns.
        bool bEncodeUrl = bODF;
 
        do
        {
            if (!makeExternalSingleRefStr(rLimits, rBuffer, rFileName, rTabName, rRef.Ref1, rPos, true, bEncodeUrl))
                break;
 
            rBuffer.append(':');
 
            OUString aLastTabName;
            bool bDisplayTabName = (aAbsRange.aStart.Tab() != aAbsRange.aEnd.Tab());
            if (bDisplayTabName)
            {
                // Get the name of the last table.
                if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, aAbsRange))
                {
                    OSL_FAIL( "ConventionOOO_A1::makeExternalRefStrImpl: sheet name not found");
                    // aLastTabName contains #REF!, proceed.
                }
            }
            else if (bODF)
                rBuffer.append( '.');      // need at least the sheet separator in ODF
            makeExternalSingleRefStr(rLimits,
                rBuffer, rFileName, aLastTabName, rRef.Ref2, rPos, bDisplayTabName, bEncodeUrl);
        } while (false);
 
        if (bODF)
            rBuffer.append( ']');
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const std::vector<OUString>& rTabNames, const OUString& rTabName,
        const ScComplexRefData& rRef ) const override
    {
        makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, false);
    }
};
 
struct ConventionOOO_A1_ODF : public ConventionOOO_A1
{
    ConventionOOO_A1_ODF() : ConventionOOO_A1 (FormulaGrammar::CONV_ODF) { }
 
    virtual void makeRefStr(
                     ScSheetLimits& rLimits,
                     OUStringBuffer&   rBuffer,
                     formula::FormulaGrammar::Grammar eGram,
                     const ScAddress& rPos,
                     const OUString& rErrRef, const std::vector<OUString>& rTabNames,
                     const ScComplexRefData& rRef,
                     bool bSingleRef,
                     bool bFromRangeName ) const override
    {
        rBuffer.append('[');
        // In case absolute/relative positions weren't separately available:
        // transform relative to absolute!
        ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos), aAbs2;
        if( !bSingleRef )
            aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
 
        if (FormulaGrammar::isODFF(eGram) && (rRef.Ref1.IsDeleted() || !rLimits.ValidAddress(aAbs1) ||
                    (!bSingleRef && (rRef.Ref2.IsDeleted() || !rLimits.ValidAddress(aAbs2)))))
        {
            rBuffer.append(rErrRef);
            // For ODFF write [#REF!], but not for PODF so apps reading ODF
            // 1.0/1.1 may have a better chance if they implemented the old
            // form.
        }
        else
        {
            SingletonDisplay eSingleton = bSingleRef ? SINGLETON_NONE :
                getSingletonDisplay( rLimits, aAbs1, aAbs2, rRef, bFromRangeName);
            MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref1, aAbs1, false, true, eSingleton);
            if (!bSingleRef)
            {
                rBuffer.append(':');
                MakeOneRefStrImpl(rLimits, rBuffer, rErrRef, rTabNames, rRef.Ref2, aAbs2, aAbs1.Tab() != aAbs2.Tab(), true,
                        eSingleton);
            }
        }
        rBuffer.append(']');
    }
 
    virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
            const OUString& rName ) const override
    {
        return lcl_makeExternalNameStr( rFile, rName, '#', true);
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const OUString& rTabName, const ScSingleRefData& rRef ) const override
    {
        makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabName, rRef, true);
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const std::vector<OUString>& rTabNames,
        const OUString& rTabName, const ScComplexRefData& rRef ) const override
    {
        makeExternalRefStrImpl(rLimits, rBuffer, rPos, rFileName, rTabNames, rTabName, rRef, true);
    }
};
 
struct ConventionXL
{
    virtual ~ConventionXL()
    {
    }
 
    static void GetTab(
        const ScSheetLimits& rLimits,
        const ScAddress& rPos, const std::vector<OUString>& rTabNames,
        const ScSingleRefData& rRef, OUString& rTabName )
    {
        ScAddress aAbs = rRef.toAbs(rLimits, rPos);
        if (rRef.IsTabDeleted() || o3tl::make_unsigned(aAbs.Tab()) >= rTabNames.size())
        {
            rTabName = ScResId( STR_NO_REF_TABLE );
            return;
        }
        rTabName = rTabNames[aAbs.Tab()];
    }
 
    static void MakeTabStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf,
                            const ScAddress& rPos,
                            const std::vector<OUString>& rTabNames,
                            const ScComplexRefData& rRef,
                            bool bSingleRef )
    {
        if( !rRef.Ref1.IsFlag3D() )
            return;
 
        OUString aStartTabName, aEndTabName;
 
        GetTab(rLimits, rPos, rTabNames, rRef.Ref1, aStartTabName);
 
        if( !bSingleRef && rRef.Ref2.IsFlag3D() )
        {
            GetTab(rLimits, rPos, rTabNames, rRef.Ref2, aEndTabName);
        }
 
        const sal_Int32 nQuotePos = rBuf.getLength();
        rBuf.append( aStartTabName );
        if( !bSingleRef && rRef.Ref2.IsFlag3D() && aStartTabName != aEndTabName )
        {
            ScCompiler::FormExcelSheetRange( rBuf, nQuotePos, aEndTabName);
        }
 
        rBuf.append( '!' );
    }
 
    static sal_Unicode getSpecialSymbol( ScCompiler::Convention::SpecialSymbolType eSymType )
    {
        switch (eSymType)
        {
            case ScCompiler::Convention::ABS_SHEET_PREFIX:
                return u'\0';
            case ScCompiler::Convention::SHEET_SEPARATOR:
                return '!';
        }
        return u'\0';
    }
 
    static bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
            const ScDocument& rDoc,
            const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks )
    {
        return lcl_parseExternalName( rSymbol, rFile, rName, '!', rDoc, pExternalLinks);
    }
 
    static OUString makeExternalNameStr( const OUString& rFile, const OUString& rName )
    {
        return lcl_makeExternalNameStr( rFile, rName, '!', false);
    }
 
    static void makeExternalDocStr( OUStringBuffer& rBuffer, std::u16string_view rFullName )
    {
        // Format that is easier to deal with inside OOo, because we use file
        // URL, and all characters are allowed.  Check if it makes sense to do
        // it the way Gnumeric does it.  Gnumeric doesn't use the URL form
        // and allows relative file path.
        //
        //   ['file:///path/to/source/filename.xls']
 
        rBuffer.append('[');
        rBuffer.append('\'');
        OUString aFullName = INetURLObject::decode(rFullName, INetURLObject::DecodeMechanism::Unambiguous);
 
        const sal_Unicode* pBuf = aFullName.getStr();
        sal_Int32 nLen = aFullName.getLength();
        for (sal_Int32 i = 0; i < nLen; ++i)
        {
            const sal_Unicode c = pBuf[i];
            if (c == '\'')
                rBuffer.append(c);
            rBuffer.append(c);
        }
        rBuffer.append('\'');
        rBuffer.append(']');
    }
 
    static void makeExternalTabNameRange( OUStringBuffer& rBuf, const OUString& rTabName,
                                          const vector<OUString>& rTabNames,
                                          const ScRange& rRef )
    {
        OUString aLastTabName;
        if (!lcl_getLastTabName(aLastTabName, rTabName, rTabNames, rRef))
        {
            ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
            return;
        }
 
        ScRangeStringConverter::AppendTableName(rBuf, rTabName);
        if (rTabName != aLastTabName)
        {
            rBuf.append(':');
            ScRangeStringConverter::AppendTableName(rBuf, aLastTabName);
        }
    }
 
    virtual void parseExternalDocName( const OUString& rFormula, sal_Int32& rSrcPos ) const
    {
        sal_Int32 nLen = rFormula.getLength();
        const sal_Unicode* p = rFormula.getStr();
        sal_Unicode cPrev = 0;
        for (sal_Int32 i = rSrcPos; i < nLen; ++i)
        {
            sal_Unicode c = p[i];
            if (i == rSrcPos)
            {
                // first character must be '['.
                if (c != '[')
                    return;
            }
            else if (i == rSrcPos + 1)
            {
                // second character must be a single quote.
                if (c != '\'')
                    return;
            }
            else if (c == '\'')
            {
                if (cPrev == '\'')
                    // two successive single quote is treated as a single
                    // valid character.
                    c = 'a';
            }
            else if (c == ']')
            {
                if (cPrev == '\'')
                {
                    // valid source document path found.  Increment the
                    // current position to skip the source path.
                    rSrcPos = i + 1;
                    if (rSrcPos >= nLen)
                        rSrcPos = nLen - 1;
                    return;
                }
                else
                    return;
            }
            else
            {
                // any other character
                if (i > rSrcPos + 2 && cPrev == '\'')
                    // unless it's the 3rd character, a normal character
                    // following immediately a single quote is invalid.
                    return;
            }
            cPrev = c;
        }
    }
};
 
struct ConventionXL_A1 : public Convention_A1, public ConventionXL
{
    ConventionXL_A1() : Convention_A1( FormulaGrammar::CONV_XL_A1 ) { }
    explicit ConventionXL_A1( FormulaGrammar::AddressConvention eConv ) : Convention_A1( eConv ) { }
 
    static void makeSingleCellStr( const ScSheetLimits& rLimits, OUStringBuffer& rBuf, const ScSingleRefData& rRef, const ScAddress& rAbs )
    {
        if (!rRef.IsColRel())
            rBuf.append('$');
        MakeColStr(rLimits, rBuf, rAbs.Col());
        if (!rRef.IsRowRel())
            rBuf.append('$');
        MakeRowStr(rLimits, rBuf, rAbs.Row());
    }
 
    virtual void makeRefStr(
                     ScSheetLimits& rLimits,
                     OUStringBuffer&   rBuf,
                     formula::FormulaGrammar::Grammar /*eGram*/,
                     const ScAddress& rPos,
                     const OUString& rErrRef, const std::vector<OUString>& rTabNames,
                     const ScComplexRefData& rRef,
                     bool bSingleRef,
                     bool /*bFromRangeName*/ ) const override
    {
        ScComplexRefData aRef( rRef );
 
        // Play fast and loose with invalid refs.  There is not much point in producing
        // Foo!A1:#REF! versus #REF! at this point
        ScAddress aAbs1 = aRef.Ref1.toAbs(rLimits, rPos), aAbs2;
 
        MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
 
        if (!rLimits.ValidAddress(aAbs1))
        {
            rBuf.append(rErrRef);
            return;
        }
 
        if( !bSingleRef )
        {
            aAbs2 = aRef.Ref2.toAbs(rLimits, rPos);
            if (!rLimits.ValidAddress(aAbs2))
            {
                rBuf.append(rErrRef);
                return;
            }
 
            if (aAbs1.Col() == 0 && aAbs2.Col() >= rLimits.mnMaxCol)
            {
                if (!aRef.Ref1.IsRowRel())
                    rBuf.append( '$' );
                MakeRowStr(rLimits, rBuf, aAbs1.Row());
                rBuf.append( ':' );
                if (!aRef.Ref2.IsRowRel())
                    rBuf.append( '$' );
                MakeRowStr(rLimits, rBuf, aAbs2.Row());
                return;
            }
 
            if (aAbs1.Row() == 0 && aAbs2.Row() >= rLimits.mnMaxRow)
            {
                if (!aRef.Ref1.IsColRel())
                    rBuf.append( '$' );
                MakeColStr(rLimits, rBuf, aAbs1.Col());
                rBuf.append( ':' );
                if (!aRef.Ref2.IsColRel())
                    rBuf.append( '$' );
                MakeColStr(rLimits, rBuf, aAbs2.Col());
                return;
            }
        }
 
        makeSingleCellStr(rLimits, rBuf, aRef.Ref1, aAbs1);
        if (!bSingleRef && (aAbs1.Row() != aAbs2.Row() || aAbs1.Col() != aAbs2.Col()))
        {
            rBuf.append( ':' );
            makeSingleCellStr(rLimits, rBuf, aRef.Ref2, aAbs2);
        }
    }
 
    virtual ParseResult parseAnyToken( const OUString& rFormula,
                                       sal_Int32 nSrcPos,
                                       const CharClass* pCharClass,
                                       bool bGroupSeparator) const override
    {
        parseExternalDocName(rFormula, nSrcPos);
 
        ParseResult aRet;
        if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
            return aRet;
 
        constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
            KParseTokens::ASC_UNDERSCORE | KParseTokens::ASC_DOLLAR;
        constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
        // '?' allowed in range names
        static constexpr OUString aAddAllowed(u"?!"_ustr);
        return pCharClass->parseAnyToken( rFormula,
                nSrcPos, nStartFlags, aAddAllowed,
                (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
                aAddAllowed );
    }
 
    virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
    {
        return ConventionXL::getSpecialSymbol(eSymType);
    }
 
    virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
            const ScDocument& rDoc,
            const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
    {
        return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
    }
 
    virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
            const OUString& rName ) const override
    {
        return ConventionXL::makeExternalNameStr(rFile, rName);
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const OUString& rTabName, const ScSingleRefData& rRef ) const override
    {
        // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
        // This is a little different from the format Excel uses, as Excel
        // puts [] only around the file name.  But we need to enclose the
        // whole file path with [] because the file name can contain any
        // characters.
 
        ConventionXL::makeExternalDocStr(rBuffer, rFileName);
        ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
        rBuffer.append('!');
 
        makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const std::vector<OUString>& rTabNames, const OUString& rTabName,
        const ScComplexRefData& rRef ) const override
    {
        ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
 
        ConventionXL::makeExternalDocStr(rBuffer, rFileName);
        ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
        rBuffer.append('!');
 
        makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
        if (aAbsRef.aStart != aAbsRef.aEnd)
        {
            rBuffer.append(':');
            makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
        }
    }
};
 
struct ConventionXL_OOX : public ConventionXL_A1
{
    ConventionXL_OOX() : ConventionXL_A1( FormulaGrammar::CONV_XL_OOX ) { }
 
    virtual void makeRefStr( ScSheetLimits& rLimits,
                     OUStringBuffer&   rBuf,
                     formula::FormulaGrammar::Grammar eGram,
                     const ScAddress& rPos,
                     const OUString& rErrRef, const std::vector<OUString>& rTabNames,
                     const ScComplexRefData& rRef,
                     bool bSingleRef,
                     bool bFromRangeName ) const override
    {
        // In OOXML relative references in named expressions are relative to
        // column 0 and row 0. Relative sheet references don't exist.
        ScAddress aPos( rPos );
        if (bFromRangeName)
        {
            // XXX NOTE: by decrementing the reference position we may end up
            // with resolved references with negative values. There's no proper
            // way to solve that or wrap them around without sheet dimensions
            // that are stored along. That, or blindly assume fixed dimensions
            // here and in import.
            /* TODO: maybe do that blind fixed dimensions wrap? */
            aPos.SetCol(0);
            aPos.SetRow(0);
        }
 
        if (rRef.Ref1.IsDeleted() || (!bSingleRef && rRef.Ref2.IsDeleted()))
        {
            // For OOXML write plain "#REF!" instead of detailed sheet/col/row
            // information.
            rBuf.append(rErrRef);
            return;
        }
 
        {
            ScAddress aAbs1 = rRef.Ref1.toAbs(rLimits, rPos);
            if (!rLimits.ValidAddress(aAbs1)
                || o3tl::make_unsigned(aAbs1.Tab()) >= rTabNames.size())
            {
                rBuf.append(rErrRef);
                return;
            }
        }
 
        if (!bSingleRef)
        {
            ScAddress aAbs2 = rRef.Ref2.toAbs(rLimits, rPos);
            if (!rLimits.ValidAddress(aAbs2)
                || o3tl::make_unsigned(aAbs2.Tab()) >= rTabNames.size())
            {
                rBuf.append(rErrRef);
                return;
            }
        }
 
        ConventionXL_A1::makeRefStr( rLimits, rBuf, eGram, aPos, rErrRef, rTabNames, rRef, bSingleRef, bFromRangeName);
    }
 
    virtual OUString makeExternalNameStr( sal_uInt16 nFileId, const OUString& /*rFile*/,
            const OUString& rName ) const override
    {
        // [N]!DefinedName is a workbook global name.
        return OUString( "[" + OUString::number(nFileId+1) + "]!" + rName );
 
        /* TODO: add support for sheet local names, would be
         * [N]'Sheet Name'!DefinedName
         * Similar to makeExternalRefStr() but with DefinedName instead of
         * CellStr. */
    }
 
    virtual void parseExternalDocName(const OUString& rFormula, sal_Int32& rSrcPos) const override
    {
        sal_Int32 nLen = rFormula.getLength();
        const sal_Unicode* p = rFormula.getStr();
        for (sal_Int32 i = rSrcPos; i < nLen; ++i)
        {
            sal_Unicode c = p[i];
            if (i == rSrcPos)
            {
                // first character must be '['.
                if (c != '[')
                    return;
            }
            else if (c == ']')
            {
                rSrcPos = i + 1;
                return;
            }
        }
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
        const OUString& rTabName, const ScSingleRefData& rRef ) const override
    {
        // '[N]Sheet Name'!$A$1 or [N]SheetName!$A$1
        // Where N is a 1-based positive integer number of a file name in OOXML
        // xl/externalLinks/externalLinkN.xml
 
        OUString aQuotedTab( rTabName);
        ScCompiler::CheckTabQuotes( aQuotedTab);
        if (!aQuotedTab.isEmpty() && aQuotedTab[0] == '\'')
        {
            rBuffer.append('\'');
            ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
            rBuffer.append( aQuotedTab.subView(1));
        }
        else
        {
            ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
            rBuffer.append( aQuotedTab);
        }
        rBuffer.append('!');
 
        makeSingleCellStr(rLimits, rBuffer, rRef, rRef.toAbs(rLimits, rPos));
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 nFileId, const OUString& /*rFileName*/,
        const std::vector<OUString>& rTabNames, const OUString& rTabName,
        const ScComplexRefData& rRef ) const override
    {
        // '[N]Sheet One':'Sheet Two'!A1:B2 or [N]SheetOne!A1:B2
        // Actually Excel writes '[N]Sheet One:Sheet Two'!A1:B2 but reads the
        // simpler to produce and more logical form with independently quoted
        // sheet names as well. The [N] having to be within the quoted sheet
        // name is ugly enough...
 
        ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
 
        OUStringBuffer aBuf;
        ConventionXL::makeExternalTabNameRange( aBuf, rTabName, rTabNames, aAbsRef);
        if (!aBuf.isEmpty() && aBuf[0] == '\'')
        {
            rBuffer.append('\'');
            ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
            rBuffer.append( aBuf.subView(1));
        }
        else
        {
            ConventionXL_OOX::makeExternalDocStr( rBuffer, nFileId);
            rBuffer.append( aBuf);
        }
        rBuffer.append('!');
 
        makeSingleCellStr(rLimits, rBuffer, rRef.Ref1, aAbsRef.aStart);
        if (aAbsRef.aStart != aAbsRef.aEnd)
        {
            rBuffer.append(':');
            makeSingleCellStr(rLimits, rBuffer, rRef.Ref2, aAbsRef.aEnd);
        }
    }
 
    static void makeExternalDocStr( OUStringBuffer& rBuffer, sal_uInt16 nFileId )
    {
        rBuffer.append("[" + OUString::number( static_cast<sal_Int32>(nFileId+1) ) + "]");
    }
};
 
}
 
static void
r1c1_add_col( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
{
    rBuf.append( 'C' );
    if( rRef.IsColRel() )
    {
        SCCOL nCol = rRef.Col();
        if (nCol != 0)
            rBuf.append("[" + OUString::number(nCol) + "]");
    }
    else
        rBuf.append( static_cast<sal_Int32>(rAbsRef.Col() + 1) );
}
static void
r1c1_add_row( OUStringBuffer &rBuf, const ScSingleRefData& rRef, const ScAddress& rAbsRef )
{
    rBuf.append( 'R' );
    if( rRef.IsRowRel() )
    {
        if (rRef.Row() != 0)
        {
            rBuf.append("[" + OUString::number(rRef.Row()) + "]");
        }
    }
    else
        rBuf.append( rAbsRef.Row() + 1 );
}
 
namespace {
 
struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL
{
    ConventionXL_R1C1() : ScCompiler::Convention( FormulaGrammar::CONV_XL_R1C1 ) { }
 
    virtual void makeRefStr( ScSheetLimits& rLimits,
                     OUStringBuffer&   rBuf,
                     formula::FormulaGrammar::Grammar /*eGram*/,
                     const ScAddress& rPos,
                     const OUString& rErrRef, const std::vector<OUString>& rTabNames,
                     const ScComplexRefData& rRef,
                     bool bSingleRef,
                     bool /*bFromRangeName*/ ) const override
    {
        ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
        ScComplexRefData aRef( rRef );
 
        MakeTabStr(rLimits, rBuf, rPos, rTabNames, aRef, bSingleRef);
 
        // Play fast and loose with invalid refs.  There is not much point in producing
        // Foo!A1:#REF! versus #REF! at this point
        if (!rLimits.ValidCol(aAbsRef.aStart.Col()) || !rLimits.ValidRow(aAbsRef.aStart.Row()))
        {
            rBuf.append(rErrRef);
            return;
        }
 
        if( !bSingleRef )
        {
            if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
            {
                rBuf.append(rErrRef);
                return;
            }
 
            if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
            {
                r1c1_add_row(rBuf,  rRef.Ref1, aAbsRef.aStart);
                if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() ||
                    rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel() )
                {
                    rBuf.append( ':' );
                    r1c1_add_row(rBuf,  rRef.Ref2, aAbsRef.aEnd);
                }
                return;
 
            }
 
            if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
            {
                r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
                if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() ||
                    rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
                {
                    rBuf.append( ':' );
                    r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
                }
                return;
            }
        }
 
        r1c1_add_row(rBuf, rRef.Ref1, aAbsRef.aStart);
        r1c1_add_col(rBuf, rRef.Ref1, aAbsRef.aStart);
        // We can't parse a single col/row reference in the context of a R1C1
        // 3D reference back yet, otherwise (if Excel understands it) an
        // additional condition similar to ConventionXL_A1::makeRefStr() could
        // be
        //
        //   && (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || aAbsRef.aStart.Col() != aAbsRef.aEnd.Col())
        if (!bSingleRef)
        {
            rBuf.append( ':' );
            r1c1_add_row(rBuf, rRef.Ref2, aAbsRef.aEnd);
            r1c1_add_col(rBuf, rRef.Ref2, aAbsRef.aEnd);
        }
    }
 
    ParseResult parseAnyToken( const OUString& rFormula,
                               sal_Int32 nSrcPos,
                               const CharClass* pCharClass,
                               bool bGroupSeparator) const override
    {
        parseExternalDocName(rFormula, nSrcPos);
 
        ParseResult aRet;
        if ( lcl_isValidQuotedText(rFormula, nSrcPos, aRet) )
            return aRet;
 
        constexpr sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER |
            KParseTokens::ASC_UNDERSCORE ;
        constexpr sal_Int32 nContFlags = nStartFlags | KParseTokens::ASC_DOT;
        // '?' allowed in range names
        static constexpr OUString aAddAllowed(u"?-[]!"_ustr);
 
        return pCharClass->parseAnyToken( rFormula,
                nSrcPos, nStartFlags, aAddAllowed,
                (bGroupSeparator ? nContFlags | KParseTokens::GROUP_SEPARATOR_IN_NUMBER : nContFlags),
                aAddAllowed );
    }
 
    virtual sal_Unicode getSpecialSymbol( SpecialSymbolType eSymType ) const override
    {
        return ConventionXL::getSpecialSymbol(eSymType);
    }
 
    virtual bool parseExternalName( const OUString& rSymbol, OUString& rFile, OUString& rName,
            const ScDocument& rDoc,
            const uno::Sequence<sheet::ExternalLinkInfo>* pExternalLinks ) const override
    {
        return ConventionXL::parseExternalName( rSymbol, rFile, rName, rDoc, pExternalLinks);
    }
 
    virtual OUString makeExternalNameStr( sal_uInt16 /*nFileId*/, const OUString& rFile,
            const OUString& rName ) const override
    {
        return ConventionXL::makeExternalNameStr(rFile, rName);
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const OUString& rTabName, const ScSingleRefData& rRef ) const override
    {
        // ['file:///path/to/file/filename.xls']'Sheet Name'!$A$1
        // This is a little different from the format Excel uses, as Excel
        // puts [] only around the file name.  But we need to enclose the
        // whole file path with [] because the file name can contain any
        // characters.
 
        ScAddress aAbsRef = rRef.toAbs(rLimits, rPos);
        ConventionXL::makeExternalDocStr(rBuffer, rFileName);
        ScRangeStringConverter::AppendTableName(rBuffer, rTabName);
        rBuffer.append('!');
 
        r1c1_add_row(rBuffer, rRef, aAbsRef);
        r1c1_add_col(rBuffer, rRef, aAbsRef);
    }
 
    virtual void makeExternalRefStr(
        ScSheetLimits& rLimits,
        OUStringBuffer& rBuffer, const ScAddress& rPos, sal_uInt16 /*nFileId*/, const OUString& rFileName,
        const std::vector<OUString>& rTabNames, const OUString& rTabName,
        const ScComplexRefData& rRef ) const override
    {
        ScRange aAbsRef = rRef.toAbs(rLimits, rPos);
 
        ConventionXL::makeExternalDocStr(rBuffer, rFileName);
        ConventionXL::makeExternalTabNameRange(rBuffer, rTabName, rTabNames, aAbsRef);
        rBuffer.append('!');
 
        if (!rLimits.ValidCol(aAbsRef.aEnd.Col()) || !rLimits.ValidRow(aAbsRef.aEnd.Row()))
        {
            rBuffer.append(ScResId(STR_NO_REF_TABLE));
            return;
        }
 
        if (aAbsRef.aStart.Col() == 0 && aAbsRef.aEnd.Col() >= rLimits.mnMaxCol)
        {
            r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
            if (aAbsRef.aStart.Row() != aAbsRef.aEnd.Row() || rRef.Ref1.IsRowRel() != rRef.Ref2.IsRowRel())
            {
                rBuffer.append(':');
                r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
            }
            return;
        }
 
        if (aAbsRef.aStart.Row() == 0 && aAbsRef.aEnd.Row() >= rLimits.mnMaxRow)
        {
            r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
            if (aAbsRef.aStart.Col() != aAbsRef.aEnd.Col() || rRef.Ref1.IsColRel() != rRef.Ref2.IsColRel())
            {
                rBuffer.append(':');
                r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
            }
            return;
        }
 
        r1c1_add_row(rBuffer, rRef.Ref1, aAbsRef.aStart);
        r1c1_add_col(rBuffer, rRef.Ref1, aAbsRef.aStart);
        rBuffer.append(':');
        r1c1_add_row(rBuffer, rRef.Ref2, aAbsRef.aEnd);
        r1c1_add_col(rBuffer, rRef.Ref2, aAbsRef.aEnd);
    }
 
    virtual ScCharFlags getCharTableFlags( sal_Unicode c, sal_Unicode cLast ) const override
    {
        ScCharFlags nFlags = mpCharTable[static_cast<sal_uInt8>(c)];
        if (c == '-' && cLast == '[')
            // '-' can occur within a reference string only after '[' e.g. R[-1]C.
            nFlags |= ScCharFlags::Ident;
        return nFlags;
    }
};
 
}
 
ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr,
                        bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
    : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
    rDoc(rCxt.getDoc()),
    aPos(rPos),
    mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
    mnCurrentSheetTab(-1),
    mnCurrentSheetEndPos(0),
    pCharClass(&ScGlobal::getCharClass()),
    mbCharClassesDiffer(false),
    mnPredetectedReference(0),
    mnRangeOpPosInSymbol(-1),
    pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
    meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
    mbCloseBrackets(true),
    mbRewind(false),
    mbRefConventionChartOOXML(false),
    maTabNames(rCxt.getTabNames())
{
    SetGrammar(rCxt.getGrammar());
    m_oODFSavingVersion = rCxt.getODFSavingVersion();
}
 
ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos, ScTokenArray& rArr,
                        formula::FormulaGrammar::Grammar eGrammar,
                        bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
    : FormulaCompiler(rArr, bComputeII, bMatrixFlag),
        rDoc( rDocument ),
        aPos( rPos ),
        mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
        mnCurrentSheetTab(-1),
        mnCurrentSheetEndPos(0),
        nSrcPos(0),
        pCharClass( &ScGlobal::getCharClass() ),
        mbCharClassesDiffer(false),
        mnPredetectedReference(0),
        mnRangeOpPosInSymbol(-1),
        pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
        meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
        mbCloseBrackets( true ),
        mbRewind( false ),
        mbRefConventionChartOOXML( false )
{
    SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
                rDocument.GetGrammar() :
                eGrammar );
}
 
ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos,
                        bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
    : FormulaCompiler(bComputeII, bMatrixFlag),
    rDoc(rCxt.getDoc()),
    aPos(rPos),
    mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
    mnCurrentSheetTab(-1),
    mnCurrentSheetEndPos(0),
    pCharClass(&ScGlobal::getCharClass()),
    mbCharClassesDiffer(false),
    mnPredetectedReference(0),
    mnRangeOpPosInSymbol(-1),
    pConv(GetRefConvention(FormulaGrammar::CONV_OOO)),
    meExtendedErrorDetection(EXTENDED_ERROR_DETECTION_NONE),
    mbCloseBrackets(true),
    mbRewind(false),
    mbRefConventionChartOOXML(false),
    maTabNames(rCxt.getTabNames())
{
    SetGrammar(rCxt.getGrammar());
}
 
ScCompiler::ScCompiler( ScDocument& rDocument, const ScAddress& rPos,
                        formula::FormulaGrammar::Grammar eGrammar,
                        bool bComputeII, bool bMatrixFlag, ScInterpreterContext* pContext )
        : FormulaCompiler(bComputeII, bMatrixFlag),
        rDoc( rDocument ),
        aPos( rPos ),
        mrInterpreterContext(pContext ? *pContext : rDoc.GetNonThreadedContext()),
        mnCurrentSheetTab(-1),
        mnCurrentSheetEndPos(0),
        nSrcPos(0),
        pCharClass( &ScGlobal::getCharClass() ),
        mbCharClassesDiffer(false),
        mnPredetectedReference(0),
        mnRangeOpPosInSymbol(-1),
        pConv( GetRefConvention( FormulaGrammar::CONV_OOO ) ),
        meExtendedErrorDetection( EXTENDED_ERROR_DETECTION_NONE ),
        mbCloseBrackets( true ),
        mbRewind( false ),
        mbRefConventionChartOOXML( false )
{
    SetGrammar( (eGrammar == formula::FormulaGrammar::GRAM_UNSPECIFIED) ?
                rDocument.GetGrammar() :
                eGrammar );
}
 
ScCompiler::~ScCompiler()
{
}
 
void ScCompiler::CheckTabQuotes( OUString& rString,
                                 const FormulaGrammar::AddressConvention eConv )
{
    sal_Int32 nStartFlags = KParseTokens::ANY_LETTER_OR_NUMBER | KParseTokens::ASC_UNDERSCORE;
    sal_Int32 nContFlags = nStartFlags;
    ParseResult aRes = ScGlobal::getCharClass().parsePredefinedToken(
        KParseType::IDENTNAME, rString, 0, nStartFlags, OUString(), nContFlags, OUString());
    bool bNeedsQuote = !((aRes.TokenType & KParseType::IDENTNAME) && aRes.EndPos == rString.getLength());
 
    switch ( eConv )
    {
        default :
        case FormulaGrammar::CONV_UNSPECIFIED :
            break;
        case FormulaGrammar::CONV_OOO :
        case FormulaGrammar::CONV_XL_A1 :
        case FormulaGrammar::CONV_XL_R1C1 :
        case FormulaGrammar::CONV_XL_OOX :
        case FormulaGrammar::CONV_ODF :
            if( bNeedsQuote )
            {
                // escape embedded quotes
                rString = rString.replaceAll( "'", "''" );
            }
            break;
    }
 
    if ( !bNeedsQuote && CharClass::isAsciiNumeric( rString ) )
    {
        // Prevent any possible confusion resulting from pure numeric sheet names.
        bNeedsQuote = true;
    }
 
    if( bNeedsQuote )
    {
        rString = "'" + rString + "'";
    }
}
 
void ScCompiler::FormExcelSheetRange( OUStringBuffer& rBuf, sal_Int32 nQuotePos, const OUString& rEndTabName )
{
    OUString aEndTabName(rEndTabName);
    if (nQuotePos < rBuf.getLength())
    {
        const bool bQuoted2 = (!aEndTabName.isEmpty() && aEndTabName[0] == '\'');
        if (bQuoted2)
            aEndTabName = aEndTabName.subView(1);   //  Sheet2'
        if (rBuf[nQuotePos] == '\'')                // 'Sheet1'
        {
            const sal_Int32 nLast = rBuf.getLength() - 1;
            if (rBuf[nLast] == '\'')
                rBuf.remove(nLast, 1);              // 'Sheet1
        }
        else if (bQuoted2)                          //  Sheet1
        {
            rBuf.insert(nQuotePos, '\'');           // 'Sheet1
        }
    }
    rBuf.append( ':' );
    rBuf.append( aEndTabName );
}
 
sal_Int32 ScCompiler::GetDocTabPos( const OUString& rString )
{
    if (rString[0] != '\'')
        return -1;
    sal_Int32 nPos = ScGlobal::FindUnquoted( rString, SC_COMPILER_FILE_TAB_SEP);
    // it must be 'Doc'#
    if (nPos != -1 && rString[nPos-1] != '\'')
        nPos = -1;
    return nPos;
}
 
void ScCompiler::SetRefConvention( FormulaGrammar::AddressConvention eConv )
{
    const Convention* p = GetRefConvention(eConv);
    if (p)
        SetRefConvention(p);
}
 
const ScCompiler::Convention* ScCompiler::GetRefConvention( FormulaGrammar::AddressConvention eConv )
{
 
    switch (eConv)
    {
        case FormulaGrammar::CONV_OOO:
        {
            static const ConventionOOO_A1 ConvOOO_A1;
            return &ConvOOO_A1;
        }
        case FormulaGrammar::CONV_ODF:
        {
            static const ConventionOOO_A1_ODF ConvOOO_A1_ODF;
            return &ConvOOO_A1_ODF;
        }
        case FormulaGrammar::CONV_XL_A1:
        {
            static const ConventionXL_A1 ConvXL_A1;
            return &ConvXL_A1;
        }
        case FormulaGrammar::CONV_XL_R1C1:
        {
            static const ConventionXL_R1C1 ConvXL_R1C1;
            return &ConvXL_R1C1;
        }
        case FormulaGrammar::CONV_XL_OOX:
        {
            static const ConventionXL_OOX ConvXL_OOX;
            return &ConvXL_OOX;
        }
        case FormulaGrammar::CONV_UNSPECIFIED:
        default:
            ;
    }
 
    return nullptr;
}
 
void ScCompiler::SetRefConvention( const ScCompiler::Convention *pConvP )
{
    pConv = pConvP;
    meGrammar = FormulaGrammar::mergeToGrammar( meGrammar, pConv->meConv);
    assert( FormulaGrammar::isSupported( meGrammar));
}
 
void ScCompiler::SetError(FormulaError nError)
{
    if( pArr->GetCodeError() == FormulaError::NONE)
        pArr->SetCodeError( nError);
}
 
static sal_Unicode* lcl_UnicodeStrNCpy( sal_Unicode* pDst, const sal_Unicode* pSrc, sal_Int32 nMax )
{
    const sal_Unicode* const pStop = pDst + nMax;
    while ( pDst < pStop )
    {
        *pDst++ = *pSrc++;
    }
    *pDst = 0;
    return pDst;
}
 
// p1 MUST contain at least n characters, or terminate with NIL.
// p2 MUST pass upper case letters, if any.
// n  MUST not be greater than length of p2
static bool lcl_isUnicodeIgnoreAscii( const sal_Unicode* p1, const char* p2, size_t n )
{
    for (size_t i=0; i<n; ++i)
    {
        if (!p1[i])
            return false;
        if (p1[i] != p2[i])
        {
            if (p1[i] < 'a' || 'z' < p1[i])
                return false;   // not a lower case letter
            if (p2[i] < 'A' || 'Z' < p2[i])
                return false;   // not a letter to match
            if (p1[i] != p2[i] + 0x20)
                return false;   // lower case doesn't match either
        }
    }
    return true;
}
 
// static
void ScCompiler::addWhitespace( std::vector<ScCompiler::Whitespace> & rvSpaces,
        ScCompiler::Whitespace & rSpace, sal_Unicode c, sal_Int32 n )
{
    if (rSpace.cChar != c)
    {
        if (rSpace.cChar && rSpace.nCount > 0)
            rvSpaces.emplace_back(rSpace);
        rSpace.reset(c);
    }
    rSpace.nCount += n;
}
 
// NextSymbol
 
// Parses the formula into separate symbols for further processing.
// XXX NOTE: this is a rough sketch of the original idea, there are other
// states that were added and didn't make it into this table and things are
// more complicated. Use the source, Luke.
 
// initial state = GetChar
 
// old state     | read character    | action                | new state
//---------------+-------------------+-----------------------+---------------
// GetChar       | ;()+-*/^=&        | Symbol=char           | Stop
//               | <>                | Symbol=char           | GetBool
//               | $ letter          | Symbol=char           | GetWord
//               | number            | Symbol=char           | GetValue
//               | "                 | none                  | GetString
//               | other             | none                  | GetChar
//---------------+-------------------+-----------------------+---------------
// GetBool       | =>                | Symbol=Symbol+char    | Stop
//               | other             | Dec(CharPos)          | Stop
//---------------+-------------------+-----------------------+---------------
// GetWord       | SepSymbol         | Dec(CharPos)          | Stop
//               | ()+-*/^=<>&~      |                       |
//               | space             | Dec(CharPos)          | Stop
//               | $_:.              |                       |
//               | letter, number    | Symbol=Symbol+char    | GetWord
//               | other             | error                 | Stop
//---------------+-------------------+-----------------------+---------------
// GetValue      | ;()*/^=<>&        |                       |
//               | space             | Dec(CharPos)          | Stop
//               | number E+-%,.     | Symbol=Symbol+char    | GetValue
//               | other             | error                 | Stop
//---------------+-------------------+-----------------------+---------------
// GetString     | "                 | none                  | Stop
//               | other             | Symbol=Symbol+char    | GetString
//---------------+-------------------+-----------------------+---------------
 
std::vector<ScCompiler::Whitespace> ScCompiler::NextSymbol(bool bInArray)
{
    std::vector<Whitespace> vSpaces;
    cSymbol[MAXSTRLEN] = 0;       // end
    sal_Unicode* pSym = cSymbol;
    const sal_Unicode* const pStart = aFormula.getStr();
    const sal_Unicode* pSrc = pStart + nSrcPos;
    bool bi18n = false;
    sal_Unicode c = *pSrc;
    sal_Unicode cLast = 0;
    bool bQuote = false;
    mnRangeOpPosInSymbol = -1;
    ScanState eState = ssGetChar;
    Whitespace aSpace;
    sal_Unicode cSep = mxSymbols->getSymbolChar( ocSep);
    sal_Unicode cArrayColSep = mxSymbols->getSymbolChar( ocArrayColSep);
    sal_Unicode cArrayRowSep = mxSymbols->getSymbolChar( ocArrayRowSep);
    sal_Unicode cDecSep = (mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0]);
    sal_Unicode cDecSepAlt = (mxSymbols->isEnglishLocale() ? 0 : ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar());
 
    // special symbols specific to address convention used
    sal_Unicode cSheetPrefix = pConv->getSpecialSymbol(ScCompiler::Convention::ABS_SHEET_PREFIX);
    sal_Unicode cSheetSep    = pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR);
 
    int nDecSeps = 0;
    bool bAutoIntersection = false;
    size_t nAutoIntersectionSpacesPos = 0;
    int nRefInName = 0;
    bool bErrorConstantHadSlash = false;
    mnPredetectedReference = 0;
    // try to parse simple tokens before calling i18n parser
    while ((c != 0) && (eState != ssStop) )
    {
        pSrc++;
        ScCharFlags nMask = GetCharTableFlags( c, cLast );
 
        // The parameter separator and the array column and row separators end
        // things unconditionally if not in string or reference.
        if (c == cSep || (bInArray && (c == cArrayColSep || c == cArrayRowSep)))
        {
            switch (eState)
            {
                // these are to be continued
                case ssGetString:
                case ssSkipString:
                case ssGetReference:
                case ssSkipReference:
                case ssGetTableRefItem:
                case ssGetTableRefColumn:
                    break;
                default:
                    if (eState == ssGetChar)
                        *pSym++ = c;
                    else
                        pSrc--;
                    eState = ssStop;
            }
        }
Label_MaskStateMachine:
        switch (eState)
        {
            case ssGetChar :
            {
                // Order is important!
                if (eLastOp == ocTableRefOpen && c != '[' && c != '#' && c != ']')
                {
                    *pSym++ = c;
                    eState = ssGetTableRefColumn;
                }
                else if( nMask & ScCharFlags::OdfLabelOp )
                {
                    // '!!' automatic intersection
                    if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfLabelOp)
                    {
                        /* TODO: For now the UI "space operator" is used, this
                         * could be enhanced using a specialized OpCode to get
                         * rid of the space ambiguity, which would need some
                         * places to be adapted though. And we would still need
                         * to support the ambiguous space operator for UI
                         * purposes anyway. However, we then could check for
                         * invalid usage of '!!', which currently isn't
                         * possible. */
                        if (!bAutoIntersection)
                        {
                            ++pSrc;
                            // Add 2 because it must match the character count
                            // for bi18n.
                            addWhitespace( vSpaces, aSpace, 0x20, 2);
                            // Position of Whitespace where it will be added to
                            // vector.
                            nAutoIntersectionSpacesPos = vSpaces.size();
                            bAutoIntersection = true;
                        }
                        else
                        {
                            pSrc--;
                            eState = ssStop;
                        }
                    }
                    else
                    {
                        nMask &= ~ScCharFlags::OdfLabelOp;
                        goto Label_MaskStateMachine;
                    }
                }
                else if( nMask & ScCharFlags::OdfNameMarker )
                {
                    // '$$' defined name marker
                    if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::OdfNameMarker)
                    {
                        // both eaten, not added to pSym
                        ++pSrc;
                    }
                    else
                    {
                        nMask &= ~ScCharFlags::OdfNameMarker;
                        goto Label_MaskStateMachine;
                    }
                }
                else if( nMask & ScCharFlags::Char )
                {
                    // '[' is a special case in Excel syntax, it can start an
                    // external reference, ID in OOXML like [1]Sheet1!A1 or
                    // Excel_A1 [filename]Sheet!A1 or Excel_R1C1
                    // [filename]Sheet!R1C1 that needs to be scanned
                    // entirely, or can be ocTableRefOpen, of which the first
                    // transforms an ocDBArea into an ocTableRef.
                    if (c == '[' && FormulaGrammar::isExcelSyntax( meGrammar)
                            && eLastOp != ocDBArea && maTableRefs.empty())
                    {
                        // [0]!Global_Range_Name, is a special case in OOXML
                        // syntax, where the '0' is referencing to self and we
                        // do not need it, so we should skip it, in order to
                        // later it will be more recognisable for IsNamedRange.
                        if (FormulaGrammar::isRefConventionOOXML(meGrammar) &&
                                pSrc[0] == '0' && pSrc[1] == ']' && pSrc[2] == '!')
                        {
                            pSrc += 3;
                            c = *pSrc;
                            continue;
                        }
 
                        nMask &= ~ScCharFlags::Char;
                        goto Label_MaskStateMachine;
                    }
                    else
                    {
                        *pSym++ = c;
                        eState = ssStop;
                    }
                }
                else if( nMask & ScCharFlags::OdfLBracket )
                {
                    // eaten, not added to pSym
                    eState = ssGetReference;
                    mnPredetectedReference = 1;
                }
                else if( nMask & ScCharFlags::CharBool )
                {
                    *pSym++ = c;
                    eState = ssGetBool;
                }
                else if( nMask & ScCharFlags::CharValue )
                {
                    *pSym++ = c;
                    eState = ssGetValue;
                }
                else if( nMask & ScCharFlags::CharString )
                {
                    *pSym++ = c;
                    eState = ssGetString;
                }
                else if( nMask & ScCharFlags::CharErrConst )
                {
                    *pSym++ = c;
                    sal_uInt16 nLevel;
                    if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
                        eState = ssGetTableRefItem;
                    else
                        eState = ssGetErrorConstant;
                }
                else if( nMask & ScCharFlags::CharDontCare )
                {
                    addWhitespace( vSpaces, aSpace, c);
                }
                else if( nMask & ScCharFlags::CharIdent )
                {   // try to get a simple ASCII identifier before calling
                    // i18n, to gain performance during import
                    *pSym++ = c;
                    eState = ssGetIdent;
                }
                else
                {
                    bi18n = true;
                    eState = ssStop;
                }
            }
            break;
            case ssGetIdent:
            {
                if ( nMask & ScCharFlags::Ident )
                {   // This catches also $Sheet1.A$1, for example.
                    if( pSym == &cSymbol[ MAXSTRLEN ] )
                    {
                        SetError(FormulaError::StringOverflow);
                        eState = ssStop;
                    }
                    else
                        *pSym++ = c;
                }
                else if (c == '#' && lcl_isUnicodeIgnoreAscii( pSrc, "REF!", 4))
                {
                    // Completely ugly means to catch broken
                    // [$]#REF!.[$]#REF![$]#REF! (one or multiple parts)
                    // references that were written in ODF named ranges
                    // (without embracing [] hence no predetected reference)
                    // and to OOXML and handle them as one symbol.
                    // Also catches these in UI, so we can process them
                    // further.
                    int i = 0;
                    for ( ; i<5; ++i)
                    {
                        if( pSym == &cSymbol[ MAXSTRLEN ] )
                        {
                            SetError(FormulaError::StringOverflow);
                            eState = ssStop;
                            break;  // for
                        }
                        else
                        {
                            *pSym++ = c;
                            c = *pSrc++;
                        }
                    }
                    if (i == 5)
                        c = *((--pSrc)-1);  // position last/next character correctly
                }
                else if (c == ':' && mnRangeOpPosInSymbol < 0)
                {
                    // One range operator may form Sheet1.A:A, which we need to
                    // pass as one entity to IsReference().
                    if( pSym == &cSymbol[ MAXSTRLEN ] )
                    {
                        SetError(FormulaError::StringOverflow);
                        eState = ssStop;
                    }
                    else
                    {
                        mnRangeOpPosInSymbol = pSym - &cSymbol[0];
                        *pSym++ = c;
                    }
                }
                else if ( 128 <= c || '\'' == c )
                {   // High values need reparsing with i18n,
                    // single quoted $'sheet' names too (otherwise we'd had to
                    // implement everything twice).
                    bi18n = true;
                    eState = ssStop;
                }
                else
                {
                    pSrc--;
                    eState = ssStop;
                }
            }
            break;
            case ssGetBool :
            {
                if( nMask & ScCharFlags::Bool )
                {
                    *pSym++ = c;
                    eState = ssStop;
                }
                else
                {
                    pSrc--;
                    eState = ssStop;
                }
            }
            break;
            case ssGetValue :
            {
                if( pSym == &cSymbol[ MAXSTRLEN ] )
                {
                    SetError(FormulaError::StringOverflow);
                    eState = ssStop;
                }
                else if (c == cDecSep || (cDecSepAlt && c == cDecSepAlt))
                {
                    if (++nDecSeps > 1)
                    {
                        // reparse with i18n, may be numeric sheet name as well
                        bi18n = true;
                        eState = ssStop;
                    }
                    else
                        *pSym++ = c;
                }
                else if( nMask & ScCharFlags::Value )
                    *pSym++ = c;
                else if( nMask & ScCharFlags::ValueSep )
                {
                    pSrc--;
                    eState = ssStop;
                }
                else if (c == 'E' || c == 'e')
                {
                    if (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueExp)
                        *pSym++ = c;
                    else
                    {
                        // reparse with i18n
                        bi18n = true;
                        eState = ssStop;
                    }
                }
                else if( nMask & ScCharFlags::ValueSign )
                {
                    if (((cLast == 'E') || (cLast == 'e')) &&
                            (GetCharTableFlags( pSrc[0], 0 ) & ScCharFlags::ValueValue))
                    {
                        *pSym++ = c;
                    }
                    else
                    {
                        pSrc--;
                        eState = ssStop;
                    }
                }
                else
                {
                    // reparse with i18n
                    bi18n = true;
                    eState = ssStop;
                }
            }
            break;
            case ssGetString :
            {
                if( nMask & ScCharFlags::StringSep )
                {
                    if ( !bQuote )
                    {
                        if ( *pSrc == '"' )
                            bQuote = true;      // "" => literal "
                        else
                            eState = ssStop;
                    }
                    else
                        bQuote = false;
                }
                if ( !bQuote )
                {
                    if( pSym == &cSymbol[ MAXSTRLEN ] )
                    {
                        SetError(FormulaError::StringOverflow);
                        eState = ssSkipString;
                    }
                    else
                        *pSym++ = c;
                }
            }
            break;
            case ssSkipString:
                if( nMask & ScCharFlags::StringSep )
                    eState = ssStop;
                break;
            case ssGetErrorConstant:
                {
                    // ODFF Error ::= '#' [A-Z0-9]+ ([!?] | ('/' ([A-Z] | ([0-9] [!?]))))
                    // BUT, in UI these may have been translated! So don't
                    // check for ASCII alnum. Note that this construct can't be
                    // parsed with i18n.
                    /* TODO: be strict when reading ODFF, check for ASCII alnum
                     * and proper continuation after '/'. However, even with
                     * the lax parsing only the error constants we have defined
                     * as opcode symbols will be recognized and others result
                     * in ocBad, so the result is actually conformant. */
                    bool bAdd = true;
                    if ('?' == c)
                        eState = ssStop;
                    else if ('!' == c)
                    {
                        // Check if this is #REF! that starts an invalid reference.
                        // Note we have an implicit '!' here at the end.
                        if (pSym - &cSymbol[0] == 4 && lcl_isUnicodeIgnoreAscii( cSymbol, "#REF", 4) &&
                                (GetCharTableFlags( *pSrc, c) & ScCharFlags::Ident))
                            eState = ssGetIdent;
                        else
                            eState = ssStop;
                    }
                    else if ('/' == c)
                    {
                        if (!bErrorConstantHadSlash)
                            bErrorConstantHadSlash = true;
                        else
                        {
                            bAdd = false;
                            eState = ssStop;
                        }
                    }
                    else if ((nMask & ScCharFlags::WordSep) ||
                            (c < 128 && !rtl::isAsciiAlphanumeric( c)))
                    {
                        bAdd = false;
                        eState = ssStop;
                    }
                    if (!bAdd)
                        --pSrc;
                    else
                    {
                        if (pSym == &cSymbol[ MAXSTRLEN ])
                        {
                            SetError( FormulaError::StringOverflow);
                            eState = ssStop;
                        }
                        else
                            *pSym++ = c;
                    }
                }
                break;
            case ssGetTableRefItem:
                {
                    // Scan whatever up to the next ']' closer.
                    if (c != ']')
                    {
                        if( pSym == &cSymbol[ MAXSTRLEN ] )
                        {
                            SetError( FormulaError::StringOverflow);
                            eState = ssStop;
                        }
                        else
                            *pSym++ = c;
                    }
                    else
                    {
                        --pSrc;
                        eState = ssStop;
                    }
                }
                break;
            case ssGetTableRefColumn:
                {
                    // Scan whatever up to the next unescaped ']' closer.
                    if (c != ']' || cLast == '\'')
                    {
                        if( pSym == &cSymbol[ MAXSTRLEN ] )
                        {
                            SetError( FormulaError::StringOverflow);
                            eState = ssStop;
                        }
                        else
                            *pSym++ = c;
                    }
                    else
                    {
                        --pSrc;
                        eState = ssStop;
                    }
                }
                break;
            case ssGetReference:
                if( pSym == &cSymbol[ MAXSTRLEN ] )
                {
                    SetError( FormulaError::StringOverflow);
                    eState = ssSkipReference;
                }
                [[fallthrough]];
            case ssSkipReference:
                // ODF reference: ['External'#$'Sheet'.A1:.B2] with dots being
                // mandatory also if no sheet name. 'External'# is optional,
                // sheet name is optional, quotes around sheet name are
                // optional if no quote contained. [#REF!] is valid.
                // 2nd usage: ['Sheet'.$$'DefinedName']
                // 3rd usage: ['External'#$$'DefinedName']
                // 4th usage: ['External'#$'Sheet'.$$'DefinedName']
                // Also for all these names quotes are optional if no quote
                // contained.
                {
 
                    // nRefInName: 0 := not in sheet name yet. 'External'
                    // is parsed as if it was a sheet name and nRefInName
                    // is reset when # is encountered immediately after closing
                    // quote. Same with 'DefinedName', nRefInName is cleared
                    // when : is encountered.
 
                    // Encountered leading $ before sheet name.
                    constexpr int kDollar    = (1 << 1);
                    // Encountered ' opening quote, which may be after $ or
                    // not.
                    constexpr int kOpen      = (1 << 2);
                    // Somewhere in name.
                    constexpr int kName      = (1 << 3);
                    // Encountered ' in name, will be cleared if double or
                    // transformed to kClose if not, in which case kOpen is
                    // cleared.
                    constexpr int kQuote     = (1 << 4);
                    // Past ' closing quote.
                    constexpr int kClose     = (1 << 5);
                    // Encountered # file/sheet separator.
                    constexpr int kFileSep   = (1 << 6);
                    // Past . sheet name separator.
                    constexpr int kPast      = (1 << 7);
                    // Marked name $$ follows sheet name separator, detected
                    // while we're still on the separator. Will be cleared when
                    // entering the name.
                    constexpr int kMarkAhead = (1 << 8);
                    // In marked defined name.
                    constexpr int kDefName   = (1 << 9);
                    // Encountered # of #REF!
                    constexpr int kRefErr    = (1 << 10);
 
                    bool bAddToSymbol = true;
                    if ((nMask & ScCharFlags::OdfRBracket) && !(nRefInName & kOpen))
                    {
                        OSL_ENSURE( nRefInName & (kPast | kDefName | kRefErr),
                                "ScCompiler::NextSymbol: reference: "
                                "closing bracket ']' without prior sheet name separator '.' violates ODF spec");
                        // eaten, not added to pSym
                        bAddToSymbol = false;
                        eState = ssStop;
                    }
                    else if (cSheetSep == c && nRefInName == 0)
                    {
                        // eat it, no sheet name [.A1]
                        bAddToSymbol = false;
                        nRefInName |= kPast;
                        if ('$' == pSrc[0] && '$' == pSrc[1])
                            nRefInName |= kMarkAhead;
                    }
                    else if (!(nRefInName & kPast) || (nRefInName & (kMarkAhead | kDefName)))
                    {
                        // Not in col/row yet.
 
                        if (SC_COMPILER_FILE_TAB_SEP == c && (nRefInName & kFileSep))
                            nRefInName = 0;
                        else if ('$' == c && '$' == pSrc[0] && !(nRefInName & kOpen))
                        {
                            nRefInName &= ~kMarkAhead;
                            if (!(nRefInName & kDefName))
                            {
                                // eaten, not added to pSym (2 chars)
                                bAddToSymbol = false;
                                ++pSrc;
                                nRefInName &= kPast;
                                nRefInName |= kDefName;
                            }
                            else
                            {
                                // ScAddress::Parse() will recognize this as
                                // invalid later.
                                if (eState != ssSkipReference)
                                {
                                    *pSym++ = c;
 
                                    if( pSym == &cSymbol[ MAXSTRLEN ] )
                                    {
                                        SetError( FormulaError::StringOverflow);
                                        eState = ssStop;
                                    }
                                    else
                                        *pSym++ = *pSrc++;
                                }
                                bAddToSymbol = false;
                            }
                        }
                        else if (cSheetPrefix == c && nRefInName == 0)
                            nRefInName |= kDollar;
                        else if ('\'' == c)
                        {
                            // TODO: The conventions' parseExternalName()
                            // should handle quoted names, but as long as they
                            // don't remove non-embedded quotes here.
                            if (!(nRefInName & kName))
                            {
                                nRefInName |= (kOpen | kName);
                                bAddToSymbol = !(nRefInName & kDefName);
                            }
                            else if (!(nRefInName & kOpen))
                            {
                                OSL_FAIL("ScCompiler::NextSymbol: reference: "
                                        "a ''' without the name being enclosed in '...' violates ODF spec");
                            }
                            else if (nRefInName & kQuote)
                            {
                                // escaped embedded quote
                                nRefInName &= ~kQuote;
                            }
                            else
                            {
                                switch (pSrc[0])
                                {
                                    case '\'':
                                        // escapes embedded quote
                                        nRefInName |= kQuote;
                                        break;
                                    case SC_COMPILER_FILE_TAB_SEP:
                                        // sheet name should follow
                                        nRefInName |= kFileSep;
                                        [[fallthrough]];
                                    default:
                                        // quote not followed by quote => close
                                        nRefInName |= kClose;
                                        nRefInName &= ~kOpen;
                                }
                                bAddToSymbol = !(nRefInName & kDefName);
                            }
                        }
                        else if ('#' == c && nRefInName == 0)
                            nRefInName |= kRefErr;
                        else if (cSheetSep == c && !(nRefInName & kOpen))
                        {
                            // unquoted sheet name separator
                            nRefInName |= kPast;
                            if ('$' == pSrc[0] && '$' == pSrc[1])
                                nRefInName |= kMarkAhead;
                        }
                        else if (':' == c && !(nRefInName & kOpen))
                        {
                            OSL_FAIL("ScCompiler::NextSymbol: reference: "
                                    "range operator ':' without prior sheet name separator '.' violates ODF spec");
                            nRefInName = 0;
                            ++mnPredetectedReference;
                        }
                        else if (!(nRefInName & kName))
                        {
                            // start unquoted name
                            nRefInName |= kName;
                        }
                    }
                    else if (':' == c)
                    {
                        // range operator
                        nRefInName = 0;
                        ++mnPredetectedReference;
                    }
                    if (bAddToSymbol && eState != ssSkipReference)
                        *pSym++ = c;    // everything is part of reference
                }
                break;
            case ssStop:
                ;   // nothing, prevent warning
                break;
        }
        cLast = c;
        c = *pSrc;
    }
 
    if (aSpace.nCount && aSpace.cChar)
        vSpaces.emplace_back(aSpace);
 
    if ( bi18n )
    {
        const sal_Int32 nOldSrcPos = nSrcPos;
        for (const auto& r : vSpaces)
            nSrcPos += r.nCount;
        // If group separator is not a possible operator and not one of any
        // separators then it may be parsed away in numbers. This is
        // specifically the case with NO-BREAK SPACE, which actually triggers
        // the bi18n case (which we don't want to include as yet another
        // special case above as it is rare enough and doesn't generally occur
        // in formulas).
        const sal_Unicode cGroupSep = ScGlobal::getLocaleData().getNumThousandSep()[0];
        const bool bGroupSeparator = (128 <= cGroupSep && cGroupSep != cSep &&
                cGroupSep != cArrayColSep && cGroupSep != cArrayRowSep &&
                cGroupSep != cDecSep && cGroupSep != cDecSepAlt &&
                cGroupSep != cSheetPrefix && cGroupSep != cSheetSep);
        // If a numeric context triggered bi18n then use the default locale's
        // CharClass, this may accept group separator as well.
        const CharClass* pMyCharClass = (ScGlobal::getCharClass().isDigit( OUString(pStart[nSrcPos]), 0) ?
                &ScGlobal::getCharClass() : pCharClass);
        OUStringBuffer aSymbol;
        mnRangeOpPosInSymbol = -1;
        FormulaError nErr = FormulaError::NONE;
        do
        {
            bi18n = false;
            // special case  (e.g. $'sheetname' in OOO A1)
            if ( pStart[nSrcPos] == cSheetPrefix && pStart[nSrcPos+1] == '\'' )
                aSymbol.append(pStart[nSrcPos++]);
 
            ParseResult aRes = pConv->parseAnyToken( aFormula, nSrcPos, pMyCharClass, bGroupSeparator);
 
            if ( !aRes.TokenType )
            {
                nErr = FormulaError::IllegalChar;
                SetError( nErr );      // parsed chars as string
            }
            if ( aRes.EndPos <= nSrcPos )
            {
                // Could not parse anything meaningful.
                assert(!aRes.TokenType);
                nErr = FormulaError::IllegalChar;
                SetError( nErr );
                // Caller has to act on an empty symbol for
                // nSrcPos < aFormula.getLength()
                nSrcPos = nOldSrcPos;
                aSymbol.setLength(0);
            }
            else
            {
                // When having parsed a second reference part, ensure that the
                // i18n parser did not mistakenly parse a number that included
                // a separator which happened to be meant as a parameter
                // separator instead.
                if (mnRangeOpPosInSymbol >= 0 && (aRes.TokenType & KParseType::ASC_NUMBER))
                {
                    for (sal_Int32 i = nSrcPos; i < aRes.EndPos; ++i)
                    {
                        if (pStart[i] == cSep)
                            aRes.EndPos = i;    // also ends for
                    }
                }
                aSymbol.append( pStart + nSrcPos, aRes.EndPos - nSrcPos);
                nSrcPos = aRes.EndPos;
                c = pStart[nSrcPos];
                if ( aRes.TokenType & KParseType::SINGLE_QUOTE_NAME )
                {   // special cases (e.g. 'sheetname'. or 'filename'# in OOO A1)
                    bi18n = (c == cSheetSep || c == SC_COMPILER_FILE_TAB_SEP);
                }
                // One range operator restarts parsing for second reference.
                if (c == ':' && mnRangeOpPosInSymbol < 0)
                {
                    mnRangeOpPosInSymbol = aSymbol.getLength();
                    bi18n = true;
                }
                if ( bi18n )
                    aSymbol.append(pStart[nSrcPos++]);
            }
        } while ( bi18n && nErr == FormulaError::NONE );
        sal_Int32 nLen = aSymbol.getLength();
        if ( nLen > MAXSTRLEN )
        {
            SetError( FormulaError::StringOverflow );
            nLen = MAXSTRLEN;
        }
        if (mnRangeOpPosInSymbol >= nLen)
            mnRangeOpPosInSymbol = -1;
        lcl_UnicodeStrNCpy( cSymbol, aSymbol.getStr(), nLen );
        pSym = &cSymbol[nLen];
    }
    else
    {
        nSrcPos = pSrc - pStart;
        *pSym = 0;
    }
    if (mnRangeOpPosInSymbol >= 0 && mnRangeOpPosInSymbol == (pSym-1) - &cSymbol[0])
    {
        // This is a trailing range operator, which is nonsense. Will be caught
        // in next round.
        mnRangeOpPosInSymbol = -1;
        *--pSym = 0;
        --nSrcPos;
    }
    if ( bAutoCorrect )
        aCorrectedSymbol = OUString(cSymbol, pSym - cSymbol);
    if (bAutoIntersection && vSpaces[nAutoIntersectionSpacesPos].nCount > 1)
        --vSpaces[nAutoIntersectionSpacesPos].nCount;   // replace '!!' with only one space
    return vSpaces;
}
 
// Convert symbol to token
 
bool ScCompiler::ParseOpCode( const OUString& rName, bool bInArray )
{
    OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
    bool bFound = (iLook != mxSymbols->getHashMap().end());
    if (bFound)
    {
        OpCode eOp = iLook->second;
        if (bInArray)
        {
            if (rName == mxSymbols->getSymbol(ocArrayColSep))
                eOp = ocArrayColSep;
            else if (rName == mxSymbols->getSymbol(ocArrayRowSep))
                eOp = ocArrayRowSep;
        }
        else if (eOp == ocArrayColSep || eOp == ocArrayRowSep)
        {
            if (rName == mxSymbols->getSymbol(ocSep))
                eOp = ocSep;
            else if (rName == ";")
            {
                switch (FormulaGrammar::extractFormulaLanguage( meGrammar))
                {
                    // Only for languages/grammars that actually use ';'
                    // parameter separator.
                    case css::sheet::FormulaLanguage::NATIVE:
                    case css::sheet::FormulaLanguage::ENGLISH:
                    case css::sheet::FormulaLanguage::ODFF:
                    case css::sheet::FormulaLanguage::ODF_11:
                        eOp = ocSep;
                }
            }
        }
        else if (eOp == ocCeil && mxSymbols->isOOXML())
        {
            // Ensure that _xlfn.CEILING.MATH maps to ocCeil_Math. ocCeil is
            // unassigned for import.
            eOp = ocCeil_Math;
        }
        else if (eOp == ocFloor && mxSymbols->isOOXML())
        {
            // Ensure that _xlfn.FLOOR.MATH maps to ocFloor_Math. ocFloor is
            // unassigned for import.
            eOp = ocFloor_Math;
        }
        maRawToken.SetOpCode(eOp);
    }
    else if (mxSymbols->isODFF())
    {
        // ODFF names that are not written in the current mapping but to be
        // recognized. New names will be written in a future release, then
        // exchange (!) with the names in
        // formula/source/core/resource/core_resource.src to be able to still
        // read the old names as well.
        struct FunctionName
        {
            const char* pName;
            OpCode          eOp;
        };
        static const FunctionName aOdffAliases[] = {
            // Renamed old names, still accept them:
            { "B",              ocB },              // B -> BINOM.DIST.RANGE
            { "TDIST",          ocTDist },          // TDIST -> LEGACY.TDIST
            { "ORG.OPENOFFICE.EASTERSUNDAY",   ocEasterSunday },   // ORG.OPENOFFICE.EASTERSUNDAY -> EASTERSUNDAY
            { "ZGZ",            ocRRI },            // ZGZ -> RRI
            { "COLOR",          ocColor },          // COLOR -> ORG.LIBREOFFICE.COLOR
            { "GOALSEEK",       ocBackSolver },     // GOALSEEK -> ORG.OPENOFFICE.GOALSEEK
            { "COM.MICROSOFT.F.DIST", ocFDist_LT }, // fdo#40835, -> FDIST -> COM.MICROSOFT.F.DIST
            { "COM.MICROSOFT.F.INV",  ocFInv_LT }   // tdf#94214, COM.MICROSOFT.F.INV -> FINV (ODF)
            // Renamed new names, prepare to read future names:
            //{ "ORG.OPENOFFICE.XXX", ocXXX }         // XXX -> ORG.OPENOFFICE.XXX
        };
        for (const FunctionName& rOdffAlias : aOdffAliases)
        {
            if (rName.equalsIgnoreAsciiCaseAscii( rOdffAlias.pName))
            {
                maRawToken.SetOpCode( rOdffAlias.eOp);
                bFound = true;
                break;  // for
            }
        }
    }
    else if (mxSymbols->isOOXML())
    {
        // OOXML names that are not written in the current mapping but to be
        // recognized as old versions wrote them.
        struct FunctionName
        {
            const char* pName;
            OpCode      eOp;
        };
        static const FunctionName aOoxmlAliases[] = {
            { "EFFECTIVE",      ocEffect },         // EFFECTIVE -> EFFECT
            { "ERRORTYPE",      ocErrorType },      // ERRORTYPE -> _xlfn.ORG.OPENOFFICE.ERRORTYPE
            { "MULTIRANGE",     ocMultiArea },      // MULTIRANGE -> _xlfn.ORG.OPENOFFICE.MULTIRANGE
            { "GOALSEEK",       ocBackSolver },     // GOALSEEK -> _xlfn.ORG.OPENOFFICE.GOALSEEK
            { "EASTERSUNDAY",   ocEasterSunday },   // EASTERSUNDAY -> _xlfn.ORG.OPENOFFICE.EASTERSUNDAY
            { "CURRENT",        ocCurrent },        // CURRENT -> _xlfn.ORG.OPENOFFICE.CURRENT
            { "STYLE",          ocStyle }           // STYLE -> _xlfn.ORG.OPENOFFICE.STYLE
        };
        for (const FunctionName& rOoxmlAlias : aOoxmlAliases)
        {
            if (rName.equalsIgnoreAsciiCaseAscii( rOoxmlAlias.pName))
            {
                maRawToken.SetOpCode( rOoxmlAlias.eOp);
                bFound = true;
                break;  // for
            }
        }
    }
    else if (mxSymbols->isPODF())
    {
        // PODF names are ODF 1.0/1.1 and also used in API XFunctionAccess.
        // We can't rename them in
        // formula/source/core/resource/core_resource.src but can add
        // additional names to be recognized here so they match the UI names if
        // those are renamed.
        struct FunctionName
        {
            const char* pName;
            OpCode      eOp;
        };
        static const FunctionName aPodfAliases[] = {
            { "EFFECT",  ocEffect }      // EFFECTIVE -> EFFECT
        };
        for (const FunctionName& rPodfAlias : aPodfAliases)
        {
            if (rName.equalsIgnoreAsciiCaseAscii( rPodfAlias.pName))
            {
                maRawToken.SetOpCode( rPodfAlias.eOp);
                bFound = true;
                break;  // for
            }
        }
    }
 
    if (!bFound)
    {
        OUString aIntName;
        if (mxSymbols->hasExternals())
        {
            // If symbols are set by filters get mapping to exact name.
            ExternalHashMap::const_iterator iExt(
                    mxSymbols->getExternalHashMap().find( rName));
            if (iExt != mxSymbols->getExternalHashMap().end())
            {
                if (ScGlobal::GetAddInCollection()->GetFuncData( (*iExt).second))
                    aIntName = (*iExt).second;
            }
        }
        else
        {
            // Old (deprecated) addins first for legacy.
            if (ScGlobal::GetLegacyFuncCollection()->findByName(OUString(cSymbol)))
            {
                aIntName = cSymbol;
            }
            else
                // bLocalFirst=false for (English) upper full original name
                // (service.function)
                aIntName = ScGlobal::GetAddInCollection()->FindFunction(
                        rName, !mxSymbols->isEnglish());
        }
        if (!aIntName.isEmpty())
        {
            maRawToken.SetExternal( aIntName );     // international name
            bFound = true;
        }
    }
    if (!bFound)
        return false;
    OpCode eOp = maRawToken.GetOpCode();
    if (eOp == ocSub || eOp == ocNegSub)
    {
        bool bShouldBeNegSub =
            (eLastOp == ocOpen || eLastOp == ocSep || eLastOp == ocNegSub ||
             (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_BIN_OP) ||
             eLastOp == ocArrayOpen ||
             eLastOp == ocArrayColSep || eLastOp == ocArrayRowSep);
        if (bShouldBeNegSub && eOp == ocSub)
            maRawToken.NewOpCode( ocNegSub );
            //TODO: if ocNegSub had ForceArray we'd have to set it here
        else if (!bShouldBeNegSub && eOp == ocNegSub)
            maRawToken.NewOpCode( ocSub );
    }
    return bFound;
}
 
bool ScCompiler::ParseOpCode2( std::u16string_view rName )
{
    for (sal_uInt16 i = ocInternalBegin; i <= ocInternalEnd; i++)
    {
        if (o3tl::equalsAscii(rName, pInternal[i - ocInternalBegin]))
        {
            maRawToken.SetOpCode(static_cast<OpCode>(i));
            return true;
        }
    }
 
    return false;
}
 
static bool lcl_ParenthesisFollows( const sal_Unicode* p )
{
    while (*p == ' ')
        p++;
    return *p == '(';
}
 
bool ScCompiler::ParseValue( const OUString& rSym )
{
    const sal_Int32 nFormulaLanguage = FormulaGrammar::extractFormulaLanguage( GetGrammar());
    if (nFormulaLanguage == css::sheet::FormulaLanguage::ODFF || nFormulaLanguage == css::sheet::FormulaLanguage::OOXML)
    {
        // Speedup things for ODFF, only well-formed numbers, not locale
        // dependent nor user input.
        rtl_math_ConversionStatus eStatus;
        sal_Int32 nParseEnd;
        double fVal = rtl::math::stringToDouble( rSym, '.', 0, &eStatus, &nParseEnd);
        if (nParseEnd != rSym.getLength())
        {
            // Not (only) a number.
 
            if (nParseEnd > 0)
                return false;   // partially a number => no such thing
 
            if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
                return false;   // some function name, not a constant
 
            // Could be TRUE or FALSE constant.
            OpCode eOpFunc = ocNone;
            if (rSym.equalsIgnoreAsciiCase("TRUE"))
                eOpFunc = ocTrue;
            else if (rSym.equalsIgnoreAsciiCase("FALSE"))
                eOpFunc = ocFalse;
            if (eOpFunc != ocNone)
            {
                maRawToken.SetOpCode(eOpFunc);
                // add missing trailing parentheses
                maPendingOpCodes.push(ocOpen);
                maPendingOpCodes.push(ocClose);
                return true;
            }
            return false;
        }
        if (eStatus == rtl_math_ConversionStatus_OutOfRange)
        {
            // rtl::math::stringToDouble() recognizes XMLSchema-2 "INF" and
            // "NaN" (case sensitive) that could be named expressions or DB
            // areas as well.
            // rSym is already upper so "NaN" is not possible here.
            if (!std::isfinite(fVal) && rSym == "INF")
            {
                SCTAB nSheet = -1;
                if (GetRangeData( nSheet, rSym))
                    return false;
                if (rDoc.GetDBCollection()->getNamedDBs().findByUpperName(rSym))
                    return false;
            }
            /* TODO: is there a specific reason why we don't accept an infinity
             * value that would raise an error in the interpreter, instead of
             * setting the hard error at the token array already? */
            SetError( FormulaError::IllegalArgument );
        }
        maRawToken.SetDouble( fVal );
        return true;
    }
 
    double fVal;
    sal_uInt32 nIndex = mxSymbols->isEnglishLocale() ? mrInterpreterContext.NFGetStandardIndex(LANGUAGE_ENGLISH_US) : 0;
 
    if (!mrInterpreterContext.NFIsNumberFormat(rSym, nIndex, fVal))
        return false;
 
    SvNumFormatType nType = mrInterpreterContext.NFGetType(nIndex);
 
    // Don't accept 3:3 as time, it is a reference to entire row 3 instead.
    // Dates should never be entered directly and automatically converted
    // to serial, because the serial would be wrong if null-date changed.
    // Usually it wouldn't be accepted anyway because the date separator
    // clashed with other separators or operators.
    if (nType & (SvNumFormatType::TIME | SvNumFormatType::DATE))
        return false;
 
    if (nType == SvNumFormatType::LOGICAL)
    {
        if (lcl_ParenthesisFollows( aFormula.getStr() + nSrcPos))
            return false;   // Boolean function instead.
    }
 
    if( nType == SvNumFormatType::TEXT )
        // HACK: number too big!
        SetError( FormulaError::IllegalArgument );
    maRawToken.SetDouble( fVal );
    return true;
}
 
bool ScCompiler::ParseString()
{
    if ( cSymbol[0] != '"' )
        return false;
    const sal_Unicode* p = cSymbol+1;
    while ( *p )
        p++;
    sal_Int32 nLen = sal::static_int_cast<sal_Int32>( p - cSymbol - 1 );
    if (!nLen || cSymbol[nLen] != '"')
        return false;
    svl::SharedString aSS = rDoc.GetSharedStringPool().intern(OUString(cSymbol+1, nLen-1));
    maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
    return true;
}
 
bool ScCompiler::ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef )
{
    switch (mnPredetectedReference)
    {
        case 1:
            return ParseSingleReference( rName, pErrRef);
        case 2:
            return ParseDoubleReference( rName, pErrRef);
        default:
            return false;
    }
}
 
bool ScCompiler::ParsePredetectedReference( const OUString& rName )
{
    // Speedup documents with lots of broken references, e.g. sheet deleted.
    // It could also be a broken invalidated reference that contains #REF!
    // (but is not equal to), which we wrote prior to ODFF and also to ODFF
    // between 2013 and 2016 until 5.1.4
    static constexpr OUString aErrRef(u"#REF!"_ustr);    // not localized in ODFF
    sal_Int32 nPos = rName.indexOf( aErrRef);
    if (nPos != -1)
    {
        /* TODO: this may be enhanced by reusing scan information from
         * NextSymbol(), the positions of quotes and special characters found
         * there for $'sheet'.A1:... could be stored in a vector. We don't
         * fully rescan here whether found positions are within single quotes
         * for performance reasons. This code does not check for possible
         * occurrences of insane "valid" sheet names like
         * 'haha.#REF!1fooledyou' and will generate an error on such. */
        if (nPos == 0)
        {
            // Per ODFF the correct string for a reference error is just #REF!,
            // so pass it on.
            if (rName.getLength() == 5)
                return ParseErrorConstant( rName);
            // #REF!.AB42 or #REF!42 or #REF!#REF!
            return ParsePredetectedErrRefReference( rName, &aErrRef);
        }
        sal_Unicode c = rName[nPos-1];      // before #REF!
        if ('$' == c)
        {
            if (nPos == 1)
            {
                // $#REF!.AB42 or $#REF!42 or $#REF!#REF!
                return ParsePredetectedErrRefReference( rName, &aErrRef);
            }
            c = rName[nPos-2];              // before $#REF!
        }
        sal_Unicode c2 = nPos+5 < rName.getLength() ? rName[nPos+5] : 0;     // after #REF!
        switch (c)
        {
            case '.':
                if ('$' == c2 || '#' == c2 || ('0' <= c2 && c2 <= '9'))
                {
                    // sheet.#REF!42 or sheet.#REF!#REF!
                    return ParsePredetectedErrRefReference( rName, &aErrRef);
                }
                break;
            case ':':
                if (mnPredetectedReference > 1 &&
                        ('.' == c2 || '$' == c2 || '#' == c2 ||
                         ('0' <= c2 && c2 <= '9')))
                {
                    // :#REF!.AB42 or :#REF!42 or :#REF!#REF!
                    return ParsePredetectedErrRefReference( rName, &aErrRef);
                }
                break;
            default:
                if (rtl::isAsciiAlpha(c) &&
                        ((mnPredetectedReference > 1 && ':' == c2) || 0 == c2))
                {
                    // AB#REF!: or AB#REF!
                    return ParsePredetectedErrRefReference( rName, &aErrRef);
                }
        }
    }
    switch (mnPredetectedReference)
    {
        case 1:
            return ParseSingleReference( rName);
        case 2:
            return ParseDoubleReference( rName);
    }
    return false;
}
 
bool ScCompiler::ParseDoubleReference( const OUString& rName, const OUString* pErrRef )
{
    ScRange aRange( aPos, aPos );
    const ScAddress::Details aDetails( pConv->meConv, aPos );
    ScAddress::ExternalInfo aExtInfo;
    ScRefFlags nFlags = aRange.Parse( rName, rDoc, aDetails, &aExtInfo, &maExternalLinks, pErrRef );
    if( nFlags & ScRefFlags::VALID )
    {
        ScComplexRefData aRef;
        aRef.InitRange( aRange );
        aRef.Ref1.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
        aRef.Ref1.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
        aRef.Ref1.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
        if ( !(nFlags & ScRefFlags::TAB_VALID) )
            aRef.Ref1.SetTabDeleted( true );        // #REF!
        aRef.Ref1.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
        aRef.Ref2.SetColRel( (nFlags & ScRefFlags::COL2_ABS) == ScRefFlags::ZERO );
        aRef.Ref2.SetRowRel( (nFlags & ScRefFlags::ROW2_ABS) == ScRefFlags::ZERO );
        aRef.Ref2.SetTabRel( (nFlags & ScRefFlags::TAB2_ABS) == ScRefFlags::ZERO );
        if ( !(nFlags & ScRefFlags::TAB2_VALID) )
            aRef.Ref2.SetTabDeleted( true );        // #REF!
        aRef.Ref2.SetFlag3D( ( nFlags & ScRefFlags::TAB2_3D ) != ScRefFlags::ZERO );
        aRef.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
        if (aExtInfo.mbExternal)
        {
            ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
            const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
            maRawToken.SetExternalDoubleRef(
                aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
            maExternalFiles.push_back(aExtInfo.mnFileId);
        }
        else
        {
            maRawToken.SetDoubleReference(aRef);
        }
    }
 
    return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
}
 
bool ScCompiler::ParseSingleReference( const OUString& rName, const OUString* pErrRef )
{
    mnCurrentSheetEndPos = 0;
    mnCurrentSheetTab = -1;
    ScAddress aAddr( aPos );
    const ScAddress::Details aDetails( pConv->meConv, aPos );
    ScAddress::ExternalInfo aExtInfo;
    ScRefFlags nFlags = aAddr.Parse( rName, rDoc, aDetails,
            &aExtInfo, &maExternalLinks, &mnCurrentSheetEndPos, pErrRef);
    // Something must be valid in order to recognize Sheet1.blah or blah.a1
    // as a (wrong) reference.
    if( nFlags & ( ScRefFlags::COL_VALID|ScRefFlags::ROW_VALID|ScRefFlags::TAB_VALID ) )
    {
        // Valid given tab and invalid col or row may indicate a sheet-local
        // named expression, bail out early and don't create a reference token.
        if (!(nFlags & ScRefFlags::VALID) && mnCurrentSheetEndPos > 0 &&
                (nFlags & ScRefFlags::TAB_VALID) && (nFlags & ScRefFlags::TAB_3D))
        {
            if (aExtInfo.mbExternal)
            {
                // External names are handled separately.
                mnCurrentSheetEndPos = 0;
                mnCurrentSheetTab = -1;
            }
            else
            {
                mnCurrentSheetTab = aAddr.Tab();
            }
            return false;
        }
 
        if( HasPossibleNamedRangeConflict( aAddr.Tab()))
        {
            // A named range named e.g. 'num1' is valid with 1k columns, but would become a reference
            // when the document is opened later with 16k columns. Resolve the conflict by not
            // considering it a reference.
            OUString aUpper( ScGlobal::getCharClass().uppercase( rName ));
            mnCurrentSheetTab = aAddr.Tab(); // temporarily set for ParseNamedRange()
            if(ParseNamedRange( aUpper, true )) // only check
                return false;
            mnCurrentSheetTab = -1;
        }
 
        ScSingleRefData aRef;
        aRef.InitAddress( aAddr );
        aRef.SetColRel( (nFlags & ScRefFlags::COL_ABS) == ScRefFlags::ZERO );
        aRef.SetRowRel( (nFlags & ScRefFlags::ROW_ABS) == ScRefFlags::ZERO );
        aRef.SetTabRel( (nFlags & ScRefFlags::TAB_ABS) == ScRefFlags::ZERO );
        aRef.SetFlag3D( ( nFlags & ScRefFlags::TAB_3D ) != ScRefFlags::ZERO );
        // the reference is really invalid
        if( !( nFlags & ScRefFlags::VALID ) )
        {
            if( !( nFlags & ScRefFlags::COL_VALID ) )
                aRef.SetColDeleted(true);
            if( !( nFlags & ScRefFlags::ROW_VALID ) )
                aRef.SetRowDeleted(true);
            if( !( nFlags & ScRefFlags::TAB_VALID ) )
                aRef.SetTabDeleted(true);
            nFlags |= ScRefFlags::VALID;
        }
        aRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
 
        if (aExtInfo.mbExternal)
        {
            ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
            const OUString* pRealTab = pRefMgr->getRealTableName(aExtInfo.mnFileId, aExtInfo.maTabName);
            maRawToken.SetExternalSingleRef(
                aExtInfo.mnFileId, pRealTab ? *pRealTab : aExtInfo.maTabName, aRef);
            maExternalFiles.push_back(aExtInfo.mnFileId);
        }
        else
            maRawToken.SetSingleReference(aRef);
    }
 
    return ( nFlags & ScRefFlags::VALID ) != ScRefFlags::ZERO;
}
 
bool ScCompiler::ParseReference( const OUString& rName, const OUString* pErrRef )
{
    // Has to be called before ParseValue
 
    // A later ParseNamedRange() relies on these, being set in ParseSingleReference()
    // if so, reset in all cases.
    mnCurrentSheetEndPos = 0;
    mnCurrentSheetTab = -1;
 
    sal_Unicode ch1 = rName[0];
    sal_Unicode cDecSep = ( mxSymbols->isEnglishLocale() ? '.' : ScGlobal::getLocaleData().getNumDecimalSep()[0] );
    if ( ch1 == cDecSep )
        return false;
    // Code further down checks only if cDecSep=='.' so simply obtaining the
    // alternative decimal separator if it's not is sufficient.
    if (cDecSep != '.')
    {
        cDecSep = ScGlobal::getLocaleData().getNumDecimalSepAlt().toChar();
        if ( ch1 == cDecSep )
            return false;
    }
    // Who was that imbecile introducing '.' as the sheet name separator!?!
    if ( rtl::isAsciiDigit( ch1 ) && pConv->getSpecialSymbol( Convention::SHEET_SEPARATOR) == '.' )
    {
        // Numerical sheet name is valid.
        // But English 1.E2 or 1.E+2 is value 100, 1.E-2 is 0.01
        // Don't create a #REF! of values. But also do not bail out on
        // something like 3:3, meaning entire row 3.
        do
        {
            const sal_Int32 nPos = ScGlobal::FindUnquoted( rName, '.');
            if ( nPos == -1 )
            {
                if (ScGlobal::FindUnquoted( rName, ':') != -1)
                    break;      // may be 3:3, continue as usual
                return false;
            }
            sal_Unicode const * const pTabSep = rName.getStr() + nPos;
            sal_Unicode ch2 = pTabSep[1];   // maybe a column identifier
            if ( !(ch2 == '$' || rtl::isAsciiAlpha( ch2 )) )
                return false;
            if ( cDecSep == '.' && (ch2 == 'E' || ch2 == 'e')   // E + - digit
                    && (GetCharTableFlags( pTabSep[2], pTabSep[1] ) & ScCharFlags::ValueExp) )
            {
                // If it is an 1.E2 expression check if "1" is an existent sheet
                // name. If so, a desired value 1.E2 would have to be entered as
                // 1E2 or 1.0E2 or 1.E+2, sorry. Another possibility would be to
                // require numerical sheet names always being entered quoted, which
                // is not desirable (too many 1999, 2000, 2001 sheets in use).
                // Furthermore, XML files created with versions prior to SRC640e
                // wouldn't contain the quotes added by MakeTabStr()/CheckTabQuotes()
                // and would produce wrong formulas if the conditions here are met.
                // If you can live with these restrictions you may remove the
                // check and return an unconditional FALSE.
                OUString aTabName( rName.copy( 0, nPos ) );
                SCTAB nTab;
                if ( !rDoc.GetTable( aTabName, nTab ) )
                    return false;
                // If sheet "1" exists and the expression is 1.E+2 continue as
                // usual, the ScRange/ScAddress parser will take care of it.
            }
        } while(false);
    }
 
    if (ParseSingleReference( rName, pErrRef))
        return true;
 
    // Though the range operator is handled explicitly, when encountering
    // something like Sheet1.A:A we will have to treat it as one entity if it
    // doesn't pass as single cell reference.
    if (mnRangeOpPosInSymbol > 0)   // ":foo" would be nonsense
    {
        if (ParseDoubleReference( rName, pErrRef))
            return true;
        // Now try with a symbol up to the range operator, rewind source
        // position.
        assert(mnRangeOpPosInSymbol < MAXSTRLEN);   // We should have caught the maldoers.
        if (mnRangeOpPosInSymbol >= MAXSTRLEN)      // TODO: this check and return
            return false;                           // can be removed when sure.
        sal_Int32 nLen = mnRangeOpPosInSymbol;
        while (cSymbol[++nLen])
            ;
        cSymbol[mnRangeOpPosInSymbol] = 0;
        nSrcPos -= (nLen - mnRangeOpPosInSymbol);
        mnRangeOpPosInSymbol = -1;
        mbRewind = true;
        return true;    // end all checks
    }
    else
    {
        switch (pConv->meConv)
        {
            case FormulaGrammar::CONV_XL_A1:
            case FormulaGrammar::CONV_XL_OOX:
                // Special treatment for the 'E:\[doc]Sheet1:Sheet3'!D5 Excel
                // sickness, mnRangeOpPosInSymbol did not catch the range
                // operator as it is within a quoted name.
                if (rName[0] != '\'')
                    return false;   // Document name has to be single quoted.
                [[fallthrough]];
            case FormulaGrammar::CONV_XL_R1C1:
                // C2 or C[1] are valid entire column references.
                if (ParseDoubleReference( rName, pErrRef))
                    return true;
                break;
            default:
                ;   // nothing
        }
    }
    return false;
}
 
bool ScCompiler::ParseMacro( const OUString& rName )
{
#if !HAVE_FEATURE_SCRIPTING
    (void) rName;
 
    return false;
#else
 
    // Calling SfxObjectShell::GetBasic() may result in all sort of things
    // including obtaining the model and deep down in
    // SfxBaseModel::getDocumentStorage() acquiring the SolarMutex, which when
    // formulas are compiled from a threaded import may result in a deadlock.
    // Check first if we actually could acquire it and if not bail out.
    /* FIXME: yes, but how ... */
    vcl::SolarMutexTryAndBuyGuard g;
    if (!g.isAcquired())
    {
        SAL_WARN( "sc.core", "ScCompiler::ParseMacro - SolarMutex would deadlock, not obtaining Basic");
        return false;   // bad luck
    }
 
    OUString aName( rName);
    StarBASIC* pObj = nullptr;
    ScDocShell* pDocSh = rDoc.GetDocumentShell();
 
    try
    {
        if( pDocSh )//XXX
            pObj = pDocSh->GetBasic();
        else
            pObj = SfxApplication::GetBasic();
    }
    catch (...)
    {
        return false;
    }
 
    if (!pObj)
        return false;
 
    // ODFF recommends to store user-defined functions prefixed with "USER.",
    // use only unprefixed name if encountered. BASIC doesn't allow '.' in a
    // function name so a function "USER.FOO" could not exist, and macro check
    // is assigned the lowest priority in function name check.
    if (FormulaGrammar::isODFF( GetGrammar()) && aName.startsWithIgnoreAsciiCase("USER."))
        aName = aName.copy(5);
 
    SbxMethod* pMeth = static_cast<SbxMethod*>(pObj->Find( aName, SbxClassType::Method ));
    if( !pMeth )
    {
        return false;
    }
    // It really should be a BASIC function!
    if( pMeth->GetType() == SbxVOID
     || ( pMeth->IsFixed() && pMeth->GetType() == SbxEMPTY )
     || dynamic_cast<const SbMethod*>( pMeth) ==  nullptr )
    {
        return false;
    }
    maRawToken.SetExternal( aName );
    maRawToken.eOp = ocMacro;
    return true;
#endif
}
 
const ScRangeData* ScCompiler::GetRangeData( SCTAB& rSheet, const OUString& rUpperName ) const
{
    // try local names first
    rSheet = aPos.Tab();
    const ScRangeName* pRangeName = rDoc.GetRangeName(rSheet);
    const ScRangeData* pData = nullptr;
    if (pRangeName)
        pData = pRangeName->findByUpperName(rUpperName);
    if (!pData)
    {
        pRangeName = rDoc.GetRangeName();
        if (pRangeName)
            pData = pRangeName->findByUpperName(rUpperName);
        if (pData)
            rSheet = -1;
    }
    return pData;
}
 
bool ScCompiler::HasPossibleNamedRangeConflict( SCTAB nTab ) const
{
    const ScRangeName* pRangeName = rDoc.GetRangeName();
    if (pRangeName && pRangeName->hasPossibleAddressConflict())
        return true;
    pRangeName = rDoc.GetRangeName(nTab);
    if (pRangeName && pRangeName->hasPossibleAddressConflict())
        return true;
    return false;
}
 
bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck )
{
    // ParseNamedRange is called only from NextNewToken, with an upper-case string
 
    SCTAB nSheet = -1;
    const ScRangeData* pData = GetRangeData( nSheet, rUpperName);
    if (pData)
    {
        if (!onlyCheck)
            maRawToken.SetName( nSheet, pData->GetIndex());
        return true;
    }
 
    // Sheet-local name with sheet specified.
    if (mnCurrentSheetEndPos > 0 && mnCurrentSheetTab >= 0)
    {
        OUString aName( rUpperName.copy( mnCurrentSheetEndPos));
        const ScRangeName* pRangeName = rDoc.GetRangeName( mnCurrentSheetTab);
        if (pRangeName)
        {
            pData = pRangeName->findByUpperName(aName);
            if (pData)
            {
                if (!onlyCheck)
                    maRawToken.SetName( mnCurrentSheetTab, pData->GetIndex());
                return true;
            }
        }
    }
 
    return false;
}
 
bool ScCompiler::ParseLambdaFuncName( const OUString& aOrg )
{
    if (m_aLambda.bInLambdaFunction && !aOrg.isEmpty())
    {
        OUString aName = aOrg;
        if (aOrg.startsWithIgnoreAsciiCase(u"_xlpm."))
            aName = aName.copy(6);
 
        if (m_aLambda.nParaPos % 2 == 1 && m_aLambda.nParaCount > m_aLambda.nParaPos)
            m_aLambda.aNameSet.insert(aName);
        else
        {
            // should already exist the name
            if (m_aLambda.aNameSet.find(aName) == m_aLambda.aNameSet.end())
                return false;
        }
        svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aName);
        maRawToken.SetStringName(aSS.getData(), aSS.getDataIgnoreCase());
        return true;
    }
    return false;
}
 
bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange )
{
    /* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc)
     * correctly parses external named references in OOo, as required per RFE
     * #i3740#, just that we can't store them in ODF yet. We will need an OASIS
     * spec first. Until then don't pretend to support external names that
     * wouldn't survive a save and reload cycle, return false instead. */
 
    rbInvalidExternalNameRange = false;
 
    if (!pConv)
        return false;
 
    OUString aFile, aName;
    if (!pConv->parseExternalName( rSymbol, aFile, aName, rDoc, &maExternalLinks))
        return false;
 
    if (aFile.getLength() > MAXSTRLEN || aName.getLength() > MAXSTRLEN)
        return false;
 
    ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
    OUString aTmp = aFile;
    pRefMgr->convertToAbsName(aTmp);
    aFile = aTmp;
    sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFile);
    if (!pRefMgr->isValidRangeName(nFileId, aName))
    {
        rbInvalidExternalNameRange = true;
        // range name doesn't exist in the source document.
        return false;
    }
 
    const OUString* pRealName = pRefMgr->getRealRangeName(nFileId, aName);
    maRawToken.SetExternalName(nFileId, pRealName ? *pRealName : aTmp);
    maExternalFiles.push_back(nFileId);
    return true;
}
 
bool ScCompiler::ParseDBRange( const OUString& rName )
{
    ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs();
    const ScDBData* p = rDBs.findByUpperName(rName);
    if (!p)
        return false;
 
    maRawToken.SetName( -1, p->GetIndex()); // DB range is always global.
    maRawToken.eOp = ocDBArea;
    return true;
}
 
bool ScCompiler::ParseColRowName( const OUString& rName )
{
    bool bInList = false;
    bool bFound = false;
    ScSingleRefData aRef;
    OUString aName( rName );
    DeQuote( aName );
    SCTAB nThisTab = aPos.Tab();
    for ( short jThisTab = 1; jThisTab >= 0 && !bInList; jThisTab-- )
    {   // first check ranges on this sheet, in case of duplicated names
        for ( short jRow=0; jRow<2 && !bInList; jRow++ )
        {
            ScRangePairList* pRL;
            if ( !jRow )
                pRL = rDoc.GetColNameRanges();
            else
                pRL = rDoc.GetRowNameRanges();
            for ( size_t iPair = 0, nPairs = pRL->size(); iPair < nPairs && !bInList; ++iPair )
            {
                const ScRangePair & rR = (*pRL)[iPair];
                const ScRange& rNameRange = rR.GetRange(0);
                if ( jThisTab && (rNameRange.aStart.Tab() > nThisTab ||
                        nThisTab > rNameRange.aEnd.Tab()) )
                    continue;   // for
                ScCellIterator aIter( rDoc, rNameRange );
                for (bool bHas = aIter.first(); bHas && !bInList; bHas = aIter.next())
                {
                    // Don't crash if cell (via CompileNameFormula) encounters
                    // a formula cell without code and
                    // HasStringData/Interpret/Compile is executed and all that
                    // recursively...
                    // Furthermore, *this* cell won't be touched, since no RPN exists yet.
                    CellType eType = aIter.getType();
                    bool bOk = false;
                    if (eType == CELLTYPE_FORMULA)
                    {
                        ScFormulaCell* pFC = aIter.getFormulaCell();
                        bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
                    }
                    else
                        bOk = true;
 
                    if (bOk && aIter.hasString())
                    {
                        OUString aStr = aIter.getString();
                        if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
                        {
                            aRef.InitFlags();
                            if ( !jRow )
                                aRef.SetColRel( true );     // ColName
                            else
                                aRef.SetRowRel( true );     // RowName
                            aRef.SetAddress(rDoc.GetSheetLimits(), aIter.GetPos(), aPos);
                            bInList = bFound = true;
                        }
                    }
                }
            }
        }
    }
    if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
    {   // search in current sheet
        tools::Long nDistance = 0, nMax = 0;
        tools::Long nMyCol = static_cast<tools::Long>(aPos.Col());
        tools::Long nMyRow = static_cast<tools::Long>(aPos.Row());
        bool bTwo = false;
        ScAddress aOne( 0, 0, aPos.Tab() );
        ScAddress aTwo( rDoc.MaxCol(), rDoc.MaxRow(), aPos.Tab() );
 
        ScAutoNameCache* pNameCache = rDoc.GetAutoNameCache();
        if ( pNameCache )
        {
            //  use GetNameOccurrences to collect all positions of aName on the sheet
            //  (only once), similar to the outer part of the loop in the "else" branch.
 
            const ScAutoNameAddresses& rAddresses = pNameCache->GetNameOccurrences( aName, aPos.Tab() );
 
            //  Loop through the found positions, similar to the inner part of the loop in the "else" branch.
            //  The order of addresses in the vector is the same as from ScCellIterator.
 
            for ( const ScAddress& aAddress : rAddresses )
            {
                if ( bFound )
                {   // stop if everything else is further away
                    if ( nMax < static_cast<tools::Long>(aAddress.Col()) )
                        break;      // aIter
                }
                if ( aAddress != aPos )
                {
                    // same treatment as in isEqual case below
 
                    SCCOL nCol = aAddress.Col();
                    SCROW nRow = aAddress.Row();
                    tools::Long nC = nMyCol - nCol;
                    tools::Long nR = nMyRow - nRow;
                    if ( bFound )
                    {
                        tools::Long nD = nC * nC + nR * nR;
                        if ( nD < nDistance )
                        {
                            if ( nC < 0 || nR < 0 )
                            {   // right or below
                                bTwo = true;
                                aTwo.Set( nCol, nRow, aAddress.Tab() );
                                nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
                                nDistance = nD;
                            }
                            else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
                            {
                                // upper left, only if not further up than the
                                // current entry and nMyRow is below (CellIter
                                // runs column-wise)
                                bTwo = false;
                                aOne.Set( nCol, nRow, aAddress.Tab() );
                                nMax = std::max( nMyCol + nC, nMyRow + nR );
                                nDistance = nD;
                            }
                        }
                    }
                    else
                    {
                        aOne.Set( nCol, nRow, aAddress.Tab() );
                        nDistance = nC * nC + nR * nR;
                        nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
 
                    }
                    bFound = true;
                }
            }
        }
        else
        {
            ScCellIterator aIter( rDoc, ScRange( aOne, aTwo ) );
            for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
            {
                if ( bFound )
                {   // stop if everything else is further away
                    if ( nMax < static_cast<tools::Long>(aIter.GetPos().Col()) )
                        break;      // aIter
                }
                CellType eType = aIter.getType();
                bool bOk = false;
                if (eType == CELLTYPE_FORMULA)
                {
                    ScFormulaCell* pFC = aIter.getFormulaCell();
                    bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
                }
                else
                    bOk = true;
 
                if (bOk && aIter.hasString())
                {
                    OUString aStr = aIter.getString();
                    if ( ScGlobal::GetTransliteration().isEqual( aStr, aName ) )
                    {
                        SCCOL nCol = aIter.GetPos().Col();
                        SCROW nRow = aIter.GetPos().Row();
                        tools::Long nC = nMyCol - nCol;
                        tools::Long nR = nMyRow - nRow;
                        if ( bFound )
                        {
                            tools::Long nD = nC * nC + nR * nR;
                            if ( nD < nDistance )
                            {
                                if ( nC < 0 || nR < 0 )
                                {   // right or below
                                    bTwo = true;
                                    aTwo.Set( nCol, nRow, aIter.GetPos().Tab() );
                                    nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
                                    nDistance = nD;
                                }
                                else if ( nRow >= aOne.Row() || nMyRow < static_cast<tools::Long>(aOne.Row()) )
                                {
                                    // upper left, only if not further up than the
                                    // current entry and nMyRow is below (CellIter
                                    // runs column-wise)
                                    bTwo = false;
                                    aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
                                    nMax = std::max( nMyCol + nC, nMyRow + nR );
                                    nDistance = nD;
                                }
                            }
                        }
                        else
                        {
                            aOne.Set( nCol, nRow, aIter.GetPos().Tab() );
                            nDistance = nC * nC + nR * nR;
                            nMax = std::max( nMyCol + std::abs( nC ), nMyRow + std::abs( nR ) );
                        }
                        bFound = true;
                    }
                }
            }
        }
 
        if ( bFound )
        {
            ScAddress aAdr;
            if ( bTwo )
            {
                if ( nMyCol >= static_cast<tools::Long>(aOne.Col()) && nMyRow >= static_cast<tools::Long>(aOne.Row()) )
                    aAdr = aOne;        // upper left takes precedence
                else
                {
                    if ( nMyCol < static_cast<tools::Long>(aOne.Col()) )
                    {   // two to the right
                        if ( nMyRow >= static_cast<tools::Long>(aTwo.Row()) )
                            aAdr = aTwo;        // directly right
                        else
                            aAdr = aOne;
                    }
                    else
                    {   // two below or below and right, take the nearest
                        tools::Long nC1 = nMyCol - aOne.Col();
                        tools::Long nR1 = nMyRow - aOne.Row();
                        tools::Long nC2 = nMyCol - aTwo.Col();
                        tools::Long nR2 = nMyRow - aTwo.Row();
                        if ( nC1 * nC1 + nR1 * nR1 <= nC2 * nC2 + nR2 * nR2 )
                            aAdr = aOne;
                        else
                            aAdr = aTwo;
                    }
                }
            }
            else
                aAdr = aOne;
            aRef.InitAddress( aAdr );
            // Prioritize on column label; row label only if the next cell
            // above/below the found label cell is text, or if both are not and
            // the cell below is empty and the next cell to the right is
            // numeric.
            if ((aAdr.Row() < rDoc.MaxRow() && rDoc.HasStringData(
                            aAdr.Col(), aAdr.Row() + 1, aAdr.Tab()))
                    || (aAdr.Row() > 0 && rDoc.HasStringData(
                            aAdr.Col(), aAdr.Row() - 1, aAdr.Tab()))
                    || (aAdr.Row() < rDoc.MaxRow() && rDoc.GetRefCellValue(
                            ScAddress( aAdr.Col(), aAdr.Row() + 1, aAdr.Tab())).isEmpty()
                        && aAdr.Col() < rDoc.MaxCol() && rDoc.GetRefCellValue(
                            ScAddress( aAdr.Col() + 1, aAdr.Row(), aAdr.Tab())).hasNumeric()))
                aRef.SetRowRel( true );     // RowName
            else
                aRef.SetColRel( true );     // ColName
            aRef.SetAddress(rDoc.GetSheetLimits(), aAdr, aPos);
        }
    }
    if ( bFound )
    {
        maRawToken.SetSingleReference( aRef );
        maRawToken.eOp = ocColRowName;
        return true;
    }
    else
        return false;
}
 
bool ScCompiler::ParseBoolean( const OUString& rName )
{
    OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName ) );
    if( iLook != mxSymbols->getHashMap().end() &&
        ((*iLook).second == ocTrue ||
         (*iLook).second == ocFalse) )
    {
        maRawToken.SetOpCode( (*iLook).second );
        return true;
    }
    else
        return false;
}
 
bool ScCompiler::ParseErrorConstant( const OUString& rName )
{
    FormulaError nError = GetErrorConstant( rName);
    if (nError != FormulaError::NONE)
    {
        maRawToken.SetErrorConstant( nError);
        return true;
    }
    else
        return false;
}
 
bool ScCompiler::ParseTableRefItem( const OUString& rName )
{
    bool bItem = false;
    OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
    if (iLook != mxSymbols->getHashMap().end())
    {
        // Only called when there actually is a current TableRef, hence
        // accessing maTableRefs.back() is safe.
        ScTableRefToken* p = maTableRefs.back().mxToken.get();
        assert(p);  // not a ScTableRefToken can't be
 
        switch ((*iLook).second)
        {
            case ocTableRefItemAll:
                bItem = true;
                p->AddItem( ScTableRefToken::ALL);
                break;
            case ocTableRefItemHeaders:
                bItem = true;
                p->AddItem( ScTableRefToken::HEADERS);
                break;
            case ocTableRefItemData:
                bItem = true;
                p->AddItem( ScTableRefToken::DATA);
                break;
            case ocTableRefItemTotals:
                bItem = true;
                p->AddItem( ScTableRefToken::TOTALS);
                break;
            case ocTableRefItemThisRow:
                bItem = true;
                p->AddItem( ScTableRefToken::THIS_ROW);
                break;
            default:
                ;
        }
        if (bItem)
            maRawToken.SetOpCode( (*iLook).second );
    }
    return bItem;
}
 
namespace {
OUString unescapeTableRefColumnSpecifier( const OUString& rStr )
{
    // '#', '[', ']' and '\'' are escaped with '\''
 
    if (rStr.indexOf( '\'' ) < 0)
        return rStr;
 
    const sal_Int32 n = rStr.getLength();
    OUStringBuffer aBuf( n );
    const sal_Unicode* p = rStr.getStr();
    const sal_Unicode* const pStop = p + n;
    bool bEscaped = false;
    for ( ; p < pStop; ++p)
    {
        const sal_Unicode c = *p;
        if (bEscaped)
        {
            aBuf.append( c );
            bEscaped = false;
        }
        else if (c == '\'')
            bEscaped = true;    // unescaped escaping '\''
        else
            aBuf.append( c );
    }
    return aBuf.makeStringAndClear();
}
}
 
bool ScCompiler::ParseTableRefColumn( const OUString& rName )
{
    // Only called when there actually is a current TableRef, hence
    // accessing maTableRefs.back() is safe.
    ScTableRefToken* p = maTableRefs.back().mxToken.get();
    assert(p);  // not a ScTableRefToken can't be
 
    ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( p->GetIndex());
    if (!pDBData)
        return false;
 
    OUString aName( unescapeTableRefColumnSpecifier( rName));
 
    ScRange aRange;
    pDBData->GetArea( aRange);
    aRange.aEnd.SetTab( aRange.aStart.Tab());
    aRange.aEnd.SetRow( aRange.aStart.Row());
 
    // Prefer the stored internal table column name, which is also needed for
    // named expressions during document load time when cell content isn't
    // available yet. Also, avoiding a possible calculation step in case the
    // header cell is a formula cell is "a good thing".
    sal_Int32 nOffset = pDBData->GetColumnNameOffset( aName);
    if (nOffset >= 0)
    {
        // This is sneaky... we always use the top row of the database range,
        // regardless of whether it is a header row or not. Code evaluating
        // this reference must take that into account and may have to act
        // differently if it is a header-less table. Which are two places,
        // HandleTableRef() (no change necessary there) and
        // CreateStringFromSingleRef() (must not fallback to cell lookup).
        ScSingleRefData aRef;
        ScAddress aAdr( aRange.aStart);
        aAdr.IncCol( nOffset);
        aRef.InitAddress( aAdr);
        maRawToken.SetSingleReference( aRef );
        return true;
    }
 
    if (pDBData->HasHeader())
    {
        // Quite similar to IsColRowName() but limited to one row of headers.
        ScCellIterator aIter( rDoc, aRange);
        for (bool bHas = aIter.first(); bHas; bHas = aIter.next())
        {
            CellType eType = aIter.getType();
            bool bOk = false;
            if (eType == CELLTYPE_FORMULA)
            {
                ScFormulaCell* pFC = aIter.getFormulaCell();
                bOk = (pFC->GetCode()->GetCodeLen() > 0) && (pFC->aPos != aPos);
            }
            else
                bOk = true;
 
            if (bOk && aIter.hasString())
            {
                OUString aStr = aIter.getString();
                if (ScGlobal::GetTransliteration().isEqual( aStr, aName))
                {
                    // If this is successful and the internal column name
                    // lookup was not, it may be worth a warning.
                    SAL_WARN("sc.core", "ScCompiler::IsTableRefColumn - falling back to cell lookup");
 
                    /* XXX NOTE: we could init the column as relative so copying a
                     * formula across columns would point to the relative column,
                     * but do it absolute because:
                     * a) it makes the reference work in named expressions without
                     * having to distinguish
                     * b) Excel does it the same. */
                    ScSingleRefData aRef;
                    aRef.InitAddress( aIter.GetPos());
                    maRawToken.SetSingleReference( aRef );
                    return true;
                }
            }
        }
    }
 
    return false;
}
 
void ScCompiler::SetAutoCorrection( bool bVal )
{
    assert(mbJumpCommandReorder);
    bAutoCorrect = bVal;
    mbStopOnError = !bVal;
}
 
void ScCompiler::AutoCorrectParsedSymbol()
{
    sal_Int32 nPos = aCorrectedSymbol.getLength();
    if ( !nPos )
        return;
 
    nPos--;
    const sal_Unicode cQuote = '\"';
    const sal_Unicode cx = 'x';
    const sal_Unicode cX = 'X';
    sal_Unicode c1 = aCorrectedSymbol[0];
    sal_Unicode c2 = aCorrectedSymbol[nPos];
    sal_Unicode c2p = nPos > 0 ? aCorrectedSymbol[nPos-1] : 0;
    if ( c1 == cQuote && c2 != cQuote  )
    {   // "...
        // What's not a word doesn't belong to it.
        // Don't be pedantic: c < 128 should be sufficient here.
        while ( nPos && ((aCorrectedSymbol[nPos] < 128) &&
                ((GetCharTableFlags(aCorrectedSymbol[nPos], aCorrectedSymbol[nPos-1]) &
                (ScCharFlags::Word | ScCharFlags::CharDontCare)) == ScCharFlags::NONE)) )
            nPos--;
        if ( nPos == MAXSTRLEN - 1 )
            aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos, 1, rtl::OUStringChar(cQuote) );   // '"' the MAXSTRLENth character
        else
            aCorrectedSymbol = aCorrectedSymbol.replaceAt( nPos + 1, 0, rtl::OUStringChar(cQuote) );
        bCorrected = true;
    }
    else if ( c1 != cQuote && c2 == cQuote )
    {   // ..."
        aCorrectedSymbol = OUStringChar(cQuote) + aCorrectedSymbol;
        bCorrected = true;
    }
    else if ( nPos == 0 && (c1 == cx || c1 == cX) )
    {   // x => *
        aCorrectedSymbol = mxSymbols->getSymbol(ocMul);
        bCorrected = true;
    }
    else if ( (GetCharTableFlags( c1, 0 ) & ScCharFlags::CharValue)
           && (GetCharTableFlags( c2, c2p ) & ScCharFlags::CharValue) )
    {
        if ( aCorrectedSymbol.indexOf(cx) >= 0 ) // At least two tokens separated by cx
        {   // x => *
            sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
            aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cx), OUStringChar(c));
            bCorrected = true;
        }
        if ( aCorrectedSymbol.indexOf(cX) >= 0 ) // At least two tokens separated by cX
        {   // X => *
            sal_Unicode c = mxSymbols->getSymbolChar(ocMul);
            aCorrectedSymbol = aCorrectedSymbol.replaceAll(OUStringChar(cX), OUStringChar(c));
            bCorrected = true;
        }
    }
    else
    {
        OUString aSymbol( aCorrectedSymbol );
        OUString aDoc;
        if ( aSymbol[0] == '\'' )
        {
            sal_Int32 nPosition = aSymbol.indexOf( "'#" );
            if (nPosition != -1)
            {   // Split off 'Doc'#, may be d:\... or whatever
                aDoc = aSymbol.copy(0, nPosition + 2);
                aSymbol = aSymbol.copy(nPosition + 2);
            }
        }
        sal_Int32 nRefs = comphelper::string::getTokenCount(aSymbol, ':');
        bool bColons;
        if ( nRefs > 2 )
        {   // duplicated or too many ':'? B:2::C10 => B2:C10
            bColons = true;
            sal_Int32 nIndex = 0;
            OUString aTmp1( aSymbol.getToken( 0, ':', nIndex ) );
            sal_Int32 nLen1 = aTmp1.getLength();
            OUStringBuffer aSym;
            OUString aTmp2;
            bool bLastAlp = true;
            sal_Int32 nStrip = 0;
            sal_Int32 nCount = nRefs;
            for ( sal_Int32 j=1; j<nCount; j++ )
            {
                aTmp2 = aSymbol.getToken( 0, ':', nIndex );
                sal_Int32 nLen2 = aTmp2.getLength();
                if ( nLen1 || nLen2 )
                {
                    if ( nLen1 )
                    {
                        aSym.append(aTmp1);
                        bLastAlp = CharClass::isAsciiAlpha( aTmp1 );
                    }
                    if ( nLen2 )
                    {
                        bool bNextNum = CharClass::isAsciiNumeric( aTmp2 );
                        if ( bLastAlp == bNextNum && nStrip < 1 )
                        {
                            // Must be alternating number/string, only
                            // strip within a reference.
                            nRefs--;
                            nStrip++;
                        }
                        else
                        {
                            if ( !aSym.isEmpty() && aSym[aSym.getLength()-1] != ':')
                                aSym.append(":");
                            nStrip = 0;
                        }
                        bLastAlp = !bNextNum;
                    }
                    else
                    {   // ::
                        nRefs--;
                        if ( nLen1 )
                        {   // B10::C10 ? append ':' on next round
                            if ( !bLastAlp && !CharClass::isAsciiNumeric( aTmp1 ) )
                                nStrip++;
                        }
                    }
                    aTmp1 = aTmp2;
                    nLen1 = nLen2;
                }
                else
                    nRefs--;
            }
            aSymbol = aSym + aTmp1;
            aSym.setLength(0);
        }
        else
            bColons = false;
        if ( nRefs && nRefs <= 2 )
        {   // reference twisted? 4A => A4 etc.
            OUString aTab[2], aRef[2];
            const ScAddress::Details aDetails( pConv->meConv, aPos );
            if ( nRefs == 2 )
            {
                sal_Int32 nIdx{ 0 };
                aRef[0] = aSymbol.getToken( 0, ':', nIdx );
                aRef[1] = aSymbol.getToken( 0, ':', nIdx );
            }
            else
                aRef[0] = aSymbol;
 
            bool bChanged = false;
            bool bOk = true;
            ScRefFlags nMask = ScRefFlags::VALID | ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID;
            for ( int j=0; j<nRefs; j++ )
            {
                sal_Int32 nTmp = 0;
                sal_Int32 nDotPos = -1;
                while ( (nTmp = aRef[j].indexOf( '.', nTmp )) != -1 )
                    nDotPos = nTmp++;      // the last one counts
                if ( nDotPos != -1 )
                {
                    aTab[j] = aRef[j].copy( 0, nDotPos + 1 );  // with '.'
                    aRef[j] = aRef[j].copy( nDotPos + 1 );
                }
                OUString aOld( aRef[j] );
                OUStringBuffer aStr2;
                const sal_Unicode* p = aRef[j].getStr();
                while ( *p && rtl::isAsciiDigit( *p ) )
                    aStr2.append(*p++);
                aRef[j] = OUString( p );
                aRef[j] += aStr2;
                if ( bColons || aRef[j] != aOld )
                {
                    bChanged = true;
                    ScAddress aAdr;
                    bOk &= ((aAdr.Parse( aRef[j], rDoc, aDetails ) & nMask) == nMask);
                }
            }
            if ( bChanged && bOk )
            {
                aCorrectedSymbol = aDoc;
                aCorrectedSymbol += aTab[0];
                aCorrectedSymbol += aRef[0];
                if ( nRefs == 2 )
                {
                    aCorrectedSymbol += ":";
                    aCorrectedSymbol += aTab[1];
                    aCorrectedSymbol += aRef[1];
                }
                bCorrected = true;
            }
        }
    }
}
 
bool ScCompiler::ToUpperAsciiOrI18nIsAscii( OUString& rUpper, const OUString& rOrg ) const
{
    if (FormulaGrammar::isODFF( meGrammar) || FormulaGrammar::isOOXML( meGrammar))
    {
        // ODFF and OOXML have defined sets of English function names, avoid
        // i18n overhead.
        rUpper = rOrg.toAsciiUpperCase();
        return true;
    }
    else
    {
        // One of localized or English.
        rUpper = pCharClass->uppercase(rOrg);
        return false;
    }
}
 
short ScCompiler::GetPossibleParaCount( const std::u16string_view& rLambdaFormula ) const
{
    sal_Unicode cSep = mxSymbols->getSymbolChar(ocSep);
    sal_Unicode cOpen = mxSymbols->getSymbolChar(ocOpen);
    sal_Unicode cClose = mxSymbols->getSymbolChar(ocClose);
    sal_Unicode cArrayOpen = mxSymbols->getSymbolChar(ocArrayOpen);
    sal_Unicode cArrayClose = mxSymbols->getSymbolChar(ocArrayClose);
    short nBrackets = 0;
 
    short nCount = std::count_if(rLambdaFormula.begin(), rLambdaFormula.end(),
        [&](sal_Unicode c) {
            if (c == cOpen || c == cArrayOpen || c == '[') {
                nBrackets++;
                return false;
            }
            else if (c == cClose || c == cArrayClose || c == ']') {
                nBrackets--;
                return false;
            }
            else {
                if (nBrackets == 1)
                    return c == cSep;
                else
                    return false;
            }
        });
 
    return static_cast<short>(nCount + 1);
}
 
bool ScCompiler::NextNewToken( bool bInArray )
{
    if (!maPendingOpCodes.empty())
    {
        maRawToken.SetOpCode(maPendingOpCodes.front());
        maPendingOpCodes.pop();
        return true;
    }
 
    bool bAllowBooleans = bInArray;
    const std::vector<Whitespace> vSpaces = NextSymbol(bInArray);
 
    if (!cSymbol[0])
    {
        if (nSrcPos < aFormula.getLength())
        {
            // Nothing could be parsed, remainder as bad string.
            // NextSymbol() must had set an error for this.
            assert( pArr->GetCodeError() != FormulaError::NONE);
            const OUString aBad( aFormula.copy( nSrcPos));
            svl::SharedString aSS = rDoc.GetSharedStringPool().intern( aBad);
            maRawToken.SetString( aSS.getData(), aSS.getDataIgnoreCase());
            maRawToken.NewOpCode( ocBad);
            nSrcPos = aFormula.getLength();
            // Add bad string as last token.
            return true;
        }
        return false;
    }
 
    if (!vSpaces.empty())
    {
        ScRawToken aToken;
        for (const auto& rSpace : vSpaces)
        {
            if (rSpace.cChar == 0x20)
            {
                // For now keep this a FormulaByteToken for the nasty
                // significant whitespace intersection. This probably can be
                // changed to a FormulaSpaceToken but then other places may
                // need to be adapted.
                aToken.SetOpCode( ocSpaces );
                aToken.sbyte.cByte = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
            }
            else
            {
                aToken.SetOpCode( ocWhitespace );
                aToken.whitespace.nCount = static_cast<sal_uInt8>( std::min<sal_Int32>(rSpace.nCount, 255) );
                aToken.whitespace.cChar = rSpace.cChar;
            }
            if (!static_cast<ScTokenArray*>(pArr)->AddRawToken( aToken ))
            {
                SetError(FormulaError::CodeOverflow);
                return false;
            }
        }
    }
 
    // Short cut for references when reading ODF to speedup things.
    if (mnPredetectedReference)
    {
        OUString aStr( cSymbol);
        bool bInvalidExternalNameRange;
        if (!ParsePredetectedReference( aStr) && !ParseExternalNamedRange( aStr, bInvalidExternalNameRange ))
        {
            svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aStr);
            maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
            maRawToken.NewOpCode( ocBad );
        }
        return true;
    }
 
    if ( (cSymbol[0] == '#' || cSymbol[0] == '$') && cSymbol[1] == 0 &&
            !bAutoCorrect )
    {   // special case to speed up broken [$]#REF documents
        /* FIXME: ISERROR(#REF!) would be valid and true and the formula to
         * be processed as usual. That would need some special treatment,
         * also in NextSymbol() because of possible combinations of
         * #REF!.#REF!#REF! parts. In case of reading ODF that is all
         * handled by IsPredetectedReference(), this case here remains for
         * manual/API input. */
        OUString aBad( aFormula.copy( nSrcPos-1 ) );
        const FormulaToken* pBadToken = pArr->AddBad(aBad);
        eLastOp = pBadToken ? pBadToken->GetOpCode() : ocNone;
        return false;
    }
 
    if( ParseString() )
        return true;
 
    bool bMayBeFuncName;
    bool bAsciiNonAlnum;    // operators, separators, ...
    if ( cSymbol[0] < 128 )
    {
        bMayBeFuncName = rtl::isAsciiAlpha(cSymbol[0])
            || (cSymbol[0] == '_' && mxSymbols->isOOXML() && rtl::isAsciiAlpha(cSymbol[1]));
        if (!bMayBeFuncName && (cSymbol[0] == '_' && cSymbol[1] == '_') && !comphelper::IsFuzzing())
        {
            bMayBeFuncName = officecfg::Office::Common::Misc::ExperimentalMode::get();
        }
 
        bAsciiNonAlnum = !bMayBeFuncName && !rtl::isAsciiDigit( cSymbol[0] );
    }
    else
    {
        OUString aTmpStr( cSymbol[0] );
        bMayBeFuncName = pCharClass->isLetter( aTmpStr, 0 );
        bAsciiNonAlnum = false;
    }
 
    // Within a TableRef anything except an unescaped '[' or ']' is an item
    // or a column specifier, do not attempt to recognize any other single
    // operator there so even [,] or [+] for a single character column
    // specifier works. Note that space between two ocTableRefOpen is not
    // supported (Table[ [ColumnSpec]]), not only here. Note also that Table[]
    // without any item or column specifier is valid.
    if (bAsciiNonAlnum && cSymbol[1] == 0 && (eLastOp != ocTableRefOpen || cSymbol[0] == '[' || cSymbol[0] == ']'))
    {
        // Shortcut for operators and separators that need no further checks or upper.
        if (ParseOpCode( OUString( cSymbol), bInArray ))
            return true;
    }
 
    if ( bMayBeFuncName )
    {
        // a function name must be followed by a parenthesis
        const sal_Unicode* p = aFormula.getStr() + nSrcPos;
        while( *p == ' ' )
            p++;
        bMayBeFuncName = ( *p == '(' );
    }
 
    // Italian ARCTAN.2 resulted in #REF! => ParseOpcode() before
    // ParseReference().
 
    OUString aUpper;
    bool bAsciiUpper = false;
 
Label_Rewind:
 
    do
    {
        const OUString aOrg( cSymbol );
 
        // Check for TableRef column specifier first, it may be anything.
        if (cSymbol[0] != '#' && !maTableRefs.empty() && maTableRefs.back().mnLevel)
        {
            if (ParseTableRefColumn( aOrg ))
                return true;
            // Do not attempt to resolve as any other name.
            aUpper = aOrg;  // for ocBad
            break;          // do; create ocBad token or set error.
        }
 
        mbRewind = false;
        aUpper.clear();
        bAsciiUpper = false;
 
        if (bAsciiNonAlnum)
        {
            bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
            if (cSymbol[0] == '#')
            {
                // Check for TableRef item specifiers first.
                sal_uInt16 nLevel;
                if (!maTableRefs.empty() && ((nLevel = maTableRefs.back().mnLevel) == 2 || nLevel == 1))
                {
                    if (ParseTableRefItem( aUpper ))
                        return true;
                }
 
                // This can be either an error constant ...
                if (ParseErrorConstant( aUpper))
                    return true;
 
                // ... or some invalidated reference starting with #REF!
                // which is handled after the do loop.
 
                break;  // do; create ocBad token or set error.
            }
            if (ParseOpCode( aUpper, bInArray ))
                return true;
        }
 
        if (bMayBeFuncName)
        {
            if (aUpper.isEmpty())
                bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
            if (ParseOpCode( aUpper, bInArray ))
                return true;
        }
 
        // Column 'DM' ("Deutsche Mark", German currency) couldn't be
        // referred => ParseReference() before ParseValue().
        // Preserve case of file names in external references.
        if (ParseReference( aOrg ))
        {
            if (mbRewind)   // Range operator, but no direct reference.
                continue;   // do; up to range operator.
            // If a syntactically correct reference was recognized but invalid
            // e.g. because of non-existing sheet name => entire reference
            // ocBad to preserve input instead of #REF!.A1
            if (!maRawToken.IsValidReference(rDoc))
            {
                aUpper = aOrg;          // ensure for ocBad
                break;                  // do; create ocBad token or set error.
            }
            return true;
        }
 
        if (aUpper.isEmpty())
            bAsciiUpper = ToUpperAsciiOrI18nIsAscii( aUpper, aOrg);
 
        // ParseBoolean() before ParseValue() to catch inline bools without the kludge
        //    for inline arrays.
        if (bAllowBooleans && ParseBoolean( aUpper ))
            return true;
 
        if (ParseValue( aUpper ))
            return true;
 
        // User defined names and such do need i18n upper also in ODF.
        if (bAsciiUpper || mbCharClassesDiffer)
        {
            // Use current system locale here because user defined symbols are
            // more likely in that localized language than in the formula
            // language. This in corner cases needs to continue to work for
            // existing documents and environments.
            // Do not change bAsciiUpper from here on for the lowercase() call
            // below in the ocBad case to use the correct CharClass.
            aUpper = ScGlobal::getCharClass().uppercase( aOrg );
        }
 
        if (ParseNamedRange( aUpper ))
            return true;
 
        // Compiling a named expression during collecting them in import shall
        // not match arbitrary names that otherwise if all named expressions
        // were present would be recognized as named expression. Such name will
        // flag an error below and will be recompiled in a second step later
        // with ScRangeData::CompileUnresolvedXML()
        if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_NO_BREAK && rDoc.IsImportingXML())
            break;  // while
 
        // Preserve case of file names in external references.
        bool bInvalidExternalNameRange;
        if (ParseExternalNamedRange( aOrg, bInvalidExternalNameRange ))
            return true;
        // Preserve case of file names in external references even when range
        // is not valid and previous check failed tdf#89330
        if (bInvalidExternalNameRange)
        {
            // add ocBad but do not lowercase
            svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aOrg);
            maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
            maRawToken.NewOpCode( ocBad );
            return true;
        }
        if (ParseDBRange( aUpper ))
            return true;
        // If followed by '(' (with or without space inbetween) it can not be a
        // column/row label. Prevent arbitrary content detection.
        if (!bMayBeFuncName && ParseColRowName( aUpper ))
            return true;
        if (bMayBeFuncName && ParseMacro( aUpper ))
            return true;
        if (bMayBeFuncName && ParseOpCode2( aUpper ))
            return true;
 
        if (ParseLambdaFuncName( aOrg ))
            return true;
 
    } while (mbRewind);
 
    // Last chance: it could be a broken invalidated reference that contains
    // #REF! (but is not equal to), which we also wrote to ODFF between 2013
    // and 2016 until 5.1.4
    OUString aErrRef( mxSymbols->getSymbol( ocErrRef));
    if (aUpper.indexOf( aErrRef) >= 0 && ParseReference( aUpper, &aErrRef))
    {
        if (mbRewind)
            goto Label_Rewind;
        return true;
    }
 
    if ( meExtendedErrorDetection != EXTENDED_ERROR_DETECTION_NONE )
    {
        // set an error
        SetError( FormulaError::NoName );
        if (meExtendedErrorDetection == EXTENDED_ERROR_DETECTION_NAME_BREAK)
            return false;   // end compilation
    }
 
    // Provide single token information and continue. Do not set an error, that
    // would prematurely end compilation. Simple unknown names are handled by
    // the interpreter.
    // Use the same CharClass that was used for uppercase.
    aUpper = ((bAsciiUpper || mbCharClassesDiffer) ? ScGlobal::getCharClass() : *pCharClass).lowercase( aUpper );
    svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aUpper);
    maRawToken.SetString(aSS.getData(), aSS.getDataIgnoreCase());
    maRawToken.NewOpCode( ocBad );
    if ( bAutoCorrect )
        AutoCorrectParsedSymbol();
    return true;
}
 
void ScCompiler::CreateStringFromXMLTokenArray( OUString& rFormula, OUString& rFormulaNmsp )
{
    bool bExternal = GetGrammar() == FormulaGrammar::GRAM_EXTERNAL;
    sal_uInt16 nExpectedCount = bExternal ? 2 : 1;
    OSL_ENSURE( pArr->GetLen() == nExpectedCount, "ScCompiler::CreateStringFromXMLTokenArray - wrong number of tokens" );
    if( pArr->GetLen() == nExpectedCount )
    {
        FormulaToken** ppTokens = pArr->GetArray();
        // string tokens expected, GetString() will assert if token type is wrong
        rFormula = ppTokens[0]->GetString().getString();
        if( bExternal )
            rFormulaNmsp = ppTokens[1]->GetString().getString();
    }
}
 
namespace {
 
class ExternalFileInserter
{
    ScAddress maPos;
    ScExternalRefManager& mrRefMgr;
public:
    ExternalFileInserter(const ScAddress& rPos, ScExternalRefManager& rRefMgr) :
        maPos(rPos), mrRefMgr(rRefMgr) {}
 
    void operator() (sal_uInt16 nFileId) const
    {
        mrRefMgr.insertRefCell(nFileId, maPos);
    }
};
 
}
 
std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula )
{
    OSL_ENSURE( meGrammar != FormulaGrammar::GRAM_EXTERNAL, "ScCompiler::CompileString - unexpected grammar GRAM_EXTERNAL" );
    if( meGrammar == FormulaGrammar::GRAM_EXTERNAL )
        SetGrammar( FormulaGrammar::GRAM_PODF );
 
    ScTokenArray aArr(rDoc);
    pArr = &aArr;
    maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
    aFormula = comphelper::string::strip(rFormula, ' ');
 
    nSrcPos = 0;
    bCorrected = false;
    if ( bAutoCorrect )
    {
        aCorrectedFormula.clear();
        aCorrectedSymbol.clear();
    }
    sal_uInt8 nForced = 0;   // ==formula forces recalc even if cell is not visible
    if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
    {
        nSrcPos++;
        nForced++;
        if ( bAutoCorrect )
            aCorrectedFormula += "=";
    }
    if( nSrcPos < aFormula.getLength() && aFormula[nSrcPos] == '=' )
    {
        nSrcPos++;
        nForced++;
        if ( bAutoCorrect )
            aCorrectedFormula += "=";
    }
    struct FunctionStack
    {
        OpCode  eOp;
        short   nSep;
    };
    // FunctionStack only used if PODF or OOXML!
    bool bPODF = FormulaGrammar::isPODF( meGrammar);
    bool bOOXML = FormulaGrammar::isOOXML( meGrammar);
    bool bUseFunctionStack = (bPODF || bOOXML);
    const size_t nAlloc = 512;
    FunctionStack aFuncs[ nAlloc ];
    FunctionStack* pFunctionStack = (bUseFunctionStack && o3tl::make_unsigned(rFormula.getLength()) > nAlloc ?
         new FunctionStack[rFormula.getLength()] : &aFuncs[0]);
    pFunctionStack[0].eOp = ocNone;
    pFunctionStack[0].nSep = 0;
    size_t nFunction = 0;
    size_t nHighWatermark = 0;
    short nBrackets = 0;
    bool bInArray = false;
    eLastOp = ocOpen;
    while( NextNewToken( bInArray ) )
    {
        const OpCode eOp = maRawToken.GetOpCode();
        if (eOp == ocSkip)
            continue;
 
        switch (eOp)
        {
            case ocOpen:
            {
                if (eLastOp == ocLet)
                {
                    m_aLambda.bInLambdaFunction = true;
                    m_aLambda.nBracketPos = nBrackets;
                    m_aLambda.nParaPos++;
                    m_aLambda.nParaCount = GetPossibleParaCount(rFormula.subView(nSrcPos - 1));
                }
 
                ++nBrackets;
                if (bUseFunctionStack)
                {
                    ++nFunction;
                    pFunctionStack[ nFunction ].eOp = eLastOp;
                    pFunctionStack[ nFunction ].nSep = 0;
                    nHighWatermark = nFunction;
                }
            }
            break;
            case ocClose:
            {
                if( !nBrackets )
                {
                    SetError( FormulaError::PairExpected );
                    if ( bAutoCorrect )
                    {
                        bCorrected = true;
                        aCorrectedSymbol.clear();
                    }
                }
                else
                {
                    nBrackets--;
                    if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos == nBrackets)
                    {
                        m_aLambda.bInLambdaFunction = false;
                        m_aLambda.nBracketPos = nBrackets;
                    }
                }
                if (bUseFunctionStack && nFunction)
                    --nFunction;
            }
            break;
            case ocSep:
            {
                if (bUseFunctionStack)
                    ++pFunctionStack[ nFunction ].nSep;
 
                if (m_aLambda.bInLambdaFunction && m_aLambda.nBracketPos + 1 == nBrackets)
                    m_aLambda.nParaPos++;
            }
            break;
            case ocArrayOpen:
            {
                if( bInArray )
                    SetError( FormulaError::NestedArray );
                else
                    bInArray = true;
                // Don't count following column separator as parameter separator.
                if (bUseFunctionStack)
                {
                    ++nFunction;
                    pFunctionStack[ nFunction ].eOp = eOp;
                    pFunctionStack[ nFunction ].nSep = 0;
                    nHighWatermark = nFunction;
                }
            }
            break;
            case ocArrayClose:
            {
                if( bInArray )
                {
                    bInArray = false;
                }
                else
                {
                    SetError( FormulaError::PairExpected );
                    if ( bAutoCorrect )
                    {
                        bCorrected = true;
                        aCorrectedSymbol.clear();
                    }
                }
                if (bUseFunctionStack && nFunction)
                    --nFunction;
            }
            break;
            case ocTableRefOpen:
            {
                // Don't count following item separator as parameter separator.
                if (bUseFunctionStack)
                {
                    ++nFunction;
                    pFunctionStack[ nFunction ].eOp = eOp;
                    pFunctionStack[ nFunction ].nSep = 0;
                    nHighWatermark = nFunction;
                }
            }
            break;
            case ocTableRefClose:
            {
                if (bUseFunctionStack && nFunction)
                    --nFunction;
            }
            break;
            case ocColRowName:
            case ocColRowNameAuto:
                // The current implementation of column / row labels doesn't
                // function correctly in grouped cells.
                aArr.SetShareable(false);
            break;
            default:
            break;
        }
        if ((eLastOp != ocOpen || eOp != ocClose) &&
                (eLastOp == ocOpen ||
                 eLastOp == ocSep ||
                 eLastOp == ocArrayRowSep ||
                 eLastOp == ocArrayColSep ||
                 eLastOp == ocArrayOpen) &&
                (eOp == ocSep ||
                 eOp == ocClose ||
                 eOp == ocArrayRowSep ||
                 eOp == ocArrayColSep ||
                 eOp == ocArrayClose))
        {
            // TODO: should we check for known functions with optional empty
            // args so the correction dialog can do better?
            if ( !static_cast<ScTokenArray*>(pArr)->Add( new FormulaMissingToken ) )
            {
                SetError(FormulaError::CodeOverflow); break;
            }
        }
        if (bOOXML)
        {
            // Append a parameter for WEEKNUM, all 1.0
            // Function is already closed, parameter count is nSep+1
            size_t nFunc = nFunction + 1;
            if (eOp == ocClose && nFunc <= nHighWatermark &&
                     pFunctionStack[ nFunc ].nSep == 0 &&
                     pFunctionStack[ nFunc ].eOp == ocWeek)   // 2nd week start
            {
                if (    !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
                        !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
                {
                    SetError(FormulaError::CodeOverflow); break;
                }
            }
        }
        else if (bPODF)
        {
            /* TODO: for now this is the only PODF adapter. If there were more,
             * factor this out. */
            // Insert ADDRESS() new empty parameter 4 if there is a 4th, now to be 5th.
            if (eOp == ocSep &&
                    pFunctionStack[ nFunction ].eOp == ocAddress &&
                    pFunctionStack[ nFunction ].nSep == 3)
            {
                if (    !static_cast<ScTokenArray*>(pArr)->Add( new FormulaToken( svSep, ocSep)) ||
                        !static_cast<ScTokenArray*>(pArr)->Add( new FormulaDoubleToken( 1.0)))
                {
                    SetError(FormulaError::CodeOverflow); break;
                }
                ++pFunctionStack[ nFunction ].nSep;
            }
        }
        FormulaToken* pNewToken = static_cast<ScTokenArray*>(pArr)->Add( maRawToken.CreateToken(rDoc.GetSheetLimits()));
        if (!pNewToken && eOp == ocArrayClose && pArr->OpCodeBefore( pArr->GetLen()) == ocArrayClose)
        {
            // Nested inline array or non-value/non-string in array. The
            // original tokens are still in the ScTokenArray and not merged
            // into an ScMatrixToken. Set error but keep on tokenizing.
            SetError( FormulaError::BadArrayContent);
        }
        else if (!pNewToken)
        {
            SetError(FormulaError::CodeOverflow);
            break;
        }
        else if (eLastOp == ocRange && pNewToken->GetOpCode() == ocPush && pNewToken->GetType() == svSingleRef)
        {
            static_cast<ScTokenArray*>(pArr)->MergeRangeReference( aPos);
        }
        else if (eLastOp == ocDBArea && pNewToken->GetOpCode() == ocTableRefOpen)
        {
            sal_uInt16 nIdx = pArr->GetLen() - 1;
            const FormulaToken* pPrev = pArr->PeekPrev( nIdx);
            if (pPrev && pPrev->GetOpCode() == ocDBArea)
            {
                ScTableRefToken* pTableRefToken = new ScTableRefToken( pPrev->GetIndex(), ScTableRefToken::TABLE);
                maTableRefs.emplace_back( pTableRefToken);
                // pPrev may be dead hereafter.
                static_cast<ScTokenArray*>(pArr)->ReplaceToken( nIdx, pTableRefToken,
                        FormulaTokenArray::ReplaceMode::CODE_ONLY);
            }
        }
        switch (eOp)
        {
            case ocTableRefOpen:
                SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefOpen without TableRefEntry");
                if (maTableRefs.empty())
                    SetError(FormulaError::Pair);
                else
                    ++maTableRefs.back().mnLevel;
                break;
            case ocTableRefClose:
                SAL_WARN_IF( maTableRefs.empty(), "sc.core", "ocTableRefClose without TableRefEntry");
                if (maTableRefs.empty())
                    SetError(FormulaError::Pair);
                else
                {
                    if (--maTableRefs.back().mnLevel == 0)
                        maTableRefs.pop_back();
                }
                break;
            default:
                break;
        }
        eLastOp = maRawToken.GetOpCode();
        if ( bAutoCorrect )
            aCorrectedFormula += aCorrectedSymbol;
    }
    if ( mbCloseBrackets )
    {
        if( bInArray )
        {
            FormulaByteToken aToken( ocArrayClose );
            if( !pArr->AddToken( aToken ) )
            {
                SetError(FormulaError::CodeOverflow);
            }
            else if ( bAutoCorrect )
                aCorrectedFormula += mxSymbols->getSymbol(ocArrayClose);
        }
 
        if (nBrackets)
        {
            FormulaToken aToken( svSep, ocClose );
            while( nBrackets-- )
            {
                if( !pArr->AddToken( aToken ) )
                {
                    SetError(FormulaError::CodeOverflow);
                    break;  // while
                }
                if ( bAutoCorrect )
                    aCorrectedFormula += mxSymbols->getSymbol(ocClose);
            }
        }
    }
    if ( nForced >= 2 )
        pArr->SetRecalcModeForced();
 
    if (pFunctionStack != &aFuncs[0])
        delete [] pFunctionStack;
 
    // remember pArr, in case a subsequent CompileTokenArray() is executed.
    std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( std::move(aArr) ));
    pNew->GenHash();
    // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
    pArr = pNew.get();
    maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
 
    if (!maExternalFiles.empty())
    {
        // Remove duplicates, and register all external files found in this cell.
        std::sort(maExternalFiles.begin(), maExternalFiles.end());
        std::vector<sal_uInt16>::iterator itEnd = std::unique(maExternalFiles.begin(), maExternalFiles.end());
        std::for_each(maExternalFiles.begin(), itEnd, ExternalFileInserter(aPos, *rDoc.GetExternalRefManager()));
        maExternalFiles.erase(itEnd, maExternalFiles.end());
    }
 
    return pNew;
}
 
std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormula, const OUString& rFormulaNmsp )
{
    OSL_ENSURE( (GetGrammar() == FormulaGrammar::GRAM_EXTERNAL) || rFormulaNmsp.isEmpty(),
        "ScCompiler::CompileString - unexpected formula namespace for internal grammar" );
    if( GetGrammar() == FormulaGrammar::GRAM_EXTERNAL ) try
    {
        ScFormulaParserPool& rParserPool = rDoc.GetFormulaParserPool();
        uno::Reference< sheet::XFormulaParser > xParser( rParserPool.getFormulaParser( rFormulaNmsp ), uno::UNO_SET_THROW );
        table::CellAddress aReferencePos;
        ScUnoConversion::FillApiAddress( aReferencePos, aPos );
        uno::Sequence< sheet::FormulaToken > aTokenSeq = xParser->parseFormula( rFormula, aReferencePos );
        ScTokenArray aTokenArray(rDoc);
        if( ScTokenConversion::ConvertToTokenArray( rDoc, aTokenArray, aTokenSeq ) )
        {
            // remember pArr, in case a subsequent CompileTokenArray() is executed.
            std::unique_ptr<ScTokenArray> pNew(new ScTokenArray( std::move(aTokenArray) ));
            // coverity[escape : FALSE] - ownership of pNew is retained by caller, so pArr remains valid
            pArr = pNew.get();
            maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
            return pNew;
        }
    }
    catch( uno::Exception& )
    {
    }
    // no success - fallback to some internal grammar and hope the best
    return CompileString( rFormula );
}
 
ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const
{
    return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex());
}
 
bool ScCompiler::HandleStringName()
{
    ScTokenArray* pNew = new ScTokenArray(rDoc);
    pNew->AddString(mpToken->GetString());
    PushTokenArray(pNew, true);
    return GetToken();
}
 
bool ScCompiler::HandleRange()
{
    ScTokenArray* pNew;
    const ScRangeData* pRangeData = GetRangeData( *mpToken);
    if (pRangeData)
    {
        FormulaError nErr = pRangeData->GetErrCode();
        if( nErr != FormulaError::NONE )
            SetError( nErr );
        else if (mbJumpCommandReorder)
        {
            // put named formula into parentheses.
            // But only if there aren't any yet, parenthetical
            // ocSep doesn't work, e.g. SUM((...;...))
            // or if not directly between ocSep/parenthesis,
            // e.g. SUM(...;(...;...)) no, SUM(...;(...)*3) yes,
            // in short: if it isn't a self-contained expression.
            FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
            FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
            OpCode eOp1 = (p1 ? p1->GetOpCode() : ocSep);
            OpCode eOp2 = (p2 ? p2->GetOpCode() : ocSep);
            bool bBorder1 = (eOp1 == ocSep || eOp1 == ocOpen);
            bool bBorder2 = (eOp2 == ocSep || eOp2 == ocClose);
            bool bAddPair = !(bBorder1 && bBorder2);
            if ( bAddPair )
            {
                pNew = new ScTokenArray(rDoc);
                pNew->AddOpCode( ocClose );
                PushTokenArray( pNew, true );
            }
            pNew = pRangeData->GetCode()->Clone().release();
            pNew->SetFromRangeName( true );
            PushTokenArray( pNew, true );
            if( pRangeData->HasReferences() )
            {
                // Relative sheet references in sheet-local named expressions
                // shall still point to the same sheet as if used on the
                // original sheet, not shifted to the current position where
                // they are used.
                SCTAB nSheetTab = mpToken->GetSheet();
                if (nSheetTab >= 0 && nSheetTab != aPos.Tab())
                    AdjustSheetLocalNameRelReferences( nSheetTab - aPos.Tab());
 
                SetRelNameReference();
                MoveRelWrap();
            }
            maArrIterator.Reset();
            if ( bAddPair )
            {
                pNew = new ScTokenArray(rDoc);
                pNew->AddOpCode( ocOpen );
                PushTokenArray( pNew, true );
            }
            return GetToken();
        }
    }
    else
    {
        // No ScRangeData for an already compiled token can happen in BIFF .xls
        // import if the original range is not present in the document.
        pNew = new ScTokenArray(rDoc);
        pNew->Add( new FormulaErrorToken( FormulaError::NoName));
        PushTokenArray( pNew, true );
        return GetToken();
    }
    return true;
}
 
bool ScCompiler::HandleExternalReference(const FormulaToken& _aToken)
{
    // Handle external range names.
    switch (_aToken.GetType())
    {
        case svExternalSingleRef:
        case svExternalDoubleRef:
            break;
        case svExternalName:
        {
            ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
            const OUString* pFile = pRefMgr->getExternalFileName(_aToken.GetIndex());
            if (!pFile)
            {
                SetError(FormulaError::NoName);
                return true;
            }
 
            OUString aName = _aToken.GetString().getString();
            ScExternalRefCache::TokenArrayRef xNew = pRefMgr->getRangeNameTokens(
                _aToken.GetIndex(), aName, &aPos);
 
            if (!xNew)
            {
                SetError(FormulaError::NoName);
                return true;
            }
 
            ScTokenArray* pNew = xNew->Clone().release();
            PushTokenArray( pNew, true);
            if (FormulaTokenArrayPlainIterator(*pNew).GetNextReference() != nullptr)
            {
                SetRelNameReference();
                MoveRelWrap();
            }
            maArrIterator.Reset();
            return GetToken();
        }
        default:
            OSL_FAIL("Wrong type for external reference!");
            return false;
    }
    return true;
}
 
void ScCompiler::AdjustSheetLocalNameRelReferences( SCTAB nDelta )
{
    for ( auto t: pArr->References() )
    {
        ScSingleRefData& rRef1 = *t->GetSingleRef();
        if (rRef1.IsTabRel())
            rRef1.IncTab( nDelta);
        if ( t->GetType() == svDoubleRef )
        {
            ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
            if (rRef2.IsTabRel())
                rRef2.IncTab( nDelta);
        }
    }
}
 
// reference of named range with relative references
 
void ScCompiler::SetRelNameReference()
{
    for ( auto t: pArr->References() )
    {
        ScSingleRefData& rRef1 = *t->GetSingleRef();
        if ( rRef1.IsColRel() || rRef1.IsRowRel() || rRef1.IsTabRel() )
            rRef1.SetRelName( true );
        if ( t->GetType() == svDoubleRef )
        {
            ScSingleRefData& rRef2 = t->GetDoubleRef()->Ref2;
            if ( rRef2.IsColRel() || rRef2.IsRowRel() || rRef2.IsTabRel() )
                rRef2.SetRelName( true );
        }
    }
}
 
// Wrap-adjust relative references of a RangeName to current position,
// don't call for other token arrays!
void ScCompiler::MoveRelWrap()
{
    for ( auto t: pArr->References() )
    {
        if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
            ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
        else
            ScRefUpdate::MoveRelWrap( rDoc, aPos, rDoc.MaxCol(), rDoc.MaxRow(), *t->GetDoubleRef() );
    }
}
 
// Wrap-adjust relative references of a RangeName to current position,
// don't call for other token arrays!
void ScCompiler::MoveRelWrap( const ScTokenArray& rArr, const ScDocument& rDoc, const ScAddress& rPos,
                              SCCOL nMaxCol, SCROW nMaxRow )
{
    for ( auto t: rArr.References() )
    {
        if ( t->GetType() == svSingleRef || t->GetType() == svExternalSingleRef )
            ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, SingleDoubleRefModifier( *t->GetSingleRef() ).Ref() );
        else
            ScRefUpdate::MoveRelWrap( rDoc, rPos, nMaxCol, nMaxRow, *t->GetDoubleRef() );
    }
}
 
bool ScCompiler::IsCharFlagAllConventions(
    OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags )
{
    sal_Unicode c = rStr[ nPos ];
    sal_Unicode cLast = nPos > 0 ? rStr[ nPos-1 ] : 0;
    if (c < 128)
    {
        for ( int nConv = formula::FormulaGrammar::CONV_UNSPECIFIED;
                ++nConv < formula::FormulaGrammar::CONV_LAST; )
        {
            if (pConventions[nConv] &&
                    ((pConventions[nConv]->getCharTableFlags(c, cLast) & nFlags) != nFlags))
                return false;
            // convention not known => assume valid
        }
        return true;
    }
    else
        return ScGlobal::getCharClass().isLetterNumeric( rStr, nPos );
}
 
void ScCompiler::CreateStringFromExternal( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
{
    const FormulaToken* t = pTokenP;
    sal_uInt16 nFileId = t->GetIndex();
    ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager();
    sal_uInt16 nUsedFileId = pRefMgr->convertFileIdToUsedFileId(nFileId);
    const OUString* pFileName = pRefMgr->getExternalFileName(nFileId);
    if (!pFileName)
        return;
 
    switch (t->GetType())
    {
        case svExternalName:
            rBuffer.append(pConv->makeExternalNameStr( nFileId, *pFileName, t->GetString().getString()));
        break;
        case svExternalSingleRef:
            pConv->makeExternalRefStr(rDoc.GetSheetLimits(),
                   rBuffer, GetPos(), nUsedFileId, *pFileName, t->GetString().getString(),
                   *t->GetSingleRef());
        break;
        case svExternalDoubleRef:
        {
            vector<OUString> aTabNames;
            pRefMgr->getAllCachedTableNames(nFileId, aTabNames);
            // No sheet names is a valid case if external sheets were not
            // cached in this document and external document is not reachable,
            // else not and worth to be investigated.
            SAL_WARN_IF( aTabNames.empty(), "sc.core", "wrecked cache of external document? '" <<
                    *pFileName << "' '" << t->GetString().getString() << "'");
 
            pConv->makeExternalRefStr(
                rDoc.GetSheetLimits(), rBuffer, GetPos(), nUsedFileId, *pFileName, aTabNames, t->GetString().getString(),
                *t->GetDoubleRef());
        }
        break;
        default:
            // warning, not error, otherwise we may end up with a never
            // ending message box loop if this was the cursor cell to be redrawn.
            OSL_FAIL("ScCompiler::CreateStringFromToken: unknown type of ocExternalRef");
    }
}
 
void ScCompiler::CreateStringFromMatrix( OUStringBuffer& rBuffer, const FormulaToken* pTokenP ) const
{
    const ScMatrix* pMatrix = pTokenP->GetMatrix();
    SCSIZE nC, nMaxC, nR, nMaxR;
 
    pMatrix->GetDimensions( nMaxC, nMaxR);
 
    rBuffer.append( mxSymbols->getSymbol(ocArrayOpen) );
    for( nR = 0 ; nR < nMaxR ; nR++)
    {
        if( nR > 0)
        {
            rBuffer.append( mxSymbols->getSymbol(ocArrayRowSep) );
        }
 
        for( nC = 0 ; nC < nMaxC ; nC++)
        {
            if( nC > 0)
            {
                rBuffer.append( mxSymbols->getSymbol(ocArrayColSep) );
            }
 
            if( pMatrix->IsValue( nC, nR ) )
            {
                if (pMatrix->IsBoolean(nC, nR))
                    AppendBoolean(rBuffer, pMatrix->GetDouble(nC, nR) != 0.0);
                else
                {
                    FormulaError nErr = pMatrix->GetError(nC, nR);
                    if (nErr != FormulaError::NONE)
                        rBuffer.append(ScGlobal::GetErrorString(nErr));
                    else
                        AppendDouble(rBuffer, pMatrix->GetDouble(nC, nR));
                }
            }
            else if( pMatrix->IsEmpty( nC, nR ) )
                ;
            else if( pMatrix->IsStringOrEmpty( nC, nR ) )
                AppendString( rBuffer, pMatrix->GetString(nC, nR).getString() );
        }
    }
    rBuffer.append( mxSymbols->getSymbol(ocArrayClose) );
}
 
namespace {
void escapeTableRefColumnSpecifier( OUString& rStr )
{
    const sal_Int32 n = rStr.getLength();
    OUStringBuffer aBuf( n * 2 );
    const sal_Unicode* p = rStr.getStr();
    const sal_Unicode* const pStop = p + n;
    for ( ; p < pStop; ++p)
    {
        const sal_Unicode c = *p;
        switch (c)
        {
            case '\'':
            case '[':
            case '#':
            case ']':
                aBuf.append( '\'' );
                break;
            default:
                ;   // nothing
        }
        aBuf.append( c );
    }
    rStr = aBuf.makeStringAndClear();
}
}
 
void ScCompiler::CreateStringFromSingleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
{
    const FormulaToken* p;
    OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
    const OpCode eOp = _pTokenP->GetOpCode();
    const ScSingleRefData& rRef = *_pTokenP->GetSingleRef();
    ScComplexRefData aRef;
    aRef.Ref1 = aRef.Ref2 = rRef;
    if ( eOp == ocColRowName )
    {
        ScAddress aAbs = rRef.toAbs(rDoc, aPos);
        if (rDoc.HasStringData(aAbs.Col(), aAbs.Row(), aAbs.Tab()))
        {
            OUString aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
            // Enquote to SingleQuoted.
            aStr = aStr.replaceAll(u"'", u"''");
            rBuffer.append('\'');
            rBuffer.append(aStr);
            rBuffer.append('\'');
        }
        else
        {
            rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
            pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
                              GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
        }
    }
    else if (pArr && (p = maArrIterator.PeekPrevNoSpaces()) && p->GetOpCode() == ocTableRefOpen)
    {
        OUString aStr;
        ScAddress aAbs = rRef.toAbs(rDoc, aPos);
        const ScDBData* pData = rDoc.GetDBAtCursor( aAbs.Col(), aAbs.Row(), aAbs.Tab(), ScDBDataPortion::AREA);
        SAL_WARN_IF( !pData, "sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef without ScDBData: " <<
                aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
        if (pData)
            aStr = pData->GetTableColumnName( aAbs.Col());
        if (aStr.isEmpty())
        {
            if (pData && pData->HasHeader())
            {
                SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef falling back to cell: " <<
                        aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
                aStr = rDoc.GetString(aAbs, &mrInterpreterContext);
            }
            else
            {
                SAL_WARN("sc.core", "ScCompiler::CreateStringFromSingleRef - TableRef of empty header-less: " <<
                        aAbs.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDoc));
                aStr = aErrRef;
            }
        }
        escapeTableRefColumnSpecifier( aStr);
        rBuffer.append(aStr);
    }
    else
        pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef,
                          GetSetupTabNames(), aRef, true, (pArr && pArr->IsFromRangeName()));
}
 
void ScCompiler::CreateStringFromDoubleRef( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
{
    OUString aErrRef = GetCurrentOpCodeMap()->getSymbol(ocErrRef);
    pConv->makeRefStr(rDoc.GetSheetLimits(), rBuffer, meGrammar, aPos, aErrRef, GetSetupTabNames(),
                      *_pTokenP->GetDoubleRef(), false, (pArr && pArr->IsFromRangeName()));
}
 
void ScCompiler::CreateStringFromIndex( OUStringBuffer& rBuffer, const FormulaToken* _pTokenP ) const
{
    const OpCode eOp = _pTokenP->GetOpCode();
    OUStringBuffer aBuffer;
    switch ( eOp )
    {
        case ocName:
        {
            const ScRangeData* pData = GetRangeData( *_pTokenP);
            if (pData)
            {
                SCTAB nTab = _pTokenP->GetSheet();
                if (nTab >= 0 && (nTab != aPos.Tab() || mbRefConventionChartOOXML))
                {
                    // Sheet-local on other sheet.
                    OUString aName;
                    if (rDoc.GetName( nTab, aName))
                    {
                        ScCompiler::CheckTabQuotes( aName, pConv->meConv);
                        aBuffer.append( aName);
                    }
                    else
                        aBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
                    aBuffer.append( pConv->getSpecialSymbol( ScCompiler::Convention::SHEET_SEPARATOR));
                }
                else if (mbRefConventionChartOOXML)
                {
                    aBuffer.append("[0]"
                        + OUStringChar(pConv->getSpecialSymbol(ScCompiler::Convention::SHEET_SEPARATOR)));
                }
                aBuffer.append(pData->GetName());
            }
        }
        break;
        case ocDBArea:
        {
            const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
            if (pDBData)
                aBuffer.append(pDBData->GetName());
        }
        break;
        case ocTableRef:
        {
            if (NeedsTableRefTransformation())
            {
                // Write the resulting reference if TableRef is not supported.
                const ScTableRefToken* pTR = dynamic_cast<const ScTableRefToken*>(_pTokenP);
                if (!pTR)
                    AppendErrorConstant( aBuffer, FormulaError::NoCode);
                else
                {
                    const FormulaToken* pRef = pTR->GetAreaRefRPN();
                    if (!pRef)
                        AppendErrorConstant( aBuffer, FormulaError::NoCode);
                    else
                    {
                        switch (pRef->GetType())
                        {
                            case svSingleRef:
                                CreateStringFromSingleRef( aBuffer, pRef);
                                break;
                            case svDoubleRef:
                                CreateStringFromDoubleRef( aBuffer, pRef);
                                break;
                            case svError:
                                AppendErrorConstant( aBuffer, pRef->GetError());
                                break;
                            default:
                                AppendErrorConstant( aBuffer, FormulaError::NoCode);
                        }
                    }
                }
            }
            else
            {
                const ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(_pTokenP->GetIndex());
                if (pDBData)
                    aBuffer.append(pDBData->GetName());
            }
        }
        break;
        default:
            ;   // nothing
    }
    if ( !aBuffer.isEmpty() )
        rBuffer.append(aBuffer);
    else
        rBuffer.append(ScCompiler::GetNativeSymbol(ocErrName));
}
 
void ScCompiler::LocalizeString( OUString& rName ) const
{
    ScGlobal::GetAddInCollection()->LocalizeString( rName );
}
 
bool ScCompiler::GetExcelName( OUString& rName ) const
{
    return ScGlobal::GetAddInCollection()->GetExcelName( rName, LANGUAGE_ENGLISH_US, rName);
}
 
FormulaTokenRef ScCompiler::ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 )
{
    return extendRangeReference( rDoc.GetSheetLimits(), rTok1, rTok2, aPos, true/*bReuseDoubleRef*/ );
}
 
void ScCompiler::fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const
{
    // All known AddIn functions.
    sheet::FormulaOpCodeMapEntry aEntry;
    aEntry.Token.OpCode = ocExternal;
 
    const LanguageTag aEnglishLanguageTag(LANGUAGE_ENGLISH_US);
    ScUnoAddInCollection* pColl = ScGlobal::GetAddInCollection();
    const tools::Long nCount = pColl->GetFuncCount();
    for (tools::Long i=0; i < nCount; ++i)
    {
        const ScUnoAddInFuncData* pFuncData = pColl->GetFuncData(i);
        if (pFuncData)
        {
            if ( _bIsEnglish )
            {
                // This is used with OOXML import, so GetExcelName() is really
                // wanted here until we'll have a parameter to differentiate
                // from the general css::sheet::XFormulaOpCodeMapper case and
                // use pFuncData->GetUpperEnglish().
                OUString aName;
                if (pFuncData->GetExcelName( aEnglishLanguageTag, aName))
                    aEntry.Name = aName;
                else
                    aEntry.Name = pFuncData->GetUpperName();
            }
            else
                aEntry.Name = pFuncData->GetUpperLocal();
            aEntry.Token.Data <<= pFuncData->GetOriginalName();
            _rVec.push_back( aEntry);
        }
    }
    // FIXME: what about those old non-UNO AddIns?
}
 
bool ScCompiler::HandleColRowName()
{
    ScSingleRefData& rRef = *mpToken->GetSingleRef();
    const ScAddress aAbs = rRef.toAbs(rDoc, aPos);
    if (!rDoc.ValidAddress(aAbs))
    {
        SetError( FormulaError::NoRef );
        return true;
    }
    SCCOL nCol = aAbs.Col();
    SCROW nRow = aAbs.Row();
    SCTAB nTab = aAbs.Tab();
    bool bColName = rRef.IsColRel();
    SCCOL nMyCol = aPos.Col();
    SCROW nMyRow = aPos.Row();
    bool bInList = false;
    bool bValidName = false;
    ScRangePairList* pRL = (bColName ?
        rDoc.GetColNameRanges() : rDoc.GetRowNameRanges());
    ScRange aRange;
    for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
    {
        const ScRangePair & rR = (*pRL)[i];
        if ( rR.GetRange(0).Contains( aAbs ) )
        {
            bInList = bValidName = true;
            aRange = rR.GetRange(1);
            if ( bColName )
            {
                aRange.aStart.SetCol( nCol );
                aRange.aEnd.SetCol( nCol );
            }
            else
            {
                aRange.aStart.SetRow( nRow );
                aRange.aEnd.SetRow( nRow );
            }
            break;  // for
        }
    }
    if ( !bInList && rDoc.GetDocOptions().IsLookUpColRowNames() )
    {   // automagically or created by copying and NamePos isn't in list
        ScRefCellValue aCell(rDoc, aAbs);
        bool bString = aCell.hasString();
        if (!bString && aCell.isEmpty())
            bString = true;     // empty cell is ok
        if ( bString )
        {   // corresponds with ScInterpreter::ScColRowNameAuto()
            bValidName = true;
            if ( bColName )
            {   // ColName
                SCROW nStartRow = nRow + 1;
                if ( nStartRow > rDoc.MaxRow() )
                    nStartRow = rDoc.MaxRow();
                SCROW nMaxRow = rDoc.MaxRow();
                if ( nMyCol == nCol )
                {   // formula cell in same column
                    if ( nMyRow == nStartRow )
                    {   // take remainder under name cell
                        nStartRow++;
                        if ( nStartRow > rDoc.MaxRow() )
                            nStartRow = rDoc.MaxRow();
                    }
                    else if ( nMyRow > nStartRow )
                    {   // from name cell down to formula cell
                        nMaxRow = nMyRow - 1;
                    }
                }
                for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
                {   // next defined ColNameRange below limits row
                    const ScRangePair & rR = (*pRL)[i];
                    const ScRange& rRange = rR.GetRange(1);
                    if ( rRange.aStart.Col() <= nCol && nCol <= rRange.aEnd.Col() )
                    {   // identical column range
                        SCROW nTmp = rRange.aStart.Row();
                        if ( nStartRow < nTmp && nTmp <= nMaxRow )
                            nMaxRow = nTmp - 1;
                    }
                }
                aRange.aStart.Set( nCol, nStartRow, nTab );
                aRange.aEnd.Set( nCol, nMaxRow, nTab );
            }
            else
            {   // RowName
                SCCOL nStartCol = nCol + 1;
                if ( nStartCol > rDoc.MaxCol() )
                    nStartCol = rDoc.MaxCol();
                SCCOL nMaxCol = rDoc.MaxCol();
                if ( nMyRow == nRow )
                {   // formula cell in same row
                    if ( nMyCol == nStartCol )
                    {   // take remainder right from name cell
                        nStartCol++;
                        if ( nStartCol > rDoc.MaxCol() )
                            nStartCol = rDoc.MaxCol();
                    }
                    else if ( nMyCol > nStartCol )
                    {   // from name cell right to formula cell
                        nMaxCol = nMyCol - 1;
                    }
                }
                for ( size_t i = 0, nPairs = pRL->size(); i < nPairs; ++i )
                {   // next defined RowNameRange to the right limits column
                    const ScRangePair & rR = (*pRL)[i];
                    const ScRange& rRange = rR.GetRange(1);
                    if ( rRange.aStart.Row() <= nRow && nRow <= rRange.aEnd.Row() )
                    {   // identical row range
                        SCCOL nTmp = rRange.aStart.Col();
                        if ( nStartCol < nTmp && nTmp <= nMaxCol )
                            nMaxCol = nTmp - 1;
                    }
                }
                aRange.aStart.Set( nStartCol, nRow, nTab );
                aRange.aEnd.Set( nMaxCol, nRow, nTab );
            }
        }
    }
    if ( bValidName )
    {
        // And now the magic to distinguish between a range and a single
        // cell thereof, which is picked position-dependent of the formula
        // cell. If a direct neighbor is a binary operator (ocAdd, ...) a
        // SingleRef matching the column/row of the formula cell is
        // generated. A ocColRowName or ocIntersect as a neighbor results
        // in a range. Special case: if label is valid for a single cell, a
        // position independent SingleRef is generated.
        bool bSingle = (aRange.aStart == aRange.aEnd);
        bool bFound;
        if ( bSingle )
            bFound = true;
        else
        {
            FormulaToken* p1 = maArrIterator.PeekPrevNoSpaces();
            FormulaToken* p2 = maArrIterator.PeekNextNoSpaces();
            // begin/end of a formula => single
            OpCode eOp1 = p1 ? p1->GetOpCode() : ocAdd;
            OpCode eOp2 = p2 ? p2->GetOpCode() : ocAdd;
            if ( eOp1 != ocColRowName && eOp1 != ocIntersect
                && eOp2 != ocColRowName && eOp2 != ocIntersect )
            {
                if (    (SC_OPCODE_START_BIN_OP <= eOp1 && eOp1 < SC_OPCODE_STOP_BIN_OP) ||
                        (SC_OPCODE_START_BIN_OP <= eOp2 && eOp2 < SC_OPCODE_STOP_BIN_OP))
                    bSingle = true;
            }
            if ( bSingle )
            {   // column and/or row must match range
                if ( bColName )
                {
                    bFound = (aRange.aStart.Row() <= nMyRow
                        && nMyRow <= aRange.aEnd.Row());
                    if ( bFound )
                        aRange.aStart.SetRow( nMyRow );
                }
                else
                {
                    bFound = (aRange.aStart.Col() <= nMyCol
                        && nMyCol <= aRange.aEnd.Col());
                    if ( bFound )
                        aRange.aStart.SetCol( nMyCol );
                }
            }
            else
                bFound = true;
        }
        if ( !bFound )
            SetError(FormulaError::NoRef);
        else if (mbJumpCommandReorder)
        {
            ScTokenArray* pNew = new ScTokenArray(rDoc);
            if ( bSingle )
            {
                ScSingleRefData aRefData;
                aRefData.InitAddress( aRange.aStart );
                if ( bColName )
                    aRefData.SetColRel( true );
                else
                    aRefData.SetRowRel( true );
                aRefData.SetAddress(rDoc.GetSheetLimits(), aRange.aStart, aPos);
                pNew->AddSingleReference( aRefData );
            }
            else
            {
                ScComplexRefData aRefData;
                aRefData.InitRange( aRange );
                if ( bColName )
                {
                    aRefData.Ref1.SetColRel( true );
                    aRefData.Ref2.SetColRel( true );
                }
                else
                {
                    aRefData.Ref1.SetRowRel( true );
                    aRefData.Ref2.SetRowRel( true );
                }
                aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
                if ( bInList )
                    pNew->AddDoubleReference( aRefData );
                else
                {   // automagically
                    pNew->Add( new ScDoubleRefToken( rDoc.GetSheetLimits(), aRefData, ocColRowNameAuto ) );
                }
            }
            PushTokenArray( pNew, true );
            return GetToken();
        }
    }
    else
        SetError(FormulaError::NoName);
    return true;
}
 
bool ScCompiler::HandleDbData()
{
    ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex(mpToken->GetIndex());
    if ( !pDBData )
        SetError(FormulaError::NoName);
    else if (mbJumpCommandReorder)
    {
        ScComplexRefData aRefData;
        aRefData.InitFlags();
        ScRange aRange;
        pDBData->GetArea(aRange);
        aRange.aEnd.SetTab(aRange.aStart.Tab());
        aRefData.SetRange(rDoc.GetSheetLimits(), aRange, aPos);
        ScTokenArray* pNew = new ScTokenArray(rDoc);
        pNew->AddDoubleReference( aRefData );
        PushTokenArray( pNew, true );
        return GetToken();
    }
    return true;
}
 
bool ScCompiler::GetTokenIfOpCode( OpCode eOp )
{
    const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
    if (p && p->GetOpCode() == eOp)
        return GetToken();
    return false;
}
 
 
/* Documentation on MS-Excel Table structured references:
 * https://support.office.com/en-us/article/Use-structured-references-in-Excel-table-formulas-75fb07d3-826a-449c-b76f-363057e3d16f
 * * as of Excel 2013
 * [MS-XLSX]: Formulas https://msdn.microsoft.com/en-us/library/dd906358.aspx
 * * look for structure-reference
 */
 
bool ScCompiler::HandleTableRef()
{
    ScTableRefToken* pTR = dynamic_cast<ScTableRefToken*>(mpToken.get());
    if (!pTR)
    {
        SetError(FormulaError::UnknownToken);
        return true;
    }
 
    ScDBData* pDBData = rDoc.GetDBCollection()->getNamedDBs().findByIndex( pTR->GetIndex());
    if ( !pDBData )
        SetError(FormulaError::NoName);
    else if (mbJumpCommandReorder)
    {
        ScRange aDBRange;
        pDBData->GetArea(aDBRange);
        aDBRange.aEnd.SetTab(aDBRange.aStart.Tab());
        ScRange aRange( aDBRange);
        FormulaError nError = FormulaError::NONE;
        bool bForwardToClose = false;
        ScTableRefToken::Item eItem = pTR->GetItem();
        switch (eItem)
        {
            case ScTableRefToken::TABLE:
                {
                    // The table name without items references the table data,
                    // without headers or totals.
                    if (pDBData->HasHeader())
                        aRange.aStart.IncRow();
                    if (pDBData->HasTotals())
                        aRange.aEnd.IncRow(-1);
                    if (aRange.aEnd.Row() < aRange.aStart.Row())
                        nError = FormulaError::NoRef;
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::ALL:
                {
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::HEADERS:
                {
                    if (pDBData->HasHeader())
                        aRange.aEnd.SetRow( aRange.aStart.Row());
                    else
                        nError = FormulaError::NoRef;
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::DATA:
                {
                    if (pDBData->HasHeader())
                        aRange.aStart.IncRow();
                }
                [[fallthrough]];
            case ScTableRefToken::HEADERS_DATA:
                {
                    if (pDBData->HasTotals())
                        aRange.aEnd.IncRow(-1);
                    if (aRange.aEnd.Row() < aRange.aStart.Row())
                        nError = FormulaError::NoRef;
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::TOTALS:
                {
                    if (pDBData->HasTotals())
                        aRange.aStart.SetRow( aRange.aEnd.Row());
                    else
                        nError = FormulaError::NoRef;
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::DATA_TOTALS:
                {
                    if (pDBData->HasHeader())
                        aRange.aStart.IncRow();
                    if (aRange.aEnd.Row() < aRange.aStart.Row())
                        nError = FormulaError::NoRef;
                    bForwardToClose = true;
                }
                break;
            case ScTableRefToken::THIS_ROW:
                {
                    if (aRange.aStart.Row() <= aPos.Row() && aPos.Row() <= aRange.aEnd.Row())
                    {
                        aRange.aStart.SetRow( aPos.Row());
                        aRange.aEnd.SetRow( aPos.Row());
                    }
                    else
                    {
                        nError = FormulaError::NoValue;
                        // For *some* relative row reference in named
                        // expressions' thisrow special handling below.
                        aRange.aEnd.SetRow( aRange.aStart.Row());
                    }
                    bForwardToClose = true;
                }
                break;
        }
        bool bColumnRange = false;
        bool bCol1Rel = false;
        bool bCol1RelName = false;
        int nLevel = 0;
        if (bForwardToClose && GetTokenIfOpCode( ocTableRefOpen))
        {
            ++nLevel;
            enum
            {
                sOpen,
                sItem,
                sClose,
                sSep,
                sLast,
                sStop
            } eState = sOpen;
            do
            {
                const formula::FormulaToken* p = maArrIterator.PeekNextNoSpaces();
                if (!p)
                    eState = sStop;
                else
                {
                    switch (p->GetOpCode())
                    {
                        case ocTableRefOpen:
                            eState = ((eState == sOpen || eState == sSep) ? sOpen : sStop);
                            if (++nLevel > 2)
                            {
                                SetError( FormulaError::Pair);
                                eState = sStop;
                            }
                            break;
                        case ocTableRefItemAll:
                        case ocTableRefItemHeaders:
                        case ocTableRefItemData:
                        case ocTableRefItemTotals:
                        case ocTableRefItemThisRow:
                            eState = ((eState == sOpen) ? sItem : sStop);
                            break;
                        case ocTableRefClose:
                            eState = ((eState == sItem || eState == sClose) ? sClose : sStop);
                            if (eState != sStop && --nLevel == 0)
                                eState = sLast;
                            break;
                        case ocSep:
                            eState = ((eState == sClose) ? sSep : sStop);
                            break;
                        case ocPush:
                            if (eState == sOpen && p->GetType() == svSingleRef)
                            {
                                bColumnRange = true;
                                bCol1Rel = p->GetSingleRef()->IsColRel();
                                bCol1RelName = p->GetSingleRef()->IsRelName();
                                eState = sLast;
                            }
                            else
                            {
                                eState = sStop;
                            }
                            break;
                        case ocBad:
                            eState = sLast;
                            if (nError == FormulaError::NONE)
                                nError = FormulaError::NoName;
                            break;
                        default:
                            eState = sStop;
                    }
                    if (eState != sStop)
                        GetToken();
                    if (eState == sLast)
                        eState = sStop;
                }
            } while (eState != sStop);
        }
        ScTokenArray* pNew = new ScTokenArray(rDoc);
        if (nError == FormulaError::NONE || nError == FormulaError::NoValue)
        {
            bool bCol2Rel = false;
            bool bCol2RelName = false;
            // The FormulaError::NoValue case generates a thisrow reference that can be
            // used to save named expressions in A1 syntax notation.
            if (bColumnRange)
            {
                // Limit range to specified columns.
                ScRange aColRange( ScAddress::INITIALIZE_INVALID );
                switch (mpToken->GetType())
                {
                    case svSingleRef:
                        {
                            aColRange.aStart = aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
                            if (    GetTokenIfOpCode( ocTableRefClose) && (nLevel--) &&
                                    GetTokenIfOpCode( ocRange) &&
                                    GetTokenIfOpCode( ocTableRefOpen) && (++nLevel) &&
                                    GetTokenIfOpCode( ocPush))
                            {
                                if (mpToken->GetType() != svSingleRef)
                                    aColRange = ScRange( ScAddress::INITIALIZE_INVALID);
                                else
                                {
                                    aColRange.aEnd = mpToken->GetSingleRef()->toAbs(rDoc, aPos);
                                    aColRange.PutInOrder();
                                    bCol2Rel = mpToken->GetSingleRef()->IsColRel();
                                    bCol2RelName = mpToken->GetSingleRef()->IsRelName();
                                }
                            }
                        }
                        break;
                    default:
                        ;   // nothing
                }
                // coverity[copy_paste_error : FALSE] - this is correct, aStart in both aDBRange uses
                if (aColRange.aStart.Row() != aDBRange.aStart.Row() || aColRange.aEnd.Row() != aDBRange.aStart.Row())
                    aRange = ScRange( ScAddress::INITIALIZE_INVALID);
                else
                {
                    aColRange.aEnd.SetRow( aRange.aEnd.Row());
                    aRange = aRange.Intersection( aColRange);
                }
            }
            if (aRange.IsValid())
            {
                if (aRange.aStart == aRange.aEnd)
                {
                    ScSingleRefData aRefData;
                    aRefData.InitFlags();
                    aRefData.SetColRel( bCol1Rel);
                    if (eItem == ScTableRefToken::THIS_ROW)
                    {
                        aRefData.SetRowRel( true);
                        if (!bCol1RelName)
                            bCol1RelName = pArr->IsFromRangeName();
                    }
                    aRefData.SetRelName( bCol1RelName);
                    aRefData.SetFlag3D( true);
                    if (nError != FormulaError::NONE)
                    {
                        aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aRange.aStart);
                        pTR->SetAreaRefRPN( new ScSingleRefToken(rDoc.GetSheetLimits(), aRefData));   // set reference at TableRef
                        pNew->Add( new FormulaErrorToken( nError));             // set error in RPN
                    }
                    else
                    {
                        aRefData.SetAddress( rDoc.GetSheetLimits(), aRange.aStart, aPos);
                        pTR->SetAreaRefRPN( pNew->AddSingleReference( aRefData));
                    }
                }
                else
                {
                    ScComplexRefData aRefData;
                    aRefData.InitFlags();
                    aRefData.Ref1.SetColRel( bCol1Rel);
                    aRefData.Ref2.SetColRel( bCol2Rel);
                    bool bRelName = bCol1RelName || bCol2RelName;
                    if (eItem == ScTableRefToken::THIS_ROW)
                    {
                        aRefData.Ref1.SetRowRel( true);
                        aRefData.Ref2.SetRowRel( true);
                        if (!bRelName)
                            bRelName = pArr->IsFromRangeName();
                    }
                    aRefData.Ref1.SetRelName( bRelName);
                    aRefData.Ref2.SetRelName( bRelName);
                    aRefData.Ref1.SetFlag3D( true);
                    if (nError != FormulaError::NONE)
                    {
                        aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aRange.aStart);
                        pTR->SetAreaRefRPN( new ScDoubleRefToken(rDoc.GetSheetLimits(), aRefData));   // set reference at TableRef
                        pNew->Add( new FormulaErrorToken( nError));             // set error in RPN
                    }
                    else
                    {
                        aRefData.SetRange( rDoc.GetSheetLimits(), aRange, aPos);
                        pTR->SetAreaRefRPN( pNew->AddDoubleReference( aRefData));
                    }
                }
            }
            else
            {
                pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( FormulaError::NoRef)));
            }
        }
        else
        {
            pTR->SetAreaRefRPN( pNew->Add( new FormulaErrorToken( nError)));
        }
        while (nLevel-- > 0)
        {
            if (!GetTokenIfOpCode( ocTableRefClose))
                SetError( FormulaError::Pair);
        }
        PushTokenArray( pNew, true );
        return GetToken();
    }
    return true;
}
 
formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaToken* pToken, sal_uInt16 nParam ) const
{
    return ScParameterClassification::GetParameterType( pToken, nParam);
}
 
bool ScCompiler::ParameterMayBeImplicitIntersection(const FormulaToken* token, int parameter)
{
    formula::ParamClass param = ScParameterClassification::GetParameterType( token, parameter );
    return param == Value || param == Array;
}
 
bool ScCompiler::SkipImplicitIntersectionOptimization(const FormulaToken* token) const
{
    if (mbMatrixFlag)
        return true;
    formula::ParamClass paramClass = token->GetInForceArray();
    if (paramClass == formula::ForceArray
        || paramClass == formula::ReferenceOrForceArray
        || paramClass == formula::SuppressedReferenceOrForceArray
        || paramClass == formula::ReferenceOrRefArray)
    {
        return true;
    }
    formula::ParamClass returnType = ScParameterClassification::GetParameterType( token, SAL_MAX_UINT16 );
    return returnType == formula::Reference;
}
 
void ScCompiler::HandleIIOpCode(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
{
    if (!mbComputeII)
        return;
#ifdef DBG_UTIL
    if(!HandleIIOpCodeInternal(token, pppToken, nNumParams))
        mUnhandledPossibleImplicitIntersectionsOpCodes.insert(token->GetOpCode());
#else
    HandleIIOpCodeInternal(token, pppToken, nNumParams);
#endif
}
 
// return true if opcode is handled
bool ScCompiler::HandleIIOpCodeInternal(FormulaToken* token, FormulaToken*** pppToken, sal_uInt8 nNumParams)
{
    if (nNumParams > 0 && *pppToken[0] == nullptr)
        return false; // Bad expression (see the dummy creation in FormulaCompiler::CompileTokenArray())
 
    const OpCode nOpCode = token->GetOpCode();
 
    if (nOpCode == ocPush)
    {
        if(token->GetType() == svDoubleRef)
            mUnhandledPossibleImplicitIntersections.insert( token );
        return true;
    }
    else if (nOpCode == ocSumIf || nOpCode == ocAverageIf)
    {
        if (nNumParams != 3)
            return false;
 
        if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2]))
            return false;
 
        if ((*pppToken[0])->GetType() != svDoubleRef)
            return false;
 
        const StackVar eSumRangeType = (*pppToken[2])->GetType();
 
        if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef )
            return false;
 
        const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef();
 
        ScComplexRefData aSumRange;
        if (eSumRangeType == svSingleRef)
        {
            aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef();
            aSumRange.Ref2 = aSumRange.Ref1;
        }
        else
            aSumRange = *(*pppToken[2])->GetDoubleRef();
 
        CorrectSumRange(rBaseRange, aSumRange, pppToken[2]);
        // TODO mark parameters as handled
        return true;
    }
    else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR)
    {
        if (nNumParams != 1)
            return false;
 
        if( !ParameterMayBeImplicitIntersection( token, 0 ))
            return false;
        if (SkipImplicitIntersectionOptimization(token))
            return false;
 
        if ((*pppToken[0])->GetType() != svDoubleRef)
            return false;
 
        mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
        return true;
    }
    else if ((nOpCode >= SC_OPCODE_START_BIN_OP && nOpCode < SC_OPCODE_STOP_BIN_OP
                && nOpCode != ocAnd && nOpCode != ocOr)
              || nOpCode == ocRound || nOpCode == ocRoundUp || nOpCode == ocRoundDown)
    {
        if (nNumParams != 2)
            return false;
 
        if( !ParameterMayBeImplicitIntersection( token, 0 ) || !ParameterMayBeImplicitIntersection( token, 1 ))
            return false;
        if (SkipImplicitIntersectionOptimization(token))
            return false;
 
        // Convert only if the other parameter is not a matrix (which would force the result to be a matrix).
        if ((*pppToken[0])->GetType() == svDoubleRef && (*pppToken[1])->GetType() != svMatrix)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
        if ((*pppToken[1])->GetType() == svDoubleRef && (*pppToken[0])->GetType() != svMatrix)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[1], token );
        return true;
    }
    else if ((nOpCode >= SC_OPCODE_START_UN_OP && nOpCode < SC_OPCODE_STOP_UN_OP)
              || nOpCode == ocPercentSign)
    {
        if (nNumParams != 1)
            return false;
 
        if( !ParameterMayBeImplicitIntersection( token, 0 ))
            return false;
        if (SkipImplicitIntersectionOptimization(token))
            return false;
 
        if ((*pppToken[0])->GetType() == svDoubleRef)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
        return true;
    }
    else if (nOpCode == ocVLookup)
    {
        if (nNumParams != 3 && nNumParams != 4)
            return false;
 
        if (SkipImplicitIntersectionOptimization(token))
            return false;
 
        assert( ParameterMayBeImplicitIntersection( token, 0 ));
        assert( !ParameterMayBeImplicitIntersection( token, 1 ));
        assert( ParameterMayBeImplicitIntersection( token, 2 ));
        assert( ParameterMayBeImplicitIntersection( token, 3 ));
        if ((*pppToken[2])->GetType() == svDoubleRef)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[2], token );
        if ((*pppToken[0])->GetType() == svDoubleRef)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[0], token );
        if (nNumParams == 4 && (*pppToken[3])->GetType() == svDoubleRef)
            mPendingImplicitIntersectionOptimizations.emplace_back( pppToken[3], token );
        // a range for the second parameters is not an implicit intersection
        mUnhandledPossibleImplicitIntersections.erase( *pppToken[ 1 ] );
        return true;
    }
    else
    {
        bool possibleII = false;
        for( int i = 0; i < nNumParams; ++i )
        {
            if( ParameterMayBeImplicitIntersection( token, i )
                && (*pppToken[i])->GetType() == svDoubleRef)
            {
                possibleII = true;
                break;
            }
        }
        if( !possibleII )
        {
            // all parameters have been handled, they are not implicit intersections
            for( int i = 0; i < nNumParams; ++i )
                mUnhandledPossibleImplicitIntersections.erase( *pppToken[ i ] );
            return true;
        }
    }
 
    return false;
}
 
void ScCompiler::PostProcessCode()
{
    for( const PendingImplicitIntersectionOptimization& item : mPendingImplicitIntersectionOptimizations )
    {
        if( *item.parameterLocation != item.parameter ) // the parameter has been changed somehow
            continue;
        if( item.parameterLocation >= pCode ) // the location is not inside the code (pCode points after the end)
            continue;
        // E.g. "SUMPRODUCT(I5:I6+1)" shouldn't do implicit intersection.
        if( item.operation->IsInForceArray())
            continue;
        ReplaceDoubleRefII( item.parameterLocation );
    }
    mPendingImplicitIntersectionOptimizations.clear();
}
 
void ScCompiler::AnnotateOperands()
{
    AnnotateTrimOnDoubleRefs();
}
 
void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok)
{
    const ScComplexRefData* pRange = (*ppDoubleRefTok)->GetDoubleRef();
    if (!pRange)
        return;
 
    const ScComplexRefData& rRange = *pRange;
 
    // Can't do optimization reliably in this case (when row references are absolute).
    // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100.
    // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then
    // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104)
    // instead of the #VALUE! errors we would expect. We need to know the formula-group length to
    // fix this, but that is unknown at this stage, so skip such cases.
    if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel())
        return;
 
    ScRange aAbsRange = rRange.toAbs(rDoc, aPos);
    if (aAbsRange.aStart == aAbsRange.aEnd)
        return; // Nothing to do (trivial case).
 
    ScAddress aAddr;
 
    if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos))
        return;
 
    ScSingleRefData aSingleRef;
    aSingleRef.InitFlags();
    aSingleRef.SetColRel(rRange.Ref1.IsColRel());
    aSingleRef.SetRowRel(true);
    aSingleRef.SetTabRel(rRange.Ref1.IsTabRel());
    aSingleRef.SetAddress(rDoc.GetSheetLimits(), aAddr, aPos);
 
    // Replace the original doubleref token with computed singleref token
    FormulaToken* pNewSingleRefTok = new ScSingleRefToken(rDoc.GetSheetLimits(), aSingleRef);
    (*ppDoubleRefTok)->DecRef();
    *ppDoubleRefTok = pNewSingleRefTok;
    pNewSingleRefTok->IncRef();
}
 
bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos)
{
    assert(rRange.aStart != rRange.aEnd);
 
    bool bOk = false;
    SCCOL nMyCol = rFormulaPos.Col();
    SCROW nMyRow = rFormulaPos.Row();
    SCTAB nMyTab = rFormulaPos.Tab();
    SCCOL nCol = 0;
    SCROW nRow = 0;
    SCTAB nTab;
    nTab = rRange.aStart.Tab();
    if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
    {
        nRow = rRange.aStart.Row();
        if ( nRow == rRange.aEnd.Row() )
        {
            bOk = true;
            nCol = nMyCol;
        }
        else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
                && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
        {
            bOk = true;
            nCol = nMyCol;
            nRow = nMyRow;
        }
    }
    else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() )
    {
        nCol = rRange.aStart.Col();
        if ( nCol == rRange.aEnd.Col() )
        {
            bOk = true;
            nRow = nMyRow;
        }
        else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab()
                && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() )
        {
            bOk = true;
            nCol = nMyCol;
            nRow = nMyRow;
        }
    }
    if ( bOk )
    {
        if ( nTab == rRange.aEnd.Tab() )
            ;   // all done
        else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() )
            nTab = nMyTab;
        else
            bOk = false;
        if ( bOk )
            rAdr.Set( nCol, nRow, nTab );
    }
 
    return bOk;
}
 
static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta)
{
    rXDelta = rRange.aEnd.Col() - rRange.aStart.Col();
    rYDelta = rRange.aEnd.Row() - rRange.aStart.Row();
}
 
bool ScCompiler::AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange)
{
    ScRange aAbs = rSumRange.toAbs(rDoc, aPos);
 
    // Current sum-range end col/row
    SCCOL nEndCol = aAbs.aEnd.Col();
    SCROW nEndRow = aAbs.aEnd.Row();
 
    // Current behaviour is, we will get a #NAME? for the below case, so bail out.
    // Note that sum-range's End[Col,Row] are same as Start[Col,Row] if the original formula
    // has a single-ref as the sum-range.
    if (!rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow))
        return false;
 
    SCCOL nXDeltaSum = 0;
    SCROW nYDeltaSum = 0;
 
    lcl_GetColRowDeltas(aAbs, nXDeltaSum, nYDeltaSum);
 
    aAbs = rBaseRange.toAbs(rDoc, aPos);
    SCCOL nXDelta = 0;
    SCROW nYDelta = 0;
 
    lcl_GetColRowDeltas(aAbs, nXDelta, nYDelta);
 
    if (nXDelta == nXDeltaSum &&
        nYDelta == nYDeltaSum)
        return false;  // shapes of base-range match current sum-range
 
    // Try to make the sum-range to take the same shape as base-range,
    // by adjusting Ref2 member of rSumRange if the resultant sum-range don't
    // go out-of-bounds.
 
    SCCOL nXInc = nXDelta - nXDeltaSum;
    SCROW nYInc = nYDelta - nYDeltaSum;
 
    // Don't let a valid End[Col,Row] go beyond (rDoc.MaxCol(),rDoc.MaxRow()) to match
    // what happens in ScInterpreter::IterateParametersIf(), but there it also shrinks
    // the base-range by the (out-of-bound)amount clipped off the sum-range.
    // TODO: Probably we can optimize (from threading perspective) rBaseRange
    //       by shrinking it here correspondingly (?)
    if (nEndCol + nXInc > rDoc.MaxCol())
        nXInc = rDoc.MaxCol() - nEndCol;
    if (nEndRow + nYInc > rDoc.MaxRow())
        nYInc = rDoc.MaxRow() - nEndRow;
 
    rSumRange.Ref2.IncCol(nXInc);
    rSumRange.Ref2.IncRow(nYInc);
 
    return true;
}
 
void ScCompiler::CorrectSumRange(const ScComplexRefData& rBaseRange,
                                 ScComplexRefData& rSumRange,
                                 FormulaToken** ppSumRangeToken)
{
    if (!AdjustSumRangeShape(rBaseRange, rSumRange))
        return;
 
    // Replace sum-range token
    FormulaToken* pNewSumRangeTok = new ScDoubleRefToken(rDoc.GetSheetLimits(), rSumRange);
    (*ppSumRangeToken)->DecRef();
    *ppSumRangeToken = pNewSumRangeTok;
    pNewSumRangeTok->IncRef();
}
 
void ScCompiler::AnnotateTrimOnDoubleRefs()
{
    if (!pCode || !(*(pCode - 1)))
        return;
 
    // OpCode of the "root" operator (which is already in RPN array).
    OpCode eOpCode = (*(pCode - 1))->GetOpCode();
    // Param number of the "root" operator (which is already in RPN array).
    sal_uInt8 nRootParam = (*(pCode - 1))->GetByte();
    // eOpCode can be some operator which does not change with operands with or contains zero values.
    if (eOpCode == ocSum)
    {
        FormulaToken** ppTok = pCode - 2; // exclude the root operator.
        // The following loop runs till a "pattern" is found or there is a mismatch
        // and marks the push DoubleRef arguments as trimmable when there is a match.
        // The pattern is
        // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
        // such that one of the operands of ocEqual is a double-ref.
        // Examples of formula that matches this are:
        //   SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)
        //   SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3)
        //   SUM(IF(E:E=16,F:F)*$H$1*100)
        bool bTillClose = true;
        bool bCloseTillIf = false;
        sal_Int16 nToksTillIf = 0;
        constexpr sal_Int16 MAXDIST_IF = 15;
        while (*ppTok)
        {
            FormulaToken* pTok = *ppTok;
            OpCode eCurrOp = pTok->GetOpCode();
            ++nToksTillIf;
 
            // TODO : Is there a better way to handle this ?
            // ocIf is too far off from the sum opcode.
            if (nToksTillIf > MAXDIST_IF)
                return;
 
            switch (eCurrOp)
            {
                case ocDiv:
                case ocMul:
                    if (!bTillClose)
                        return;
                    break;
                case ocPush:
 
                    break;
                case ocClose:
                    if (bTillClose)
                    {
                        bTillClose = false;
                        bCloseTillIf = true;
                    }
                    else
                        return;
                    break;
                case ocIf:
                    {
                        if (!bCloseTillIf)
                            return;
 
                        if (!pTok->IsInForceArray())
                            return;
 
                        const short nJumpCount = pTok->GetJump()[0];
                        if (nJumpCount != 2) // Should have THEN but no ELSE.
                            return;
 
                        OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
                        if (eCompOp != ocEqual)
                            return;
 
                        FormulaToken* pLHS = *(ppTok - 2);
                        FormulaToken* pRHS = *(ppTok - 3);
                        if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) ||
                            ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef))
                        {
                            if (pLHS->GetType() == svDoubleRef)
                                pLHS->GetDoubleRef()->SetTrimToData(true);
                            else
                                pRHS->GetDoubleRef()->SetTrimToData(true);
                            return;
                        }
                    }
                    break;
                default:
                    return;
            }
            --ppTok;
        }
    }
    else if (eOpCode == ocSumProduct)
    {
        FormulaToken** ppTok = pCode - 2; // exclude the root operator.
        // The following loop runs till a "pattern" is found or there is a mismatch
        // and marks the push DoubleRef arguments as trimmable when there is a match.
        // The pattern is
        // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>)
        // such that one of the operands of ocEqual is a double-ref.
        // Examples of formula that matches this are:
        //   SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G))
        // Also in case of DoubleRef arguments around other Binary operators can be trimmable inside one parameter
        // of the root operator:
        //   SUMPRODUCT(($D:$D>M47:M47)*($D:$D<M48:M48)*($I:$I=N$41))
        bool bTillClose = true;
        bool bCloseTillIf = false;
        sal_Int16 nToksTillIf = 0;
        constexpr sal_Int16 MAXDIST_IF = 15;
        while (*ppTok)
        {
            FormulaToken* pTok = *ppTok;
            OpCode eCurrOp = pTok->GetOpCode();
            ++nToksTillIf;
 
            // TODO : Is there a better way to handle this ?
            // ocIf is too far off from the sum opcode.
            if (nToksTillIf > MAXDIST_IF)
                return;
 
            switch (eCurrOp)
            {
                case ocDiv:
                case ocMul:
                    {
                        if (!pTok->IsInForceArray())
                            break;
                        FormulaToken* pLHS = *(ppTok - 1);
                        FormulaToken* pRHS = *(ppTok - 2);
                        if (pLHS && pRHS)
                        {
                            StackVar lhsType = pLHS->GetType();
                            StackVar rhsType = pRHS->GetType();
                            if (lhsType == svDoubleRef && rhsType == svDoubleRef)
                            {
                                pLHS->GetDoubleRef()->SetTrimToData(true);
                                pRHS->GetDoubleRef()->SetTrimToData(true);
                            }
                        }
                    }
                    break;
                case ocEqual:
                case ocAdd:
                case ocSub:
                case ocAmpersand:
                case ocPow:
                case ocNotEqual:
                case ocLess:
                case ocGreater:
                case ocLessEqual:
                case ocGreaterEqual:
                case ocAnd:
                case ocOr:
                case ocXor:
                case ocIntersect:
                    {
                        // tdf#160616: Double refs with these operators only
                        // trimmable in case of one parameter
                        if (!pTok->IsInForceArray() || nRootParam > 1)
                            break;
                        FormulaToken* pLHS = *(ppTok - 1);
                        FormulaToken* pRHS = *(ppTok - 2);
                        if (pLHS && pRHS)
                        {
                            StackVar lhsType = pLHS->GetType();
                            StackVar rhsType = pRHS->GetType();
                            if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDoubleRef))
                            {
                                pLHS->GetDoubleRef()->SetTrimToData(true);
                            }
                            if (rhsType == svDoubleRef && (lhsType == svSingleRef || lhsType == svDoubleRef))
                            {
                                pRHS->GetDoubleRef()->SetTrimToData(true);
                            }
                        }
                    }
                    break;
                case ocPush:
                    break;
                case ocClose:
                    if (bTillClose)
                    {
                        bTillClose = false;
                        bCloseTillIf = true;
                    }
                    else
                        return;
                    break;
                case ocIf:
                    {
                        if (!bCloseTillIf)
                            return;
 
                        if (!pTok->IsInForceArray())
                            return;
 
                        const short nJumpCount = pTok->GetJump()[0];
                        if (nJumpCount != 2) // Should have THEN but no ELSE.
                            return;
 
                        OpCode eCompOp = (*(ppTok - 1))->GetOpCode();
                        if (eCompOp != ocEqual)
                            return;
 
                        FormulaToken* pLHS = *(ppTok - 2);
                        FormulaToken* pRHS = *(ppTok - 3);
                        StackVar lhsType = pLHS->GetType();
                        StackVar rhsType = pRHS->GetType();
                        if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble))
                        {
                            pLHS->GetDoubleRef()->SetTrimToData(true);
                        }
                        if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef)
                        {
                            pRHS->GetDoubleRef()->SetTrimToData(true);
                        }
                        return;
                    }
                    break;
                default:
                    return;
            }
            --ppTok;
        }
    }
}
 
/* 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 '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 '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 '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 '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 '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 '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 '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 '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 '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 '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 'append' is required to be utilized.

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

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

V530 The return value of function 'insert' 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 '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 'append' is required to be utilized.

V773 The function was exited without releasing the 'pFunctionStack' pointer. A memory leak is possible.

V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: cSymbol, nSrcPos.

V1048 The 'mnCurrentSheetTab' variable was assigned the same value.

V1048 The 'm_aLambda.nBracketPos' variable was assigned the same value.