summaryrefslogtreecommitdiffstats
path: root/formula/source/core/api
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /formula/source/core/api
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'formula/source/core/api')
-rw-r--r--formula/source/core/api/FormulaCompiler.cxx3105
-rw-r--r--formula/source/core/api/FormulaOpCodeMapperObj.cxx104
-rw-r--r--formula/source/core/api/grammar.cxx87
-rw-r--r--formula/source/core/api/token.cxx2032
-rw-r--r--formula/source/core/api/vectortoken.cxx109
5 files changed, 5437 insertions, 0 deletions
diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx
new file mode 100644
index 000000000..b39e618a5
--- /dev/null
+++ b/formula/source/core/api/FormulaCompiler.cxx
@@ -0,0 +1,3105 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+#include <sal/macros.h>
+#include <sal/log.hxx>
+#include <rtl/math.hxx>
+#include <formula/FormulaCompiler.hxx>
+#include <formula/errorcodes.hxx>
+#include <formula/token.hxx>
+#include <formula/tokenarray.hxx>
+#include <o3tl/string_view.hxx>
+#include <core_resource.hxx>
+#include <core_resource.hrc>
+
+#include <osl/mutex.hxx>
+
+#include <svl/zforlist.hxx>
+#include <unotools/charclass.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <comphelper/processfactory.hxx>
+#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp>
+#include <com/sun/star/sheet/FormulaMapGroup.hpp>
+#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp>
+#include <algorithm>
+
+namespace formula
+{
+ using namespace ::com::sun::star;
+
+ static const char* pInternal[2] = { "TTT", "__DEBUG_VAR" };
+
+namespace {
+
+class FormulaCompilerRecursionGuard
+{
+ private:
+ short& rRecursion;
+ public:
+ explicit FormulaCompilerRecursionGuard( short& rRec )
+ : rRecursion( rRec ) { ++rRecursion; }
+ ~FormulaCompilerRecursionGuard() { --rRecursion; }
+};
+
+SvNumFormatType lcl_GetRetFormat( OpCode eOpCode )
+{
+ switch (eOpCode)
+ {
+ case ocEqual:
+ case ocNotEqual:
+ case ocLess:
+ case ocGreater:
+ case ocLessEqual:
+ case ocGreaterEqual:
+ case ocAnd:
+ case ocOr:
+ case ocXor:
+ case ocNot:
+ case ocTrue:
+ case ocFalse:
+ case ocIsEmpty:
+ case ocIsString:
+ case ocIsNonString:
+ case ocIsLogical:
+ case ocIsRef:
+ case ocIsValue:
+ case ocIsFormula:
+ case ocIsNA:
+ case ocIsErr:
+ case ocIsError:
+ case ocIsEven:
+ case ocIsOdd:
+ case ocExact:
+ return SvNumFormatType::LOGICAL;
+ case ocGetActDate:
+ case ocGetDate:
+ case ocEasterSunday :
+ return SvNumFormatType::DATE;
+ case ocGetActTime:
+ return SvNumFormatType::DATETIME;
+ case ocGetTime:
+ return SvNumFormatType::TIME;
+ case ocNPV:
+ case ocPV:
+ case ocSYD:
+ case ocDDB:
+ case ocDB:
+ case ocVBD:
+ case ocSLN:
+ case ocPMT:
+ case ocFV:
+ case ocIpmt:
+ case ocPpmt:
+ case ocCumIpmt:
+ case ocCumPrinc:
+ return SvNumFormatType::CURRENCY;
+ case ocRate:
+ case ocIRR:
+ case ocMIRR:
+ case ocRRI:
+ case ocEffect:
+ case ocNominal:
+ case ocPercentSign:
+ return SvNumFormatType::PERCENT;
+ default:
+ return SvNumFormatType::NUMBER;
+ }
+}
+
+void lclPushOpCodeMapEntry( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
+ const OUString* pTable, sal_uInt16 nOpCode )
+{
+ sheet::FormulaOpCodeMapEntry aEntry;
+ aEntry.Token.OpCode = nOpCode;
+ aEntry.Name = pTable[nOpCode];
+ rVec.push_back( aEntry);
+}
+
+void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
+ const OUString* pTable, sal_uInt16 nOpCodeBeg, sal_uInt16 nOpCodeEnd )
+{
+ for (sal_uInt16 nOpCode = nOpCodeBeg; nOpCode < nOpCodeEnd; ++nOpCode)
+ lclPushOpCodeMapEntry( rVec, pTable, nOpCode );
+}
+
+void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
+ const OUString* pTable, const sal_uInt16* pnOpCodes, size_t nCount )
+{
+ for (const sal_uInt16* pnEnd = pnOpCodes + nCount; pnOpCodes < pnEnd; ++pnOpCodes)
+ lclPushOpCodeMapEntry( rVec, pTable, *pnOpCodes );
+}
+
+CharClass* createCharClassIfNonEnglishUI()
+{
+ const LanguageTag& rLanguageTag( Application::GetSettings().GetUILanguageTag());
+ if (rLanguageTag.getLanguage() == "en")
+ return nullptr;
+ return new CharClass( ::comphelper::getProcessComponentContext(), rLanguageTag);
+}
+
+class OpCodeList
+{
+public:
+
+ OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&,
+ FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE );
+ OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&,
+ FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE );
+
+private:
+ bool getOpCodeString( OUString& rStr, sal_uInt16 nOp );
+ void putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp, const CharClass* pCharClass );
+
+private:
+ FormulaCompiler::SeparatorType meSepType;
+ const std::pair<const char*, int>* mpSymbols1;
+ const std::pair<TranslateId, int>* mpSymbols2;
+};
+
+OpCodeList::OpCodeList(const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap,
+ FormulaCompiler::SeparatorType eSepType)
+ : meSepType(eSepType)
+ , mpSymbols1(pSymbols)
+ , mpSymbols2(nullptr)
+{
+ std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
+ const CharClass* pCharClass = xCharClass.get();
+ if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE)
+ {
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ putDefaultOpCode( xMap, i, pCharClass);
+ }
+ }
+ else
+ {
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ OUString aOpStr;
+ if ( getOpCodeString( aOpStr, i) )
+ xMap->putOpCode( aOpStr, OpCode(i), pCharClass);
+ else
+ putDefaultOpCode( xMap, i, pCharClass);
+ }
+ }
+}
+
+OpCodeList::OpCodeList(const std::pair<TranslateId, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap,
+ FormulaCompiler::SeparatorType eSepType)
+ : meSepType(eSepType)
+ , mpSymbols1(nullptr)
+ , mpSymbols2(pSymbols)
+{
+ std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
+ const CharClass* pCharClass = xCharClass.get();
+ if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE)
+ {
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ putDefaultOpCode( xMap, i, pCharClass);
+ }
+ }
+ else
+ {
+ for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
+ {
+ OUString aOpStr;
+ if ( getOpCodeString( aOpStr, i) )
+ xMap->putOpCode( aOpStr, OpCode(i), pCharClass);
+ else
+ putDefaultOpCode( xMap, i, pCharClass);
+ }
+ }
+}
+
+bool OpCodeList::getOpCodeString( OUString& rStr, sal_uInt16 nOp )
+{
+ switch (nOp)
+ {
+ case SC_OPCODE_SEP:
+ {
+ if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
+ {
+ rStr = ";";
+ return true;
+ }
+ }
+ break;
+ case SC_OPCODE_ARRAY_COL_SEP:
+ {
+ if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
+ {
+ rStr = ";";
+ return true;
+ }
+ }
+ break;
+ case SC_OPCODE_ARRAY_ROW_SEP:
+ {
+ if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
+ {
+ rStr = "|";
+ return true;
+ }
+ }
+ break;
+ }
+
+ return false;
+}
+
+void OpCodeList::putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp,
+ const CharClass* pCharClass )
+{
+ OUString sKey;
+ if (mpSymbols1)
+ {
+ const char* pKey = nullptr;
+ for (const std::pair<const char*, int>* pSymbol = mpSymbols1; pSymbol->first; ++pSymbol)
+ {
+ if (nOp == pSymbol->second)
+ {
+ pKey = pSymbol->first;
+ break;
+ }
+ }
+ if (!pKey)
+ return;
+ sKey = OUString::createFromAscii(pKey);
+ }
+ else if (mpSymbols2)
+ {
+ TranslateId pKey;
+ for (const std::pair<TranslateId, int>* pSymbol = mpSymbols2; pSymbol->first; ++pSymbol)
+ {
+ if (nOp == pSymbol->second)
+ {
+ pKey = pSymbol->first;
+ break;
+ }
+ }
+ if (!pKey)
+ return;
+ sKey = ForResId(pKey);
+ }
+ xMap->putOpCode(sKey, OpCode(nOp), pCharClass);
+}
+
+// static
+const sal_Unicode* lcl_UnicodeStrChr( const sal_Unicode* pStr, sal_Unicode c )
+{
+ if ( !pStr )
+ return nullptr;
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return pStr;
+ pStr++;
+ }
+ return nullptr;
+}
+
+struct OpCodeMapData
+{
+ FormulaCompiler::NonConstOpCodeMapPtr mxSymbolMap;
+ osl::Mutex maMtx;
+};
+
+
+bool isPotentialRangeLeftOp( OpCode eOp )
+{
+ switch (eOp)
+ {
+ case ocClose:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isRangeResultFunction( OpCode eOp )
+{
+ switch (eOp)
+ {
+ case ocIndirect:
+ case ocOffset:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool isRangeResultOpCode( OpCode eOp )
+{
+ switch (eOp)
+ {
+ case ocRange:
+ case ocUnion:
+ case ocIntersect:
+ case ocIndirect:
+ case ocOffset:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ @param pToken
+ MUST be a valid token, caller has to ensure.
+
+ @param bRight
+ If bRPN==false, bRight==false means opcodes for left side are
+ checked, bRight==true means opcodes for right side. If bRPN==true
+ it doesn't matter except for the ocSep converted to ocUnion case.
+ */
+bool isPotentialRangeType( FormulaToken const * pToken, bool bRPN, bool bRight )
+{
+ switch (pToken->GetType())
+ {
+ case svByte: // could be range result, but only a few
+ if (bRPN)
+ return isRangeResultOpCode( pToken->GetOpCode());
+ else if (bRight)
+ return isRangeResultFunction( pToken->GetOpCode());
+ else
+ return isPotentialRangeLeftOp( pToken->GetOpCode());
+ case svSingleRef:
+ case svDoubleRef:
+ case svIndex: // could be range
+ //case svRefList: // um..what?
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ case svExternalName: // could be range
+ return true;
+ case svSep:
+ // A special case if a previous ocSep was converted to ocUnion it
+ // stays svSep instead of svByte.
+ return bRPN && !bRight && pToken->GetOpCode() == ocUnion;
+ default:
+ // Separators are not part of RPN and right opcodes need to be
+ // other StackVar types or functions and thus svByte.
+ return !bRPN && !bRight && isPotentialRangeLeftOp( pToken->GetOpCode());
+ }
+}
+
+bool isIntersectable( FormulaToken** pCode1, FormulaToken** pCode2 )
+{
+ FormulaToken* pToken1 = *pCode1;
+ FormulaToken* pToken2 = *pCode2;
+ if (pToken1 && pToken2)
+ return isPotentialRangeType( pToken1, true, false) && isPotentialRangeType( pToken2, true, true);
+ return false;
+}
+
+bool isAdjacentRpnEnd( sal_uInt16 nPC,
+ FormulaToken const * const * const pCode,
+ FormulaToken const * const * const pCode1,
+ FormulaToken const * const * const pCode2 )
+{
+ return nPC >= 2 && pCode1 && pCode2 &&
+ (pCode2 - pCode1 == 1) && (pCode - pCode2 == 1) &&
+ (*pCode1 != nullptr) && (*pCode2 != nullptr);
+}
+
+bool isAdjacentOrGapRpnEnd( sal_uInt16 nPC,
+ FormulaToken const * const * const pCode,
+ FormulaToken const * const * const pCode1,
+ FormulaToken const * const * const pCode2 )
+{
+ return nPC >= 2 && pCode1 && pCode2 &&
+ (pCode2 > pCode1) && (pCode - pCode2 == 1) &&
+ (*pCode1 != nullptr) && (*pCode2 != nullptr);
+}
+
+
+} // namespace
+
+
+void FormulaCompiler::OpCodeMap::putExternal( const OUString & rSymbol, const OUString & rAddIn )
+{
+ // Different symbols may map to the same AddIn, but the same AddIn may not
+ // map to different symbols, the first pair wins. Same symbol of course may
+ // not map to different AddIns, again the first pair wins and also the
+ // AddIn->symbol mapping is not inserted in other cases.
+ bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
+ SAL_WARN_IF( !bOk, "formula.core", "OpCodeMap::putExternal: symbol not inserted, " << rSymbol << " -> " << rAddIn);
+ if (bOk)
+ {
+ bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
+ // Failed insertion of the AddIn is ok for different symbols mapping to
+ // the same AddIn. Make this INFO only.
+ SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternal: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
+ }
+}
+
+void FormulaCompiler::OpCodeMap::putExternalSoftly( const OUString & rSymbol, const OUString & rAddIn )
+{
+ // Same as putExternal() but no warning, instead info whether inserted or not.
+ bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
+ SAL_INFO( "formula.core", "OpCodeMap::putExternalSoftly: symbol " << (bOk ? "" : "not ") << "inserted, " << rSymbol << " -> " << rAddIn);
+ if (bOk)
+ {
+ bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
+ SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternalSoftly: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
+ }
+}
+
+uno::Sequence< sheet::FormulaToken > FormulaCompiler::OpCodeMap::createSequenceOfFormulaTokens(
+ const FormulaCompiler& rCompiler, const uno::Sequence< OUString >& rNames ) const
+{
+ const sal_Int32 nLen = rNames.getLength();
+ uno::Sequence< sheet::FormulaToken > aTokens( nLen);
+ sheet::FormulaToken* pToken = aTokens.getArray();
+ OUString const * pName = rNames.getConstArray();
+ OUString const * const pStop = pName + nLen;
+ for ( ; pName < pStop; ++pName, ++pToken)
+ {
+ OpCodeHashMap::const_iterator iLook( maHashMap.find( *pName));
+ if (iLook != maHashMap.end())
+ pToken->OpCode = (*iLook).second;
+ else
+ {
+ OUString aIntName;
+ if (hasExternals())
+ {
+ ExternalHashMap::const_iterator iExt( maExternalHashMap.find( *pName));
+ if (iExt != maExternalHashMap.end())
+ aIntName = (*iExt).second;
+ // Check for existence not needed here, only name-mapping is of
+ // interest.
+ }
+ if (aIntName.isEmpty())
+ aIntName = rCompiler.FindAddInFunction(*pName, !isEnglish()); // bLocalFirst=false for english
+ if (aIntName.isEmpty())
+ pToken->OpCode = getOpCodeUnknown();
+ else
+ {
+ pToken->OpCode = ocExternal;
+ pToken->Data <<= aIntName;
+ }
+ }
+ }
+ return aTokens;
+}
+
+uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::createSequenceOfAvailableMappings(
+ const FormulaCompiler& rCompiler, const sal_Int32 nGroups ) const
+{
+ using namespace sheet;
+
+ // Unfortunately uno::Sequence can't grow without cumbersome reallocs. As
+ // we don't know in advance how many elements it will have we use a
+ // temporary vector to add elements and then copy to Sequence :-(
+ ::std::vector< FormulaOpCodeMapEntry > aVec;
+
+ if (nGroups == FormulaMapGroup::SPECIAL)
+ {
+ // Use specific order, keep in sync with
+ // offapi/com/sun/star/sheet/FormulaMapGroupSpecialOffset.idl
+ static const struct
+ {
+ sal_Int32 nOff;
+ OpCode eOp;
+ } aMap[] = {
+ { FormulaMapGroupSpecialOffset::PUSH , ocPush } ,
+ { FormulaMapGroupSpecialOffset::CALL , ocCall } ,
+ { FormulaMapGroupSpecialOffset::STOP , ocStop } ,
+ { FormulaMapGroupSpecialOffset::EXTERNAL , ocExternal } ,
+ { FormulaMapGroupSpecialOffset::NAME , ocName } ,
+ { FormulaMapGroupSpecialOffset::NO_NAME , ocNoName } ,
+ { FormulaMapGroupSpecialOffset::MISSING , ocMissing } ,
+ { FormulaMapGroupSpecialOffset::BAD , ocBad } ,
+ { FormulaMapGroupSpecialOffset::SPACES , ocSpaces } ,
+ { FormulaMapGroupSpecialOffset::MAT_REF , ocMatRef } ,
+ { FormulaMapGroupSpecialOffset::DB_AREA , ocDBArea } ,
+ /* TODO: { FormulaMapGroupSpecialOffset::TABLE_REF , ocTableRef } , */
+ { FormulaMapGroupSpecialOffset::MACRO , ocMacro } ,
+ { FormulaMapGroupSpecialOffset::COL_ROW_NAME , ocColRowName } ,
+ { FormulaMapGroupSpecialOffset::WHITESPACE , ocWhitespace }
+ };
+ const size_t nCount = SAL_N_ELEMENTS(aMap);
+ // Preallocate vector elements.
+ FormulaOpCodeMapEntry aEntry;
+ aEntry.Token.OpCode = getOpCodeUnknown();
+ aVec.resize(nCount, aEntry);
+
+ for (auto& i : aMap)
+ {
+ size_t nIndex = static_cast< size_t >( i.nOff );
+ if (aVec.size() <= nIndex)
+ {
+ // The offsets really should be aligned with the size, so if
+ // the vector was preallocated above this code to resize it is
+ // just a measure in case the table isn't in sync with the API,
+ // usually it isn't executed.
+ aEntry.Token.OpCode = getOpCodeUnknown();
+ aVec.resize( nIndex + 1, aEntry );
+ }
+ aEntry.Token.OpCode = i.eOp;
+ aVec[nIndex] = aEntry;
+ }
+ }
+ else
+ {
+ /* FIXME: Once we support error constants in formulas we'll need a map
+ * group for that, e.g. FormulaMapGroup::ERROR_CONSTANTS, and fill
+ * SC_OPCODE_START_ERRORS to SC_OPCODE_STOP_ERRORS. */
+
+ // Anything else but SPECIAL.
+ if ((nGroups & FormulaMapGroup::SEPARATORS) != 0)
+ {
+ static const sal_uInt16 aOpCodes[] = {
+ SC_OPCODE_OPEN,
+ SC_OPCODE_CLOSE,
+ SC_OPCODE_SEP,
+ };
+ lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
+ }
+ if ((nGroups & FormulaMapGroup::ARRAY_SEPARATORS) != 0)
+ {
+ static const sal_uInt16 aOpCodes[] = {
+ SC_OPCODE_ARRAY_OPEN,
+ SC_OPCODE_ARRAY_CLOSE,
+ SC_OPCODE_ARRAY_ROW_SEP,
+ SC_OPCODE_ARRAY_COL_SEP
+ };
+ lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
+ }
+ if ((nGroups & FormulaMapGroup::UNARY_OPERATORS) != 0)
+ {
+ // Due to the nature of the percent operator following its operand
+ // it isn't sorted into unary operators for compiler interna.
+ lclPushOpCodeMapEntry( aVec, mpTable.get(), ocPercentSign );
+ // "+" can be used as unary operator too, push only if binary group is not set
+ if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) == 0)
+ lclPushOpCodeMapEntry( aVec, mpTable.get(), ocAdd );
+ // regular unary operators
+ for (sal_uInt16 nOp = SC_OPCODE_START_UN_OP; nOp < SC_OPCODE_STOP_UN_OP && nOp < mnSymbols; ++nOp)
+ {
+ lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
+ }
+ }
+ if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) != 0)
+ {
+ for (sal_uInt16 nOp = SC_OPCODE_START_BIN_OP; nOp < SC_OPCODE_STOP_BIN_OP && nOp < mnSymbols; ++nOp)
+ {
+ switch (nOp)
+ {
+ // AND and OR in fact are functions but for legacy reasons
+ // are sorted into binary operators for compiler interna.
+ case SC_OPCODE_AND :
+ case SC_OPCODE_OR :
+ break; // nothing,
+ default:
+ lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
+ }
+ }
+ }
+ if ((nGroups & FormulaMapGroup::FUNCTIONS) != 0)
+ {
+ // Function names are not consecutive, skip the gaps between
+ // functions with no parameter, functions with 1 parameter
+ lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_NO_PAR,
+ ::std::min< sal_uInt16 >( SC_OPCODE_STOP_NO_PAR, mnSymbols ) );
+ lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_1_PAR,
+ ::std::min< sal_uInt16 >( SC_OPCODE_STOP_1_PAR, mnSymbols ) );
+ // Additional functions not within range of functions.
+ static const sal_uInt16 aOpCodes[] = {
+ SC_OPCODE_IF,
+ SC_OPCODE_IF_ERROR,
+ SC_OPCODE_IF_NA,
+ SC_OPCODE_CHOOSE,
+ SC_OPCODE_AND,
+ SC_OPCODE_OR
+ };
+ lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
+ // functions with 2 or more parameters.
+ for (sal_uInt16 nOp = SC_OPCODE_START_2_PAR; nOp < SC_OPCODE_STOP_2_PAR && nOp < mnSymbols; ++nOp)
+ {
+ switch (nOp)
+ {
+ // NO_NAME is in SPECIAL.
+ case SC_OPCODE_NO_NAME :
+ break; // nothing,
+ default:
+ lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
+ }
+ }
+ // If AddIn functions are present in this mapping, use them, and only those.
+ if (hasExternals())
+ {
+ for (auto const& elem : maExternalHashMap)
+ {
+ FormulaOpCodeMapEntry aEntry;
+ aEntry.Name = elem.first;
+ aEntry.Token.Data <<= elem.second;
+ aEntry.Token.OpCode = ocExternal;
+ aVec.push_back( aEntry);
+ }
+ }
+ else
+ {
+ rCompiler.fillAddInToken( aVec, isEnglish());
+ }
+ }
+ }
+ return uno::Sequence< FormulaOpCodeMapEntry >(aVec.data(), aVec.size());
+}
+
+
+void FormulaCompiler::OpCodeMap::putOpCode( const OUString & rStr, const OpCode eOp, const CharClass* pCharClass )
+{
+ if (0 < eOp && sal_uInt16(eOp) < mnSymbols)
+ {
+ bool bPutOp = mpTable[eOp].isEmpty();
+ bool bRemoveFromMap = false;
+ if (!bPutOp)
+ {
+ switch (eOp)
+ {
+ // These OpCodes are meant to overwrite and also remove an
+ // existing mapping.
+ case ocCurrency:
+ bPutOp = true;
+ bRemoveFromMap = true;
+ break;
+ // These separator OpCodes are meant to overwrite and also
+ // remove an existing mapping if it is not used for one of the
+ // other separators.
+ case ocArrayColSep:
+ bPutOp = true;
+ bRemoveFromMap = (mpTable[ocArrayRowSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]);
+ break;
+ case ocArrayRowSep:
+ bPutOp = true;
+ bRemoveFromMap = (mpTable[ocArrayColSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]);
+ break;
+ // For ocSep keep the ";" in map but remove any other if it is
+ // not used for ocArrayColSep or ocArrayRowSep.
+ case ocSep:
+ bPutOp = true;
+ bRemoveFromMap = (mpTable[eOp] != ";" &&
+ mpTable[ocArrayColSep] != mpTable[eOp] &&
+ mpTable[ocArrayRowSep] != mpTable[eOp]);
+ break;
+ // These OpCodes are known to be duplicates in the Excel
+ // external API mapping because of different parameter counts
+ // in different BIFF versions. Names are identical and entries
+ // are ignored.
+ case ocLinest:
+ case ocTrend:
+ case ocLogest:
+ case ocGrowth:
+ case ocTrunc:
+ case ocFixed:
+ case ocGetDayOfWeek:
+ case ocHLookup:
+ case ocVLookup:
+ case ocGetDiffDate360:
+ if (rStr == mpTable[eOp])
+ return;
+ [[fallthrough]];
+ // These OpCodes are known to be added to an existing mapping,
+ // but only for the OOXML external API mapping. This is *not*
+ // FormulaLanguage::OOXML. Keep the first
+ // (correct) definition for the OpCode, all following are
+ // additional alias entries in the map.
+ case ocErrorType:
+ case ocMultiArea:
+ case ocBackSolver:
+ case ocEasterSunday:
+ case ocCurrent:
+ case ocStyle:
+ if (mbEnglish &&
+ FormulaGrammar::extractFormulaLanguage( meGrammar) == FormulaGrammar::GRAM_EXTERNAL)
+ {
+ // Both bPutOp and bRemoveFromMap stay false.
+ break;
+ }
+ [[fallthrough]];
+ default:
+ SAL_WARN("formula.core",
+ "OpCodeMap::putOpCode: reusing OpCode " << static_cast<sal_uInt16>(eOp)
+ << ", replacing '" << mpTable[eOp] << "' with '" << rStr << "' in "
+ << (mbEnglish ? "" : "non-") << "English map 0x" << ::std::hex << meGrammar);
+ }
+ }
+
+ // Case preserving opcode -> string, upper string -> opcode
+ if (bRemoveFromMap)
+ {
+ OUString aUpper( pCharClass ? pCharClass->uppercase( mpTable[eOp]) : rStr.toAsciiUpperCase());
+ // Ensure we remove a mapping only for the requested OpCode.
+ OpCodeHashMap::const_iterator it( maHashMap.find( aUpper));
+ if (it != maHashMap.end() && (*it).second == eOp)
+ maHashMap.erase( it);
+ }
+ if (bPutOp)
+ mpTable[eOp] = rStr;
+ OUString aUpper( pCharClass ? pCharClass->uppercase( rStr) : rStr.toAsciiUpperCase());
+ maHashMap.emplace(aUpper, eOp);
+ }
+ else
+ {
+ SAL_WARN( "formula.core", "OpCodeMap::putOpCode: OpCode out of range");
+ }
+}
+
+
+FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr, bool bComputeII, bool bMatrixFlag )
+ :
+ nCurrentFactorParam(0),
+ pArr( &rArr ),
+ maArrIterator( rArr ),
+ pCode( nullptr ),
+ pStack( nullptr ),
+ eLastOp( ocPush ),
+ nRecursion( 0 ),
+ nNumFmt( SvNumFormatType::UNDEFINED ),
+ pc( 0 ),
+ meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
+ bAutoCorrect( false ),
+ bCorrected( false ),
+ glSubTotal( false ),
+ needsRPNTokenCheck( false ),
+ mbJumpCommandReorder(true),
+ mbStopOnError(true),
+ mbComputeII(bComputeII),
+ mbMatrixFlag(bMatrixFlag)
+{
+}
+
+FormulaTokenArray FormulaCompiler::smDummyTokenArray;
+
+FormulaCompiler::FormulaCompiler(bool bComputeII, bool bMatrixFlag)
+ :
+ nCurrentFactorParam(0),
+ pArr( nullptr ),
+ maArrIterator( smDummyTokenArray ),
+ pCode( nullptr ),
+ pStack( nullptr ),
+ eLastOp( ocPush ),
+ nRecursion(0),
+ nNumFmt( SvNumFormatType::UNDEFINED ),
+ pc( 0 ),
+ meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
+ bAutoCorrect( false ),
+ bCorrected( false ),
+ glSubTotal( false ),
+ needsRPNTokenCheck( false ),
+ mbJumpCommandReorder(true),
+ mbStopOnError(true),
+ mbComputeII(bComputeII),
+ mbMatrixFlag(bMatrixFlag)
+{
+}
+
+FormulaCompiler::~FormulaCompiler()
+{
+}
+
+FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetOpCodeMap( const sal_Int32 nLanguage ) const
+{
+ const bool bTemporary = !HasOpCodeMap(nLanguage);
+ OpCodeMapPtr xMap = GetFinalOpCodeMap(nLanguage);
+ if (bTemporary)
+ const_cast<FormulaCompiler*>(this)->DestroyOpCodeMap(nLanguage);
+ return xMap;
+}
+
+FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetFinalOpCodeMap( const sal_Int32 nLanguage ) const
+{
+ FormulaCompiler::OpCodeMapPtr xMap;
+ using namespace sheet;
+ switch (nLanguage)
+ {
+ case FormulaLanguage::ODFF :
+ if (!mxSymbolsODFF)
+ InitSymbolsODFF( InitSymbols::INIT);
+ xMap = mxSymbolsODFF;
+ break;
+ case FormulaLanguage::ODF_11 :
+ if (!mxSymbolsPODF)
+ InitSymbolsPODF( InitSymbols::INIT);
+ xMap = mxSymbolsPODF;
+ break;
+ case FormulaLanguage::ENGLISH :
+ if (!mxSymbolsEnglish)
+ InitSymbolsEnglish( InitSymbols::INIT);
+ xMap = mxSymbolsEnglish;
+ break;
+ case FormulaLanguage::NATIVE :
+ if (!mxSymbolsNative)
+ InitSymbolsNative( InitSymbols::INIT);
+ xMap = mxSymbolsNative;
+ break;
+ case FormulaLanguage::XL_ENGLISH:
+ if (!mxSymbolsEnglishXL)
+ InitSymbolsEnglishXL( InitSymbols::INIT);
+ xMap = mxSymbolsEnglishXL;
+ break;
+ case FormulaLanguage::OOXML:
+ if (!mxSymbolsOOXML)
+ InitSymbolsOOXML( InitSymbols::INIT);
+ xMap = mxSymbolsOOXML;
+ break;
+ case FormulaLanguage::API :
+ if (!mxSymbolsAPI)
+ InitSymbolsAPI( InitSymbols::INIT);
+ xMap = mxSymbolsAPI;
+ break;
+ default:
+ ; // nothing, NULL map returned
+ }
+ return xMap;
+}
+
+void FormulaCompiler::DestroyOpCodeMap( const sal_Int32 nLanguage )
+{
+ using namespace sheet;
+ switch (nLanguage)
+ {
+ case FormulaLanguage::ODFF :
+ InitSymbolsODFF( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::ODF_11 :
+ InitSymbolsPODF( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::ENGLISH :
+ InitSymbolsEnglish( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::NATIVE :
+ InitSymbolsNative( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::XL_ENGLISH:
+ InitSymbolsEnglishXL( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::OOXML:
+ InitSymbolsOOXML( InitSymbols::DESTROY);
+ break;
+ case FormulaLanguage::API :
+ InitSymbolsAPI( InitSymbols::DESTROY);
+ break;
+ default:
+ ; // nothing
+ }
+}
+
+bool FormulaCompiler::HasOpCodeMap( const sal_Int32 nLanguage ) const
+{
+ using namespace sheet;
+ switch (nLanguage)
+ {
+ case FormulaLanguage::ODFF :
+ return InitSymbolsODFF( InitSymbols::ASK);
+ case FormulaLanguage::ODF_11 :
+ return InitSymbolsPODF( InitSymbols::ASK);
+ case FormulaLanguage::ENGLISH :
+ return InitSymbolsEnglish( InitSymbols::ASK);
+ case FormulaLanguage::NATIVE :
+ return InitSymbolsNative( InitSymbols::ASK);
+ case FormulaLanguage::XL_ENGLISH:
+ return InitSymbolsEnglishXL( InitSymbols::ASK);
+ case FormulaLanguage::OOXML:
+ return InitSymbolsOOXML( InitSymbols::ASK);
+ case FormulaLanguage::API :
+ return InitSymbolsAPI( InitSymbols::ASK);
+ default:
+ ; // nothing
+ }
+ return false;
+}
+
+OUString FormulaCompiler::FindAddInFunction( const OUString& /*rUpperName*/, bool /*bLocalFirst*/ ) const
+{
+ return OUString();
+}
+
+FormulaCompiler::OpCodeMapPtr FormulaCompiler::CreateOpCodeMap(
+ const uno::Sequence<
+ const sheet::FormulaOpCodeMapEntry > & rMapping,
+ bool bEnglish )
+{
+ using sheet::FormulaOpCodeMapEntry;
+ // Filter / API maps are never Core
+ NonConstOpCodeMapPtr xMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, false,
+ FormulaGrammar::mergeToGrammar( FormulaGrammar::setEnglishBit(
+ FormulaGrammar::GRAM_EXTERNAL, bEnglish), FormulaGrammar::CONV_UNSPECIFIED));
+ std::unique_ptr<CharClass> xCharClass( xMap->isEnglish() ? nullptr : createCharClassIfNonEnglishUI());
+ const CharClass* pCharClass = xCharClass.get();
+ for (auto const& rMapEntry : rMapping)
+ {
+ OpCode eOp = OpCode(rMapEntry.Token.OpCode);
+ if (eOp != ocExternal)
+ xMap->putOpCode( rMapEntry.Name, eOp, pCharClass);
+ else
+ {
+ OUString aExternalName;
+ if (rMapEntry.Token.Data >>= aExternalName)
+ xMap->putExternal( rMapEntry.Name, aExternalName);
+ else
+ {
+ SAL_WARN( "formula.core", "FormulaCompiler::CreateOpCodeMap: no Token.Data external name");
+ }
+ }
+ }
+ return xMap;
+}
+
+static bool lcl_fillNativeSymbols( FormulaCompiler::NonConstOpCodeMapPtr& xMap, FormulaCompiler::InitSymbols eWhat = FormulaCompiler::InitSymbols::INIT )
+{
+ static OpCodeMapData aSymbolMap;
+ osl::MutexGuard aGuard(&aSymbolMap.maMtx);
+
+ if (eWhat == FormulaCompiler::InitSymbols::ASK)
+ {
+ return bool(aSymbolMap.mxSymbolMap);
+ }
+ else if (eWhat == FormulaCompiler::InitSymbols::DESTROY)
+ {
+ aSymbolMap.mxSymbolMap.reset();
+ }
+ else if (!aSymbolMap.mxSymbolMap)
+ {
+ // Core
+ aSymbolMap.mxSymbolMap =
+ std::make_shared<FormulaCompiler::OpCodeMap>(
+ SC_OPCODE_LAST_OPCODE_ID + 1, true, FormulaGrammar::GRAM_NATIVE_UI);
+ OpCodeList aOpCodeListSymbols(RID_STRLIST_FUNCTION_NAMES_SYMBOLS, aSymbolMap.mxSymbolMap);
+ OpCodeList aOpCodeListNative(RID_STRLIST_FUNCTION_NAMES, aSymbolMap.mxSymbolMap);
+ // No AddInMap for native core mapping.
+ }
+
+ xMap = aSymbolMap.mxSymbolMap;
+
+ return true;
+}
+
+const OUString& FormulaCompiler::GetNativeSymbol( OpCode eOp )
+{
+ NonConstOpCodeMapPtr xSymbolsNative;
+ lcl_fillNativeSymbols( xSymbolsNative);
+ return xSymbolsNative->getSymbol( eOp );
+}
+
+sal_Unicode FormulaCompiler::GetNativeSymbolChar( OpCode eOp )
+{
+ return GetNativeSymbol(eOp)[0];
+}
+
+bool FormulaCompiler::InitSymbolsNative( FormulaCompiler::InitSymbols eWhat ) const
+{
+ return lcl_fillNativeSymbols( mxSymbolsNative, eWhat);
+}
+
+bool FormulaCompiler::InitSymbolsEnglish( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
+ mxSymbolsEnglish = aMap.mxSymbolMap;
+ return true;
+}
+
+bool FormulaCompiler::InitSymbolsPODF( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF, FormulaGrammar::GRAM_PODF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
+ mxSymbolsPODF = aMap.mxSymbolMap;
+ return true;
+}
+
+bool FormulaCompiler::InitSymbolsAPI( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_API, FormulaGrammar::GRAM_API, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
+ mxSymbolsAPI = aMap.mxSymbolMap;
+ return true;
+}
+
+bool FormulaCompiler::InitSymbolsODFF( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF, FormulaGrammar::GRAM_ODFF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
+ mxSymbolsODFF = aMap.mxSymbolMap;
+ return true;
+}
+
+bool FormulaCompiler::InitSymbolsEnglishXL( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
+ mxSymbolsEnglishXL = aMap.mxSymbolMap;
+ if (eWhat != InitSymbols::INIT)
+ return true;
+
+ // TODO: For now, just replace the separators to the Excel English
+ // variants. Later, if we want to properly map Excel functions with Calc
+ // functions, we'll need to do a little more work here.
+ mxSymbolsEnglishXL->putOpCode( OUString(','), ocSep, nullptr);
+ mxSymbolsEnglishXL->putOpCode( OUString(','), ocArrayColSep, nullptr);
+ mxSymbolsEnglishXL->putOpCode( OUString(';'), ocArrayRowSep, nullptr);
+
+ return true;
+}
+
+bool FormulaCompiler::InitSymbolsOOXML( FormulaCompiler::InitSymbols eWhat ) const
+{
+ static OpCodeMapData aMap;
+ osl::MutexGuard aGuard(&aMap.maMtx);
+ if (eWhat == InitSymbols::ASK)
+ return bool(aMap.mxSymbolMap);
+ else if (eWhat == InitSymbols::DESTROY)
+ aMap.mxSymbolMap.reset();
+ else if (!aMap.mxSymbolMap)
+ loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML, FormulaGrammar::GRAM_OOXML, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
+ mxSymbolsOOXML = aMap.mxSymbolMap;
+ return true;
+}
+
+
+void FormulaCompiler::loadSymbols(const std::pair<const char*, int>* pSymbols, FormulaGrammar::Grammar eGrammar,
+ NonConstOpCodeMapPtr& rxMap, SeparatorType eSepType) const
+{
+ if ( rxMap )
+ return;
+
+ // not Core
+ rxMap = std::make_shared<OpCodeMap>( SC_OPCODE_LAST_OPCODE_ID + 1, eGrammar != FormulaGrammar::GRAM_ODFF, eGrammar );
+ OpCodeList aOpCodeList(pSymbols, rxMap, eSepType);
+
+ fillFromAddInMap( rxMap, eGrammar);
+ // Fill from collection for AddIns not already present.
+ if (FormulaGrammar::GRAM_ENGLISH == eGrammar)
+ fillFromAddInCollectionEnglishName( rxMap);
+ else
+ {
+ fillFromAddInCollectionUpperName( rxMap);
+ if (FormulaGrammar::GRAM_API == eGrammar)
+ {
+ // Add known but not in AddInMap English names, e.g. from the
+ // PricingFunctions AddIn or any user supplied AddIn.
+ fillFromAddInCollectionEnglishName( rxMap);
+ }
+ }
+}
+
+void FormulaCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& /*xMap */) const
+{
+}
+
+void FormulaCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& /*xMap */) const
+{
+}
+
+void FormulaCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& /*xMap*/, FormulaGrammar::Grammar /*_eGrammar */) const
+{
+}
+
+OpCode FormulaCompiler::GetEnglishOpCode( const OUString& rName ) const
+{
+ FormulaCompiler::OpCodeMapPtr xMap = GetOpCodeMap( sheet::FormulaLanguage::ENGLISH);
+
+ formula::OpCodeHashMap::const_iterator iLook( xMap->getHashMap().find( rName ) );
+ bool bFound = (iLook != xMap->getHashMap().end());
+ return bFound ? (*iLook).second : ocNone;
+}
+
+bool FormulaCompiler::IsOpCodeVolatile( OpCode eOp )
+{
+ bool bRet = false;
+ switch (eOp)
+ {
+ // no parameters:
+ case ocRandom:
+ case ocGetActDate:
+ case ocGetActTime:
+ // one parameter:
+ case ocFormula:
+ case ocInfo:
+ // more than one parameters:
+ // ocIndirect otherwise would have to do
+ // StopListening and StartListening on a reference for every
+ // interpreted value.
+ case ocIndirect:
+ // ocOffset results in indirect references.
+ case ocOffset:
+ // ocDebugVar shows internal value that may change as the internal state changes.
+ case ocDebugVar:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+ return bRet;
+}
+
+bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp )
+{
+ switch (eOp)
+ {
+ case ocIf:
+ case ocIfError:
+ case ocIfNA:
+ case ocChoose:
+ return true;
+ default:
+ ;
+ }
+ return false;
+}
+
+// Remove quotes, escaped quotes are unescaped.
+bool FormulaCompiler::DeQuote( OUString& rStr )
+{
+ sal_Int32 nLen = rStr.getLength();
+ if ( nLen > 1 && rStr[0] == '\'' && rStr[ nLen-1 ] == '\'' )
+ {
+ rStr = rStr.copy( 1, nLen-2 );
+ rStr = rStr.replaceAll( "''", "'" );
+ return true;
+ }
+ return false;
+}
+
+void FormulaCompiler::fillAddInToken(
+ ::std::vector< sheet::FormulaOpCodeMapEntry >& /*_rVec*/,
+ bool /*_bIsEnglish*/) const
+{
+}
+
+bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode )
+{
+ switch (eOpCode)
+ {
+ case ocDde :
+ case ocGrowth :
+ case ocTrend :
+ case ocLogest :
+ case ocLinest :
+ case ocFrequency :
+ case ocMatTrans :
+ case ocMatMult :
+ case ocMatInv :
+ case ocMatrixUnit :
+ case ocModalValue_Multi :
+ case ocFourier :
+ return true;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ return false;
+}
+
+
+void FormulaCompiler::OpCodeMap::putCopyOpCode( const OUString& rSymbol, OpCode eOp )
+{
+ SAL_WARN_IF( !mpTable[eOp].isEmpty() && rSymbol.isEmpty(), "formula.core",
+ "OpCodeMap::putCopyOpCode: NOT replacing OpCode " << static_cast<sal_uInt16>(eOp)
+ << " '" << mpTable[eOp] << "' with empty name!");
+ if (!mpTable[eOp].isEmpty() && rSymbol.isEmpty())
+ maHashMap.emplace(mpTable[eOp], eOp);
+ else
+ {
+ mpTable[eOp] = rSymbol;
+ maHashMap.emplace(rSymbol, eOp);
+ }
+}
+
+void FormulaCompiler::OpCodeMap::copyFrom( const OpCodeMap& r )
+{
+ maHashMap = OpCodeHashMap( mnSymbols);
+
+ sal_uInt16 n = r.getSymbolCount();
+ SAL_WARN_IF( n != mnSymbols, "formula.core",
+ "OpCodeMap::copyFrom: unequal size, this: " << mnSymbols << " that: " << n);
+ if (n > mnSymbols)
+ n = mnSymbols;
+
+ // OpCode 0 (ocPush) should never be in a map.
+ SAL_WARN_IF( !mpTable[0].isEmpty() || !r.mpTable[0].isEmpty(), "formula.core",
+ "OpCodeMap::copyFrom: OpCode 0 assigned, this: '"
+ << mpTable[0] << "' that: '" << r.mpTable[0] << "'");
+
+ // For bOverrideKnownBad when copying from the English core map (ODF 1.1
+ // and API) to the native map (UI "use English function names") replace the
+ // known bad legacy function names with correct ones.
+ if (r.mbCore &&
+ FormulaGrammar::extractFormulaLanguage( meGrammar) == sheet::FormulaLanguage::NATIVE &&
+ FormulaGrammar::extractFormulaLanguage( r.meGrammar) == sheet::FormulaLanguage::ENGLISH)
+ {
+ for (sal_uInt16 i = 1; i < n; ++i)
+ {
+ OUString aSymbol;
+ OpCode eOp = OpCode(i);
+ switch (eOp)
+ {
+ case ocRRI:
+ aSymbol = "RRI";
+ break;
+ case ocTableOp:
+ aSymbol = "MULTIPLE.OPERATIONS";
+ break;
+ default:
+ aSymbol = r.mpTable[i];
+ }
+ putCopyOpCode( aSymbol, eOp);
+ }
+ }
+ else
+ {
+ for (sal_uInt16 i = 1; i < n; ++i)
+ {
+ OpCode eOp = OpCode(i);
+ const OUString& rSymbol = r.mpTable[i];
+ putCopyOpCode( rSymbol, eOp);
+ }
+ }
+
+ // This was meant to copy to native map that does not have AddIn symbols
+ // but needs them from the source map. It is unclear what should happen if
+ // the destination already had externals, so do it only if it doesn't.
+ if (!hasExternals())
+ {
+ maExternalHashMap = r.maExternalHashMap;
+ maReverseExternalHashMap = r.maReverseExternalHashMap;
+ mbCore = r.mbCore;
+ if (mbEnglish != r.mbEnglish)
+ {
+ // For now keep mbEnglishLocale setting, which is false for a
+ // non-English native map we're copying to.
+ /* TODO:
+ if (!mbEnglish && r.mbEnglish)
+ mbEnglishLocale = "getUseEnglishLocaleFromConfiguration()";
+ or set from outside i.e. via ScCompiler.
+ */
+ mbEnglish = r.mbEnglish;
+ }
+ }
+}
+
+
+FormulaError FormulaCompiler::GetErrorConstant( const OUString& rName ) const
+{
+ FormulaError nError = FormulaError::NONE;
+ OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName));
+ if (iLook != mxSymbols->getHashMap().end())
+ {
+ switch ((*iLook).second)
+ {
+ // Not all may make sense in a formula, but these we know as
+ // opcodes.
+ case ocErrNull:
+ nError = FormulaError::NoCode;
+ break;
+ case ocErrDivZero:
+ nError = FormulaError::DivisionByZero;
+ break;
+ case ocErrValue:
+ nError = FormulaError::NoValue;
+ break;
+ case ocErrRef:
+ nError = FormulaError::NoRef;
+ break;
+ case ocErrName:
+ nError = FormulaError::NoName;
+ break;
+ case ocErrNum:
+ nError = FormulaError::IllegalFPOperation;
+ break;
+ case ocErrNA:
+ nError = FormulaError::NotAvailable;
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ else
+ {
+ // Per convention recognize detailed "#ERRxxx!" constants, always
+ // untranslated. Error numbers are sal_uInt16 so at most 5 decimal
+ // digits.
+ if (rName.startsWithIgnoreAsciiCase("#ERR") && rName.getLength() <= 10 && rName[rName.getLength()-1] == '!')
+ {
+ sal_uInt32 nErr = o3tl::toUInt32(rName.subView( 4, rName.getLength() - 5));
+ if (0 < nErr && nErr <= SAL_MAX_UINT16 && isPublishedFormulaError(static_cast<FormulaError>(nErr)))
+ nError = static_cast<FormulaError>(nErr);
+ }
+ }
+ return nError;
+}
+
+void FormulaCompiler::EnableJumpCommandReorder( bool bEnable )
+{
+ mbJumpCommandReorder = bEnable;
+}
+
+void FormulaCompiler::EnableStopOnError( bool bEnable )
+{
+ mbStopOnError = bEnable;
+}
+
+void FormulaCompiler::AppendErrorConstant( OUStringBuffer& rBuffer, FormulaError nError ) const
+{
+ OpCode eOp;
+ switch (nError)
+ {
+ case FormulaError::NoCode:
+ eOp = ocErrNull;
+ break;
+ case FormulaError::DivisionByZero:
+ eOp = ocErrDivZero;
+ break;
+ case FormulaError::NoValue:
+ eOp = ocErrValue;
+ break;
+ case FormulaError::NoRef:
+ eOp = ocErrRef;
+ break;
+ case FormulaError::NoName:
+ eOp = ocErrName;
+ break;
+ case FormulaError::IllegalFPOperation:
+ eOp = ocErrNum;
+ break;
+ case FormulaError::NotAvailable:
+ eOp = ocErrNA;
+ break;
+ default:
+ {
+ // Per convention create detailed "#ERRxxx!" constants, always
+ // untranslated.
+ rBuffer.append("#ERR");
+ rBuffer.append(static_cast<sal_Int32>(nError));
+ rBuffer.append('!');
+ return;
+ }
+ }
+ rBuffer.append( mxSymbols->getSymbol( eOp));
+}
+
+constexpr short nRecursionMax = 100;
+
+bool FormulaCompiler::GetToken()
+{
+ FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
+ if ( nRecursion > nRecursionMax )
+ {
+ SetError( FormulaError::StackOverflow );
+ mpLastToken = mpToken = new FormulaByteToken( ocStop );
+ return false;
+ }
+ if ( bAutoCorrect && !pStack )
+ { // don't merge stacked subroutine code into entered formula
+ aCorrectedFormula += aCorrectedSymbol;
+ aCorrectedSymbol.clear();
+ }
+ bool bStop = false;
+ if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
+ bStop = true;
+ else
+ {
+ FormulaTokenRef pSpacesToken;
+ short nWasColRowName;
+ if ( pArr->OpCodeBefore( maArrIterator.GetIndex() ) == ocColRowName )
+ nWasColRowName = 1;
+ else
+ nWasColRowName = 0;
+ OpCode eTmpOp;
+ mpToken = maArrIterator.Next();
+ while (mpToken && ((eTmpOp = mpToken->GetOpCode()) == ocSpaces || eTmpOp == ocWhitespace))
+ {
+ if (eTmpOp == ocSpaces)
+ {
+ // For significant whitespace remember last ocSpaces token.
+ // Usually there's only one even for multiple spaces.
+ pSpacesToken = mpToken;
+ if ( nWasColRowName )
+ nWasColRowName++;
+ }
+ if ( bAutoCorrect && !pStack )
+ CreateStringFromToken( aCorrectedFormula, mpToken.get() );
+ mpToken = maArrIterator.Next();
+ }
+ if ( bAutoCorrect && !pStack && mpToken )
+ CreateStringFromToken( aCorrectedSymbol, mpToken.get() );
+ if( !mpToken )
+ {
+ if( pStack )
+ {
+ PopTokenArray();
+ // mpLastToken was popped as well and corresponds to the
+ // then current last token during PushTokenArray(), e.g. for
+ // HandleRange().
+ return GetToken();
+ }
+ else
+ bStop = true;
+ }
+ else
+ {
+ if ( nWasColRowName >= 2 && mpToken->GetOpCode() == ocColRowName )
+ { // convert an ocSpaces to ocIntersect in RPN
+ mpLastToken = mpToken = new FormulaByteToken( ocIntersect );
+ maArrIterator.StepBack(); // we advanced to the second ocColRowName, step back
+ }
+ else if (pSpacesToken && FormulaGrammar::isExcelSyntax( meGrammar) &&
+ mpLastToken && mpToken &&
+ isPotentialRangeType( mpLastToken.get(), false, false) &&
+ isPotentialRangeType( mpToken.get(), false, true))
+ {
+ // Let IntersectionLine() <- Factor() decide how to treat this,
+ // once the actual arguments are determined in RPN.
+ mpLastToken = mpToken = pSpacesToken;
+ maArrIterator.StepBack(); // step back from next non-spaces token
+ return true;
+ }
+ }
+ }
+ if( bStop )
+ {
+ mpLastToken = mpToken = new FormulaByteToken( ocStop );
+ return false;
+ }
+
+ // Remember token for next round and any PushTokenArray() calls that may
+ // occur in handlers.
+ mpLastToken = mpToken;
+
+ if ( mpToken->IsExternalRef() )
+ {
+ return HandleExternalReference(*mpToken);
+ }
+ else
+ {
+ switch (mpToken->GetOpCode())
+ {
+ case ocSubTotal:
+ case ocAggregate:
+ glSubTotal = true;
+ break;
+ case ocName:
+ if( HandleRange())
+ {
+ // Expanding ocName might have introduced tokens such as ocStyle that prevent formula threading,
+ // but those wouldn't be present in the raw tokens array, so ensure RPN tokens will be checked too.
+ needsRPNTokenCheck = true;
+ return true;
+ }
+ return false;
+ case ocColRowName:
+ return HandleColRowName();
+ case ocDBArea:
+ return HandleDbData();
+ case ocTableRef:
+ return HandleTableRef();
+ case ocPush:
+ if( mbComputeII )
+ HandleIIOpCode(mpToken.get(), nullptr, 0);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ return true;
+}
+
+
+// RPN creation by recursion
+void FormulaCompiler::Factor()
+{
+ if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
+ return;
+
+ CurrentFactor pFacToken( this );
+
+ OpCode eOp = mpToken->GetOpCode();
+ if (eOp == ocPush || eOp == ocColRowNameAuto || eOp == ocMatRef || eOp == ocDBArea
+ || eOp == ocTableRef
+ || (!mbJumpCommandReorder && ((eOp == ocName) || (eOp == ocColRowName) || (eOp == ocBad)))
+ )
+ {
+ PutCode( mpToken );
+ eOp = NextToken();
+ if( eOp == ocOpen )
+ {
+ // PUSH( is an error that may be caused by an unknown function.
+ SetError(
+ ( mpToken->GetType() == svString
+ || mpToken->GetType() == svSingleRef )
+ ? FormulaError::NoName : FormulaError::OperatorExpected );
+ if ( bAutoCorrect && !pStack )
+ { // assume multiplication
+ aCorrectedFormula += mxSymbols->getSymbol( ocMul);
+ bCorrected = true;
+ NextToken();
+ eOp = Expression();
+ if( eOp != ocClose )
+ SetError( FormulaError::PairExpected);
+ else
+ NextToken();
+ }
+ }
+ }
+ else if( eOp == ocOpen )
+ {
+ NextToken();
+ eOp = Expression();
+ while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
+ { // range list (A1;A2) converted to (A1~A2)
+ pFacToken = mpToken;
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, 0);
+ eOp = Expression();
+ // Do not ignore error here, regardless of mbStopOnError, to not
+ // change the formula expression in case of an unexpected state.
+ if (pArr->GetCodeError() == FormulaError::NONE && pc >= 2)
+ {
+ // Left and right operands must be reference or function
+ // returning reference to form a range list.
+ const FormulaToken* p = pCode[-2];
+ if (p && isPotentialRangeType( p, true, false))
+ {
+ p = pCode[-1];
+ if (p && isPotentialRangeType( p, true, true))
+ {
+ pFacToken->NewOpCode( ocUnion, FormulaToken::PrivateAccess());
+ // XXX NOTE: the token's eType is still svSep here!
+ PutCode( pFacToken);
+ }
+ }
+ }
+ }
+ if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ else
+ NextToken();
+
+ /* TODO: if no conversion to ocUnion is involved this could collect
+ * such expression as a list or (matrix) vector to be passed as
+ * argument for one parameter (which in fact the ocUnion svRefList is a
+ * special case of), which would require a new StackVar type and needed
+ * to be handled by the interpreter for functions that could support it
+ * (i.e. already handle VAR_ARGS or svRefList parameters). This is also
+ * not defined by ODF.
+ * Does Excel handle =SUM((1;2))?
+ * As is, the interpreter catches extraneous uncalculated
+ * subexpressions like 1 of (1;2) as error. */
+ }
+ else
+ {
+ if( nNumFmt == SvNumFormatType::UNDEFINED )
+ nNumFmt = lcl_GetRetFormat( eOp );
+
+ if ( IsOpCodeVolatile( eOp) )
+ pArr->SetExclusiveRecalcModeAlways();
+ else
+ {
+ switch( eOp )
+ {
+ // Functions recalculated on every document load.
+ // ONLOAD_LENIENT here to be able to distinguish and not
+ // force a recalc (if not in an ALWAYS or ONLOAD_MUST
+ // context) but keep an imported result from for example
+ // OOXML a DDE call. Will be recalculated for ODFF.
+ case ocConvertOOo :
+ case ocDde:
+ case ocMacro:
+ case ocWebservice:
+ pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
+ break;
+ // RANDBETWEEN() is volatile like RAND(). Other Add-In
+ // functions may have to be recalculated or not, we don't
+ // know, classify as ONLOAD_LENIENT.
+ case ocExternal:
+ if (mpToken->GetExternal() == "com.sun.star.sheet.addin.Analysis.getRandbetween")
+ pArr->SetExclusiveRecalcModeAlways();
+ else
+ pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
+ break;
+ // If the referred cell is moved the value changes.
+ case ocColumn :
+ case ocRow :
+ pArr->SetRecalcModeOnRefMove();
+ break;
+ // ocCell needs recalc on move for some possible type values.
+ // And recalc mode on load, tdf#60645
+ case ocCell :
+ pArr->SetRecalcModeOnRefMove();
+ pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST );
+ break;
+ case ocHyperLink :
+ // Cell with hyperlink needs to be calculated on load to
+ // get its matrix result generated.
+ pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST );
+ pArr->SetHyperLink( true);
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR)
+ {
+ pFacToken = mpToken;
+ eOp = NextToken();
+ if (eOp != ocOpen)
+ {
+ SetError( FormulaError::PairExpected);
+ PutCode( pFacToken );
+ }
+ else
+ {
+ eOp = NextToken();
+ if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ PutCode( pFacToken);
+ NextToken();
+ }
+ }
+ else if (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR)
+ {
+ if (eOp == ocIsoWeeknum && FormulaGrammar::isODFF( meGrammar ))
+ {
+ // tdf#50950 ocIsoWeeknum can have 2 arguments when saved by older versions of Calc;
+ // the opcode then has to be changed to ocWeek for backward compatibility
+ pFacToken = mpToken;
+ eOp = NextToken();
+ bool bNoParam = false;
+ if (eOp == ocOpen)
+ {
+ eOp = NextToken();
+ if (eOp == ocClose)
+ bNoParam = true;
+ else
+ {
+ CheckSetForceArrayParameter( mpToken, 0);
+ eOp = Expression();
+ }
+ }
+ else
+ SetError( FormulaError::PairExpected);
+ sal_uInt32 nSepCount = 0;
+ const sal_uInt16 nSepPos = maArrIterator.GetIndex() - 1; // separator position, if any
+ if( !bNoParam )
+ {
+ nSepCount++;
+ while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
+ {
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, nSepCount);
+ nSepCount++;
+ if (nSepCount > FORMULA_MAXPARAMS)
+ SetError( FormulaError::CodeOverflow);
+ eOp = Expression();
+ }
+ }
+ if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ else
+ NextToken();
+ pFacToken->SetByte( nSepCount );
+ if (nSepCount == 2)
+ {
+ // An old mode!=1 indicates ISO week, remove argument if
+ // literal double value and keep function. Anything else
+ // can not be resolved, there exists no "like ISO but week
+ // starts on Sunday" mode in WEEKNUM and for an expression
+ // we can't determine.
+ // Current index is nSepPos+3 if expression stops, or
+ // nSepPos+4 if expression continues after the call because
+ // we just called NextToken() to move away from it.
+ if (pc >= 2 && (maArrIterator.GetIndex() == nSepPos + 3 || maArrIterator.GetIndex() == nSepPos + 4) &&
+ pArr->TokenAt(nSepPos+1)->GetType() == svDouble &&
+ pArr->TokenAt(nSepPos+1)->GetDouble() != 1.0 &&
+ pArr->TokenAt(nSepPos+2)->GetOpCode() == ocClose &&
+ pArr->RemoveToken( nSepPos, 2) == 2)
+ {
+ maArrIterator.AfterRemoveToken( nSepPos, 2);
+ // Remove the ocPush/svDouble just removed also from
+ // the compiler local RPN array.
+ --pCode; --pc;
+ (*pCode)->DecRef(); // may be dead now
+ pFacToken->SetByte( nSepCount - 1 );
+ }
+ else
+ {
+ // For the remaining two arguments cases use the
+ // compatibility function.
+ pFacToken->NewOpCode( ocWeeknumOOo, FormulaToken::PrivateAccess());
+ }
+ }
+ PutCode( pFacToken );
+ }
+ else
+ {
+ // standard handling of 1-parameter opcodes
+ pFacToken = mpToken;
+ eOp = NextToken();
+ if( nNumFmt == SvNumFormatType::UNDEFINED && eOp == ocNot )
+ nNumFmt = SvNumFormatType::LOGICAL;
+ if (eOp == ocOpen)
+ {
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, 0);
+ eOp = Expression();
+ }
+ else
+ SetError( FormulaError::PairExpected);
+ if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ else if ( pArr->GetCodeError() == FormulaError::NONE )
+ {
+ pFacToken->SetByte( 1 );
+ if (mbComputeII)
+ {
+ FormulaToken** pArg = pCode - 1;
+ HandleIIOpCode(pFacToken, &pArg, 1);
+ }
+ }
+ PutCode( pFacToken );
+ NextToken();
+ }
+ }
+ else if ((SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR)
+ || eOp == ocExternal
+ || eOp == ocMacro
+ || eOp == ocAnd
+ || eOp == ocOr
+ || eOp == ocBad
+ || ( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
+ || (!mbJumpCommandReorder && IsOpCodeJumpCommand(eOp)))
+ {
+ pFacToken = mpToken;
+ OpCode eMyLastOp = eOp;
+ eOp = NextToken();
+ bool bNoParam = false;
+ bool bBadName = false;
+ if (eOp == ocOpen)
+ {
+ eOp = NextToken();
+ if (eOp == ocClose)
+ bNoParam = true;
+ else
+ {
+ CheckSetForceArrayParameter( mpToken, 0);
+ eOp = Expression();
+ }
+ }
+ else if (eMyLastOp == ocBad)
+ {
+ // Just a bad name, not an unknown function, no parameters, no
+ // closing expected.
+ bBadName = true;
+ bNoParam = true;
+ }
+ else
+ SetError( FormulaError::PairExpected);
+ sal_uInt32 nSepCount = 0;
+ if( !bNoParam )
+ {
+ bool bDoIICompute = mbComputeII;
+ // Array of FormulaToken double pointers to collect the parameters of II opcodes.
+ FormulaToken*** pArgArray = nullptr;
+ if (bDoIICompute)
+ {
+ pArgArray = static_cast<FormulaToken***>(alloca(sizeof(FormulaToken**)*FORMULA_MAXPARAMSII));
+ if (!pArgArray)
+ bDoIICompute = false;
+ }
+
+ nSepCount++;
+
+ if (bDoIICompute)
+ pArgArray[nSepCount-1] = pCode - 1; // Add first argument
+
+ while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
+ {
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, nSepCount);
+ nSepCount++;
+ if (nSepCount > FORMULA_MAXPARAMS)
+ SetError( FormulaError::CodeOverflow);
+ eOp = Expression();
+ if (bDoIICompute && nSepCount <= FORMULA_MAXPARAMSII)
+ pArgArray[nSepCount - 1] = pCode - 1; // Add rest of the arguments
+ }
+ if (bDoIICompute)
+ HandleIIOpCode(pFacToken, pArgArray,
+ std::min(nSepCount, static_cast<sal_uInt32>(FORMULA_MAXPARAMSII)));
+ }
+ bool bDone = false;
+ if (bBadName)
+ ; // nothing, keep current token for return
+ else if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ else
+ {
+ NextToken();
+ bDone = true;
+ }
+ // Jumps are just normal functions for the FunctionAutoPilot tree view
+ if (!mbJumpCommandReorder && pFacToken->GetType() == svJump)
+ pFacToken = new FormulaFAPToken( pFacToken->GetOpCode(), nSepCount, pFacToken );
+ else
+ pFacToken->SetByte( nSepCount );
+ PutCode( pFacToken );
+
+ if (bDone)
+ AnnotateOperands();
+ }
+ else if (IsOpCodeJumpCommand(eOp))
+ {
+ // the PC counters are -1
+ pFacToken = mpToken;
+ switch (eOp)
+ {
+ case ocIf:
+ pFacToken->GetJump()[ 0 ] = 3; // if, else, behind
+ break;
+ case ocChoose:
+ pFacToken->GetJump()[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
+ break;
+ case ocIfError:
+ case ocIfNA:
+ pFacToken->GetJump()[ 0 ] = 2; // if, behind
+ break;
+ default:
+ SAL_WARN("formula.core","Jump OpCode: " << +eOp);
+ assert(!"FormulaCompiler::Factor: someone forgot to add a jump count case");
+ }
+ eOp = NextToken();
+ if (eOp == ocOpen)
+ {
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, 0);
+ eOp = Expression();
+ }
+ else
+ SetError( FormulaError::PairExpected);
+ PutCode( pFacToken );
+ // During AutoCorrect (since pArr->GetCodeError() is
+ // ignored) an unlimited ocIf would crash because
+ // ScRawToken::Clone() allocates the JumpBuffer according to
+ // nJump[0]*2+2, which is 3*2+2 on ocIf and 2*2+2 ocIfError and ocIfNA.
+ short nJumpMax;
+ OpCode eFacOpCode = pFacToken->GetOpCode();
+ switch (eFacOpCode)
+ {
+ case ocIf:
+ nJumpMax = 3;
+ break;
+ case ocChoose:
+ nJumpMax = FORMULA_MAXJUMPCOUNT;
+ break;
+ case ocIfError:
+ case ocIfNA:
+ nJumpMax = 2;
+ break;
+ default:
+ nJumpMax = 0;
+ SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
+ assert(!"FormulaCompiler::Factor: someone forgot to add a jump max case");
+ }
+ short nJumpCount = 0;
+ while ( (nJumpCount < (FORMULA_MAXJUMPCOUNT - 1)) && (eOp == ocSep)
+ && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
+ {
+ if ( ++nJumpCount <= nJumpMax )
+ pFacToken->GetJump()[nJumpCount] = pc-1;
+ NextToken();
+ CheckSetForceArrayParameter( mpToken, nJumpCount - 1);
+ eOp = Expression();
+ // ocSep or ocClose terminate the subexpression
+ PutCode( mpToken );
+ }
+ if (eOp != ocClose)
+ SetError( FormulaError::PairExpected);
+ else
+ {
+ NextToken();
+ // always limit to nJumpMax, no arbitrary overwrites
+ if ( ++nJumpCount <= nJumpMax )
+ pFacToken->GetJump()[ nJumpCount ] = pc-1;
+ eFacOpCode = pFacToken->GetOpCode();
+ bool bLimitOk;
+ switch (eFacOpCode)
+ {
+ case ocIf:
+ bLimitOk = (nJumpCount <= 3);
+ break;
+ case ocChoose:
+ bLimitOk = (nJumpCount < FORMULA_MAXJUMPCOUNT);
+ break;
+ case ocIfError:
+ case ocIfNA:
+ bLimitOk = (nJumpCount <= 2);
+ break;
+ default:
+ bLimitOk = false;
+ SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
+ assert(!"FormulaCompiler::Factor: someone forgot to add a jump limit case");
+ }
+ if (bLimitOk)
+ pFacToken->GetJump()[ 0 ] = nJumpCount;
+ else
+ SetError( FormulaError::IllegalParameter);
+ }
+ }
+ else if ( eOp == ocMissing )
+ {
+ PutCode( mpToken );
+ NextToken();
+ }
+ else if ( eOp == ocClose )
+ {
+ SetError( FormulaError::ParameterExpected );
+ }
+ else if ( eOp == ocSep )
+ { // Subsequent ocSep
+ SetError( FormulaError::ParameterExpected );
+ if ( bAutoCorrect && !pStack )
+ {
+ aCorrectedSymbol.clear();
+ bCorrected = true;
+ }
+ }
+ else if ( mpToken->IsExternalRef() )
+ {
+ PutCode( mpToken);
+ NextToken();
+ }
+ else
+ {
+ SetError( FormulaError::UnknownToken );
+ if ( bAutoCorrect && !pStack )
+ {
+ if ( eOp == ocStop )
+ { // trailing operator w/o operand
+ sal_Int32 nLen = aCorrectedFormula.getLength();
+ if ( nLen )
+ aCorrectedFormula = aCorrectedFormula.copy( 0, nLen - 1 );
+ aCorrectedSymbol.clear();
+ bCorrected = true;
+ }
+ }
+ }
+ }
+}
+
+void FormulaCompiler::RangeLine()
+{
+ Factor();
+ while (mpToken->GetOpCode() == ocRange)
+ {
+ FormulaToken** pCode1 = pCode - 1;
+ FormulaTokenRef p = mpToken;
+ NextToken();
+ Factor();
+ FormulaToken** pCode2 = pCode - 1;
+ if (!MergeRangeReference( pCode1, pCode2))
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::IntersectionLine()
+{
+ RangeLine();
+ while (mpToken->GetOpCode() == ocIntersect || mpToken->GetOpCode() == ocSpaces)
+ {
+ sal_uInt16 nCodeIndex = maArrIterator.GetIndex() - 1;
+ FormulaToken** pCode1 = pCode - 1;
+ FormulaTokenRef p = mpToken;
+ NextToken();
+ RangeLine();
+ FormulaToken** pCode2 = pCode - 1;
+ if (p->GetOpCode() == ocSpaces)
+ {
+ // Convert to intersection if both left and right are references or
+ // functions (potentially returning references, if not then a space
+ // or no space would be a syntax error anyway), not other operators
+ // or operands. Else discard.
+ if (isAdjacentOrGapRpnEnd( pc, pCode, pCode1, pCode2) && isIntersectable( pCode1, pCode2))
+ {
+ FormulaTokenRef pIntersect( new FormulaByteToken( ocIntersect));
+ // Replace ocSpaces with ocIntersect so that when switching
+ // formula syntax the correct operator string is created.
+ pArr->ReplaceToken( nCodeIndex, pIntersect.get(), FormulaTokenArray::ReplaceMode::CODE_ONLY);
+ PutCode( pIntersect);
+ }
+ }
+ else
+ {
+ PutCode(p);
+ }
+ }
+}
+
+void FormulaCompiler::UnionLine()
+{
+ IntersectionLine();
+ while (mpToken->GetOpCode() == ocUnion)
+ {
+ FormulaTokenRef p = mpToken;
+ NextToken();
+ IntersectionLine();
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::UnaryLine()
+{
+ if( mpToken->GetOpCode() == ocAdd )
+ GetToken();
+ else if (SC_OPCODE_START_UN_OP <= mpToken->GetOpCode() &&
+ mpToken->GetOpCode() < SC_OPCODE_STOP_UN_OP)
+ {
+ FormulaTokenRef p = mpToken;
+ NextToken();
+ UnaryLine();
+ if (mbComputeII)
+ {
+ FormulaToken** pArg = pCode - 1;
+ HandleIIOpCode(p.get(), &pArg, 1);
+ }
+ PutCode( p );
+ }
+ else
+ UnionLine();
+}
+
+void FormulaCompiler::PostOpLine()
+{
+ UnaryLine();
+ while ( mpToken->GetOpCode() == ocPercentSign )
+ { // this operator _follows_ its operand
+ if (mbComputeII)
+ {
+ FormulaToken** pArg = pCode - 1;
+ HandleIIOpCode(mpToken.get(), &pArg, 1);
+ }
+ PutCode( mpToken );
+ NextToken();
+ }
+}
+
+void FormulaCompiler::PowLine()
+{
+ PostOpLine();
+ while (mpToken->GetOpCode() == ocPow)
+ {
+ FormulaTokenRef p = mpToken;
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ PostOpLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::MulDivLine()
+{
+ PowLine();
+ while (mpToken->GetOpCode() == ocMul || mpToken->GetOpCode() == ocDiv)
+ {
+ FormulaTokenRef p = mpToken;
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ PowLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::AddSubLine()
+{
+ MulDivLine();
+ while (mpToken->GetOpCode() == ocAdd || mpToken->GetOpCode() == ocSub)
+ {
+ FormulaTokenRef p = mpToken;
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ MulDivLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::ConcatLine()
+{
+ AddSubLine();
+ while (mpToken->GetOpCode() == ocAmpersand)
+ {
+ FormulaTokenRef p = mpToken;
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ AddSubLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+}
+
+void FormulaCompiler::CompareLine()
+{
+ ConcatLine();
+ while (mpToken->GetOpCode() >= ocEqual && mpToken->GetOpCode() <= ocGreaterEqual)
+ {
+ FormulaTokenRef p = mpToken;
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ ConcatLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+}
+
+OpCode FormulaCompiler::Expression()
+{
+ FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
+ if ( nRecursion > nRecursionMax )
+ {
+ SetError( FormulaError::StackOverflow );
+ return ocStop; //! generate token instead?
+ }
+ CompareLine();
+ while (mpToken->GetOpCode() == ocAnd || mpToken->GetOpCode() == ocOr)
+ {
+ FormulaTokenRef p = mpToken;
+ mpToken->SetByte( 2 ); // 2 parameters!
+ FormulaToken** pArgArray[2];
+ if (mbComputeII)
+ pArgArray[0] = pCode - 1; // Add first argument
+ NextToken();
+ CompareLine();
+ if (mbComputeII)
+ {
+ pArgArray[1] = pCode - 1; // Add second argument
+ HandleIIOpCode(p.get(), pArgArray, 2);
+ }
+ PutCode(p);
+ }
+ return mpToken->GetOpCode();
+}
+
+
+void FormulaCompiler::SetError( FormulaError /*nError*/ )
+{
+}
+
+FormulaTokenRef FormulaCompiler::ExtendRangeReference( FormulaToken & /*rTok1*/, FormulaToken & /*rTok2*/ )
+{
+ return FormulaTokenRef();
+}
+
+bool FormulaCompiler::MergeRangeReference( FormulaToken * * const pCode1, FormulaToken * const * const pCode2 )
+{
+ if (!isAdjacentRpnEnd( pc, pCode, pCode1, pCode2))
+ return false;
+
+ FormulaToken *p1 = *pCode1, *p2 = *pCode2;
+ FormulaTokenRef p = ExtendRangeReference( *p1, *p2);
+ if (!p)
+ return false;
+
+ p->IncRef();
+ p1->DecRef();
+ p2->DecRef();
+ *pCode1 = p.get();
+ --pCode;
+ --pc;
+
+ return true;
+}
+
+bool FormulaCompiler::CompileTokenArray()
+{
+ glSubTotal = false;
+ bCorrected = false;
+ needsRPNTokenCheck = false;
+ if (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)
+ {
+ if ( bAutoCorrect )
+ {
+ aCorrectedFormula.clear();
+ aCorrectedSymbol.clear();
+ }
+ pArr->DelRPN();
+ maArrIterator.Reset();
+ pStack = nullptr;
+ FormulaToken* pDataArray[ FORMULA_MAXTOKENS + 1 ];
+ // Code in some places refers to the last token as 'pCode - 1', which may
+ // point before the first element if the expression is bad. So insert a dummy
+ // node in that place which will make that token be nullptr.
+ pDataArray[ 0 ] = nullptr;
+ FormulaToken** pData = pDataArray + 1;
+ pCode = pData;
+ bool bWasForced = pArr->IsRecalcModeForced();
+ if ( bWasForced && bAutoCorrect )
+ aCorrectedFormula = "=";
+ pArr->ClearRecalcMode();
+ maArrIterator.Reset();
+ eLastOp = ocOpen;
+ pc = 0;
+ NextToken();
+ OpCode eOp = Expression();
+ // Some trailing garbage that doesn't form an expression?
+ if (eOp != ocStop)
+ SetError( FormulaError::OperatorExpected);
+ PostProcessCode();
+
+ FormulaError nErrorBeforePop = pArr->GetCodeError();
+
+ while( pStack )
+ PopTokenArray();
+ if( pc )
+ {
+ pArr->CreateNewRPNArrayFromData( pData, pc );
+ if( needsRPNTokenCheck )
+ pArr->CheckAllRPNTokens();
+ }
+
+ // once an error, always an error
+ if( pArr->GetCodeError() == FormulaError::NONE && nErrorBeforePop != FormulaError::NONE )
+ pArr->SetCodeError( nErrorBeforePop);
+
+ if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
+ {
+ pArr->DelRPN();
+ maArrIterator.Reset();
+ pArr->SetHyperLink( false);
+ }
+
+ if ( bWasForced )
+ pArr->SetRecalcModeForced();
+ }
+ if( nNumFmt == SvNumFormatType::UNDEFINED )
+ nNumFmt = SvNumFormatType::NUMBER;
+ return glSubTotal;
+}
+
+void FormulaCompiler::PopTokenArray()
+{
+ if( !pStack )
+ return;
+
+ FormulaArrayStack* p = pStack;
+ pStack = p->pNext;
+ // obtain special RecalcMode from SharedFormula
+ if ( pArr->IsRecalcModeAlways() )
+ p->pArr->SetExclusiveRecalcModeAlways();
+ else if ( !pArr->IsRecalcModeNormal() && p->pArr->IsRecalcModeNormal() )
+ p->pArr->SetMaskedRecalcMode( pArr->GetRecalcMode() );
+ p->pArr->SetCombinedBitsRecalcMode( pArr->GetRecalcMode() );
+ if ( pArr->IsHyperLink() ) // fdo 87534
+ p->pArr->SetHyperLink( true );
+ if( p->bTemp )
+ delete pArr;
+ pArr = p->pArr;
+ maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
+ maArrIterator.Jump(p->nIndex);
+ mpLastToken = p->mpLastToken;
+ delete p;
+}
+
+void FormulaCompiler::CreateStringFromTokenArray( OUString& rFormula )
+{
+ OUStringBuffer aBuffer( pArr->GetLen() * 5 );
+ CreateStringFromTokenArray( aBuffer );
+ rFormula = aBuffer.makeStringAndClear();
+}
+
+void FormulaCompiler::CreateStringFromTokenArray( OUStringBuffer& rBuffer )
+{
+ rBuffer.setLength(0);
+ if( !pArr->GetLen() )
+ return;
+
+ FormulaTokenArray* pSaveArr = pArr;
+ int nSaveIndex = maArrIterator.GetIndex();
+ bool bODFF = FormulaGrammar::isODFF( meGrammar);
+ if (bODFF || FormulaGrammar::isPODF( meGrammar) )
+ {
+ // Scan token array for missing args and re-write if present.
+ MissingConventionODF aConv( bODFF);
+ if (pArr->NeedsPodfRewrite( aConv))
+ {
+ pArr = pArr->RewriteMissing( aConv );
+ maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
+ }
+ }
+ else if ( FormulaGrammar::isOOXML( meGrammar ) )
+ {
+ // Scan token array for missing args and rewrite if present.
+ if (pArr->NeedsOoxmlRewrite())
+ {
+ MissingConventionOOXML aConv;
+ pArr = pArr->RewriteMissing( aConv );
+ maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
+ }
+ }
+
+ // At least one character per token, plus some are references, some are
+ // function names, some are numbers, ...
+ rBuffer.ensureCapacity( pArr->GetLen() * 5 );
+
+ if ( pArr->IsRecalcModeForced() )
+ rBuffer.append( '=');
+ const FormulaToken* t = maArrIterator.First();
+ while( t )
+ t = CreateStringFromToken( rBuffer, t, true );
+
+ if (pSaveArr != pArr)
+ {
+ delete pArr;
+ pArr = pSaveArr;
+ maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
+ maArrIterator.Jump(nSaveIndex);
+ }
+}
+
+const FormulaToken* FormulaCompiler::CreateStringFromToken( OUString& rFormula, const FormulaToken* pTokenP )
+{
+ OUStringBuffer aBuffer;
+ const FormulaToken* p = CreateStringFromToken( aBuffer, pTokenP );
+ rFormula += aBuffer;
+ return p;
+}
+
+const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuffer, const FormulaToken* pTokenP,
+ bool bAllowArrAdvance )
+{
+ bool bNext = true;
+ bool bSpaces = false;
+ const FormulaToken* t = pTokenP;
+ OpCode eOp = t->GetOpCode();
+ if( eOp >= ocAnd && eOp <= ocOr )
+ {
+ // AND, OR infix?
+ if ( bAllowArrAdvance )
+ t = maArrIterator.Next();
+ else
+ t = maArrIterator.PeekNext();
+ bNext = false;
+ bSpaces = ( !t || t->GetOpCode() != ocOpen );
+ }
+ if( bSpaces )
+ rBuffer.append( ' ');
+
+ if (eOp == ocSpaces || eOp == ocWhitespace)
+ {
+ bool bWriteSpaces = true;
+ if (eOp == ocSpaces && mxSymbols->isODFF())
+ {
+ const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
+ bool bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
+ if (bIntersectionOp)
+ {
+ p = maArrIterator.PeekNextNoSpaces();
+ bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
+ }
+ if (bIntersectionOp)
+ {
+ rBuffer.append( "!!");
+ bWriteSpaces = false;
+ }
+ }
+ if (bWriteSpaces)
+ {
+ // ODF v1.3 OpenFormula 5.14 Whitespace states "whitespace shall
+ // not separate a function name from its initial opening
+ // parenthesis".
+ //
+ // ECMA-376-1:2016 18.17.2 Syntax states "that no space characters
+ // shall separate a function-name from the left parenthesis (()
+ // that follows it." and Excel even chokes on it.
+ //
+ // Suppress/remove it in any case also in UI, it will not be
+ // preserved.
+ const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
+ if (p && p->IsFunction())
+ {
+ p = maArrIterator.PeekNextNoSpaces();
+ if (p && p->GetOpCode() == ocOpen)
+ bWriteSpaces = false;
+ }
+ }
+ if (bWriteSpaces)
+ {
+ // most times it's just one blank
+ sal_uInt8 n = t->GetByte();
+ for ( sal_uInt8 j=0; j<n; ++j )
+ {
+ if (eOp == ocWhitespace)
+ rBuffer.append( t->GetChar());
+ else
+ rBuffer.append( ' ');
+ }
+ }
+ }
+ else if( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
+ rBuffer.appendAscii( pInternal[ eOp - ocInternalBegin ] );
+ else if (eOp == ocIntersect)
+ {
+ // Nasty, ugly, horrific, terrifying...
+ if (FormulaGrammar::isExcelSyntax( meGrammar))
+ rBuffer.append(' ');
+ else
+ rBuffer.append( mxSymbols->getSymbol( eOp));
+ }
+ else if( static_cast<sal_uInt16>(eOp) < mxSymbols->getSymbolCount()) // Keyword:
+ rBuffer.append( mxSymbols->getSymbol( eOp));
+ else
+ {
+ SAL_WARN( "formula.core","unknown OpCode");
+ rBuffer.append( GetNativeSymbol( ocErrName ));
+ }
+ if( bNext )
+ {
+ if (t->IsExternalRef())
+ {
+ CreateStringFromExternal( rBuffer, pTokenP);
+ }
+ else
+ {
+ switch( t->GetType() )
+ {
+ case svDouble:
+ AppendDouble( rBuffer, t->GetDouble() );
+ break;
+
+ case svString:
+ if( eOp == ocBad || eOp == ocStringXML )
+ rBuffer.append( t->GetString().getString());
+ else
+ AppendString( rBuffer, t->GetString().getString() );
+ break;
+ case svSingleRef:
+ CreateStringFromSingleRef( rBuffer, t);
+ break;
+ case svDoubleRef:
+ CreateStringFromDoubleRef( rBuffer, t);
+ break;
+ case svMatrix:
+ case svMatrixCell:
+ CreateStringFromMatrix( rBuffer, t );
+ break;
+
+ case svIndex:
+ CreateStringFromIndex( rBuffer, t );
+ if (t->GetOpCode() == ocTableRef && bAllowArrAdvance && NeedsTableRefTransformation())
+ {
+ // Suppress all TableRef related tokens, the resulting
+ // range was written by CreateStringFromIndex().
+ const FormulaToken* const p = maArrIterator.PeekNext();
+ if (p && p->GetOpCode() == ocTableRefOpen)
+ {
+ int nLevel = 0;
+ do
+ {
+ t = maArrIterator.Next();
+ if (!t)
+ break;
+
+ // Switch cases correspond with those in
+ // ScCompiler::HandleTableRef()
+ switch (t->GetOpCode())
+ {
+ case ocTableRefOpen:
+ ++nLevel;
+ break;
+ case ocTableRefClose:
+ --nLevel;
+ break;
+ case ocTableRefItemAll:
+ case ocTableRefItemHeaders:
+ case ocTableRefItemData:
+ case ocTableRefItemTotals:
+ case ocTableRefItemThisRow:
+ case ocSep:
+ case ocPush:
+ case ocRange:
+ case ocSpaces:
+ case ocWhitespace:
+ break;
+ default:
+ nLevel = 0;
+ bNext = false;
+ }
+ } while (nLevel);
+ }
+ }
+ break;
+ case svExternal:
+ {
+ // mapped or translated name of AddIns
+ OUString aAddIn( t->GetExternal() );
+ bool bMapped = mxSymbols->isPODF(); // ODF 1.1 directly uses programmatical name
+ if (!bMapped && mxSymbols->hasExternals())
+ {
+ ExternalHashMap::const_iterator iLook = mxSymbols->getReverseExternalHashMap().find( aAddIn);
+ if (iLook != mxSymbols->getReverseExternalHashMap().end())
+ {
+ aAddIn = (*iLook).second;
+ bMapped = true;
+ }
+ }
+ if (!bMapped && !mxSymbols->isEnglish())
+ LocalizeString( aAddIn );
+ rBuffer.append( aAddIn);
+ }
+ break;
+ case svError:
+ AppendErrorConstant( rBuffer, t->GetError());
+ break;
+ case svByte:
+ case svJump:
+ case svFAP:
+ case svMissing:
+ case svSep:
+ break; // Opcodes
+ default:
+ SAL_WARN("formula.core", "FormulaCompiler::GetStringFromToken: unknown token type " << t->GetType());
+ } // of switch
+ }
+ }
+ if( bSpaces )
+ rBuffer.append( ' ');
+ if ( bAllowArrAdvance )
+ {
+ if( bNext )
+ t = maArrIterator.Next();
+ return t;
+ }
+ return pTokenP;
+}
+
+
+void FormulaCompiler::AppendDouble( OUStringBuffer& rBuffer, double fVal ) const
+{
+ if ( mxSymbols->isEnglishLocale() )
+ {
+ ::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max, '.', true );
+ }
+ else
+ {
+ SvtSysLocale aSysLocale;
+ ::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
+ rtl_math_StringFormat_Automatic,
+ rtl_math_DecimalPlaces_Max,
+ aSysLocale.GetLocaleData().getNumDecimalSep()[0],
+ true );
+ }
+}
+
+void FormulaCompiler::AppendBoolean( OUStringBuffer& rBuffer, bool bVal ) const
+{
+ rBuffer.append( mxSymbols->getSymbol( bVal ? ocTrue : ocFalse ) );
+}
+
+void FormulaCompiler::AppendString( OUStringBuffer& rBuffer, const OUString & rStr )
+{
+ rBuffer.append( '"');
+ if ( lcl_UnicodeStrChr( rStr.getStr(), '"' ) == nullptr )
+ rBuffer.append( rStr );
+ else
+ {
+ OUString aStr = rStr.replaceAll( "\"", "\"\"" );
+ rBuffer.append(aStr);
+ }
+ rBuffer.append( '"');
+}
+
+bool FormulaCompiler::NeedsTableRefTransformation() const
+{
+ // Currently only UI representations and OOXML export use Table structured
+ // references. Not defined in ODFF.
+ // Unnecessary to explicitly check for ODFF grammar as the ocTableRefOpen
+ // symbol is not defined there.
+ return mxSymbols->getSymbol( ocTableRefOpen).isEmpty() || FormulaGrammar::isPODF( meGrammar);
+}
+
+void FormulaCompiler::UpdateSeparatorsNative(
+ const OUString& rSep, const OUString& rArrayColSep, const OUString& rArrayRowSep )
+{
+ NonConstOpCodeMapPtr xSymbolsNative;
+ lcl_fillNativeSymbols( xSymbolsNative);
+ xSymbolsNative->putOpCode( rSep, ocSep, nullptr);
+ xSymbolsNative->putOpCode( rArrayColSep, ocArrayColSep, nullptr);
+ xSymbolsNative->putOpCode( rArrayRowSep, ocArrayRowSep, nullptr);
+}
+
+void FormulaCompiler::ResetNativeSymbols()
+{
+ NonConstOpCodeMapPtr xSymbolsNative;
+ lcl_fillNativeSymbols( xSymbolsNative, InitSymbols::DESTROY);
+ lcl_fillNativeSymbols( xSymbolsNative);
+}
+
+void FormulaCompiler::SetNativeSymbols( const OpCodeMapPtr& xMap )
+{
+ NonConstOpCodeMapPtr xSymbolsNative;
+ lcl_fillNativeSymbols( xSymbolsNative);
+ xSymbolsNative->copyFrom( *xMap );
+}
+
+
+OpCode FormulaCompiler::NextToken()
+{
+ if( !GetToken() )
+ return ocStop;
+ OpCode eOp = mpToken->GetOpCode();
+ // There must be an operator before a push
+ if ( (eOp == ocPush || eOp == ocColRowNameAuto) &&
+ !( (eLastOp == ocOpen) || (eLastOp == ocSep) ||
+ (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)) )
+ SetError( FormulaError::OperatorExpected);
+ // Operator and Plus => operator
+ if (eOp == ocAdd && (eLastOp == ocOpen || eLastOp == ocSep ||
+ (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
+ {
+ FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
+ eOp = NextToken();
+ }
+ else
+ {
+ // Before an operator there must not be another operator, with the
+ // exception of AND and OR.
+ if ( eOp != ocAnd && eOp != ocOr &&
+ (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP )
+ && (eLastOp == ocOpen || eLastOp == ocSep ||
+ (SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
+ {
+ SetError( FormulaError::VariableExpected);
+ if ( bAutoCorrect && !pStack )
+ {
+ if ( eOp == eLastOp || eLastOp == ocOpen )
+ { // throw away duplicated operator
+ aCorrectedSymbol.clear();
+ bCorrected = true;
+ }
+ else
+ {
+ sal_Int32 nPos = aCorrectedFormula.getLength();
+ if ( nPos )
+ {
+ nPos--;
+ sal_Unicode c = aCorrectedFormula[ nPos ];
+ switch ( eOp )
+ { // swap operators
+ case ocGreater:
+ if ( c == mxSymbols->getSymbolChar( ocEqual) )
+ { // >= instead of =>
+ aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
+ rtl::OUStringChar( mxSymbols->getSymbolChar(ocGreater) ) );
+ aCorrectedSymbol = OUString(c);
+ bCorrected = true;
+ }
+ break;
+ case ocLess:
+ if ( c == mxSymbols->getSymbolChar( ocEqual) )
+ { // <= instead of =<
+ aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
+ rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) );
+ aCorrectedSymbol = OUString(c);
+ bCorrected = true;
+ }
+ else if ( c == mxSymbols->getSymbolChar( ocGreater) )
+ { // <> instead of ><
+ aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
+ rtl::OUStringChar( mxSymbols->getSymbolChar(ocLess) ) );
+ aCorrectedSymbol = OUString(c);
+ bCorrected = true;
+ }
+ break;
+ case ocMul:
+ if ( c == mxSymbols->getSymbolChar( ocSub) )
+ { // *- instead of -*
+ aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
+ rtl::OUStringChar( mxSymbols->getSymbolChar(ocMul) ) );
+ aCorrectedSymbol = OUString(c);
+ bCorrected = true;
+ }
+ break;
+ case ocDiv:
+ if ( c == mxSymbols->getSymbolChar( ocSub) )
+ { // /- instead of -/
+ aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
+ rtl::OUStringChar( mxSymbols->getSymbolChar(ocDiv) ) );
+ aCorrectedSymbol = OUString(c);
+ bCorrected = true;
+ }
+ break;
+ default:
+ ; // nothing
+ }
+ }
+ }
+ }
+ }
+ // Nasty, ugly, horrific, terrifying... significant whitespace...
+ if (eOp == ocSpaces && FormulaGrammar::isExcelSyntax( meGrammar))
+ {
+ // Fake an intersection op as last op for the next round, but at
+ // least roughly check if it could make sense at all.
+ FormulaToken* pPrev = maArrIterator.PeekPrevNoSpaces();
+ if (pPrev && isPotentialRangeType( pPrev, false, false))
+ {
+ FormulaToken* pNext = maArrIterator.PeekNextNoSpaces();
+ if (pNext && isPotentialRangeType( pNext, false, true))
+ eLastOp = ocIntersect;
+ else
+ eLastOp = eOp;
+ }
+ else
+ eLastOp = eOp;
+ }
+ else
+ eLastOp = eOp;
+ }
+ return eOp;
+}
+
+void FormulaCompiler::PutCode( FormulaTokenRef& p )
+{
+ if( pc >= FORMULA_MAXTOKENS - 1 )
+ {
+ if ( pc == FORMULA_MAXTOKENS - 1 )
+ {
+ p = new FormulaByteToken( ocStop );
+ p->IncRef();
+ *pCode++ = p.get();
+ ++pc;
+ }
+ SetError( FormulaError::CodeOverflow);
+ return;
+ }
+ if (pArr->GetCodeError() != FormulaError::NONE && mbJumpCommandReorder)
+ return;
+ ForceArrayOperator( p);
+ p->IncRef();
+ *pCode++ = p.get();
+ pc++;
+}
+
+
+bool FormulaCompiler::HandleExternalReference( const FormulaToken& /*_aToken*/)
+{
+ return true;
+}
+
+bool FormulaCompiler::HandleRange()
+{
+ return true;
+}
+
+bool FormulaCompiler::HandleColRowName()
+{
+ return true;
+}
+
+bool FormulaCompiler::HandleDbData()
+{
+ return true;
+}
+
+bool FormulaCompiler::HandleTableRef()
+{
+ return true;
+}
+
+void FormulaCompiler::CreateStringFromSingleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
+{
+}
+
+void FormulaCompiler::CreateStringFromDoubleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
+{
+}
+
+void FormulaCompiler::CreateStringFromIndex( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
+{
+}
+
+void FormulaCompiler::CreateStringFromMatrix( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
+{
+}
+
+void FormulaCompiler::CreateStringFromExternal( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
+{
+}
+
+void FormulaCompiler::LocalizeString( OUString& /*rName*/ ) const
+{
+}
+
+formula::ParamClass FormulaCompiler::GetForceArrayParameter( const FormulaToken* /*pToken*/, sal_uInt16 /*nParam*/ ) const
+{
+ return ParamClass::Unknown;
+}
+
+void FormulaCompiler::ForceArrayOperator( FormulaTokenRef const & rCurr )
+{
+ if (pCurrentFactorToken.get() == rCurr.get())
+ return;
+
+ const OpCode eOp = rCurr->GetOpCode();
+ const StackVar eType = rCurr->GetType();
+ const bool bInlineArray = (eOp == ocPush && eType == svMatrix);
+
+ if (!bInlineArray)
+ {
+ if (rCurr->GetInForceArray() != ParamClass::Unknown)
+ // Already set, unnecessary to evaluate again. This happens by calls to
+ // CurrentFactor::operator=() while descending through Factor() and
+ // then ascending back (and down and up, ...),
+ // CheckSetForceArrayParameter() and later PutCode().
+ return;
+
+ if (!(eOp != ocPush && (eType == svByte || eType == svJump)))
+ return;
+ }
+
+ // Return class for inline arrays and functions returning array/matrix.
+ // It's somewhat unclear what Excel actually does there and in
+ // ECMA-376-1:2016 OOXML mentions "call to ... shall be an array formula"
+ // only for FREQUENCY() and TRANSPOSE() but not for any other function
+ // returning array/matrix or inline arrays, though for the latter has one
+ // example in 18.17.2 Syntax:
+ // "SUM(SQRT({1,2,3,4})) returns 6.14 when entered normally". However,
+ // these need to be treated similar but not as ParamClass::ForceArray
+ // (which would contradict the example in
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=122301#c19 and A6 of
+ // https://bugs.documentfoundation.org/show_bug.cgi?id=133260#c10 ).
+ // See also
+ // commit d0ded163d8e93dc5b10d7a7c9bdab1d0a6a50bac
+ // commit 5413c8871dec08eff19f514f5f391b946a45c86c
+ constexpr ParamClass eArrayReturn = ParamClass::ForceArrayReturn;
+
+ if (bInlineArray)
+ {
+ // rCurr->SetInForceArray() can not be used with ocPush, but ocPush
+ // with svMatrix has an implicit ParamClass::ForceArrayReturn.
+ if (nCurrentFactorParam > 0 && pCurrentFactorToken
+ && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown
+ && GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1))
+ == ParamClass::Value)
+ {
+ // Propagate to caller as if a function returning an array/matrix
+ // was called (see also below).
+ pCurrentFactorToken->SetInForceArray( eArrayReturn);
+ }
+ return;
+ }
+
+ if (!pCurrentFactorToken)
+ {
+ if (mbMatrixFlag)
+ {
+ // An array/matrix formula acts as ForceArray on all top level
+ // operators and function calls, so that can be inherited properly
+ // below.
+ rCurr->SetInForceArray( ParamClass::ForceArray);
+ }
+ else if (pc >= 2 && SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP)
+ {
+ // Binary operators are not functions followed by arguments
+ // and need some peeking into RPN to inspect their operands.
+ // Note that array context is not forced if only one
+ // of the operands is an array like "={1;2}+A1:A2" returns #VALUE!
+ // if entered in column A and not input in array mode, because it
+ // involves a range reference with an implicit intersection. Check
+ // both arguments are arrays, or the other is ocPush without ranges
+ // for "={1;2}+3" or "={1;2}+A1".
+ // Note this does not catch "={1;2}+ABS(A1)" that could be forced
+ // to array, user still has to close in array mode.
+ // The IsMatrixFunction() is only necessary because not all
+ // functions returning matrix have ForceArrayReturn (yet?), see
+ // OOXML comment above.
+
+ const OpCode eOp1 = pCode[-1]->GetOpCode();
+ const OpCode eOp2 = pCode[-2]->GetOpCode();
+ const bool b1 = (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp1));
+ const bool b2 = (pCode[-2]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(eOp2));
+ if ((b1 && b2)
+ || (b1 && eOp2 == ocPush && pCode[-2]->GetType() != svDoubleRef)
+ || (b2 && eOp1 == ocPush && pCode[-1]->GetType() != svDoubleRef))
+ {
+ rCurr->SetInForceArray( eArrayReturn);
+ }
+ }
+ else if (pc >= 1 && SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP)
+ {
+ // Similar for unary operators.
+ if (pCode[-1]->GetInForceArray() != ParamClass::Unknown || IsMatrixFunction(pCode[-1]->GetOpCode()))
+ {
+ rCurr->SetInForceArray( eArrayReturn);
+ }
+ }
+ return;
+ }
+
+ // Inherited parameter class.
+ const formula::ParamClass eForceType = pCurrentFactorToken->GetInForceArray();
+ if (eForceType == ParamClass::ForceArray || eForceType == ParamClass::ReferenceOrRefArray)
+ {
+ // ReferenceOrRefArray was set only if in ForceArray context already,
+ // it is valid for the one function only to indicate the preferred
+ // return type. Propagate as ForceArray if not another parameter
+ // handling ReferenceOrRefArray.
+ if (nCurrentFactorParam > 0
+ && (GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1))
+ == ParamClass::ReferenceOrRefArray))
+ rCurr->SetInForceArray( ParamClass::ReferenceOrRefArray);
+ else
+ rCurr->SetInForceArray( ParamClass::ForceArray);
+ return;
+ }
+ else if (eForceType == ParamClass::ReferenceOrForceArray)
+ {
+ // Inherit further only if the return class of the nested function is
+ // not Reference. Else flag as suppressed.
+ if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
+ rCurr->SetInForceArray( eForceType);
+ else
+ rCurr->SetInForceArray( ParamClass::SuppressedReferenceOrForceArray);
+ return;
+ }
+
+ if (nCurrentFactorParam <= 0)
+ return;
+
+ // Actual current parameter's class.
+ const formula::ParamClass eParamType = GetForceArrayParameter(
+ pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1));
+ if (eParamType == ParamClass::ForceArray)
+ rCurr->SetInForceArray( eParamType);
+ else if (eParamType == ParamClass::ReferenceOrForceArray)
+ {
+ if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
+ rCurr->SetInForceArray( eParamType);
+ else
+ rCurr->SetInForceArray( formula::ParamClass::SuppressedReferenceOrForceArray);
+ }
+
+ // Propagate a ForceArrayReturn to caller if the called function
+ // returns one and the caller so far does not have a stronger array
+ // mode set and expects a scalar value for this parameter.
+ if (eParamType == ParamClass::Value && pCurrentFactorToken->GetInForceArray() == ParamClass::Unknown)
+ {
+ if (IsMatrixFunction( eOp))
+ pCurrentFactorToken->SetInForceArray( eArrayReturn);
+ else if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) == ParamClass::ForceArrayReturn)
+ pCurrentFactorToken->SetInForceArray( ParamClass::ForceArrayReturn);
+ }
+}
+
+void FormulaCompiler::CheckSetForceArrayParameter( FormulaTokenRef const & rCurr, sal_uInt8 nParam )
+{
+ if (!pCurrentFactorToken)
+ return;
+
+ nCurrentFactorParam = nParam + 1;
+
+ ForceArrayOperator( rCurr);
+}
+
+void FormulaCompiler::PushTokenArray( FormulaTokenArray* pa, bool bTemp )
+{
+ if ( bAutoCorrect && !pStack )
+ { // don't merge stacked subroutine code into entered formula
+ aCorrectedFormula += aCorrectedSymbol;
+ aCorrectedSymbol.clear();
+ }
+ FormulaArrayStack* p = new FormulaArrayStack;
+ p->pNext = pStack;
+ p->pArr = pArr;
+ p->nIndex = maArrIterator.GetIndex();
+ p->mpLastToken = mpLastToken;
+ p->bTemp = bTemp;
+ pStack = p;
+ pArr = pa;
+ maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
+}
+
+} // namespace formula
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/formula/source/core/api/FormulaOpCodeMapperObj.cxx b/formula/source/core/api/FormulaOpCodeMapperObj.cxx
new file mode 100644
index 000000000..a3ffe56cc
--- /dev/null
+++ b/formula/source/core/api/FormulaOpCodeMapperObj.cxx
@@ -0,0 +1,104 @@
+/* -*- 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 <memory>
+#include <sal/config.h>
+
+#include <utility>
+
+#include <formula/FormulaCompiler.hxx>
+#include <formula/FormulaOpCodeMapperObj.hxx>
+#include <formula/opcode.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+
+namespace formula
+{
+ using namespace ::com::sun::star;
+
+sal_Bool SAL_CALL FormulaOpCodeMapperObj::supportsService( const OUString& _rServiceName )
+{
+ return cppu::supportsService(this, _rServiceName);
+}
+
+FormulaOpCodeMapperObj::FormulaOpCodeMapperObj(::std::unique_ptr<FormulaCompiler> && _pCompiler)
+: m_pCompiler(std::move(_pCompiler))
+{
+}
+
+FormulaOpCodeMapperObj::~FormulaOpCodeMapperObj()
+{
+}
+
+::sal_Int32 SAL_CALL FormulaOpCodeMapperObj::getOpCodeExternal()
+{
+ return ocExternal;
+}
+
+
+::sal_Int32 SAL_CALL FormulaOpCodeMapperObj::getOpCodeUnknown()
+{
+ return FormulaCompiler::OpCodeMap::getOpCodeUnknown();
+}
+
+
+css::uno::Sequence< css::sheet::FormulaToken >
+SAL_CALL FormulaOpCodeMapperObj::getMappings(
+ const css::uno::Sequence< OUString >& rNames,
+ sal_Int32 nLanguage )
+{
+ FormulaCompiler::OpCodeMapPtr xMap = m_pCompiler->GetOpCodeMap( nLanguage);
+ if (!xMap)
+ throw lang::IllegalArgumentException();
+ return xMap->createSequenceOfFormulaTokens( *m_pCompiler,rNames);
+}
+
+
+css::uno::Sequence< css::sheet::FormulaOpCodeMapEntry >
+SAL_CALL FormulaOpCodeMapperObj::getAvailableMappings(
+ sal_Int32 nLanguage, sal_Int32 nGroups )
+{
+ FormulaCompiler::OpCodeMapPtr xMap = m_pCompiler->GetOpCodeMap( nLanguage);
+ if (!xMap)
+ throw lang::IllegalArgumentException();
+ return xMap->createSequenceOfAvailableMappings( *m_pCompiler,nGroups);
+}
+
+OUString SAL_CALL FormulaOpCodeMapperObj::getImplementationName( )
+{
+ return "simple.formula.FormulaOpCodeMapperObj";
+}
+
+uno::Sequence< OUString > SAL_CALL FormulaOpCodeMapperObj::getSupportedServiceNames( )
+{
+ return { "com.sun.star.sheet.FormulaOpCodeMapper" };
+}
+
+} // formula
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+simple_formula_FormulaOpCodeMapperObj(
+ css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const& )
+{
+ return cppu::acquire(
+ new formula::FormulaOpCodeMapperObj(std::make_unique<formula::FormulaCompiler>()));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/formula/source/core/api/grammar.cxx b/formula/source/core/api/grammar.cxx
new file mode 100644
index 000000000..3974ee28d
--- /dev/null
+++ b/formula/source/core/api/grammar.cxx
@@ -0,0 +1,87 @@
+/* -*- 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 <formula/grammar.hxx>
+#include <cassert>
+
+namespace formula {
+
+FormulaGrammar::Grammar FormulaGrammar::mapAPItoGrammar( const bool bEnglish, const bool bXML )
+{
+ Grammar eGrammar;
+ if (bEnglish && bXML)
+ eGrammar = GRAM_PODF;
+ else if (bEnglish && !bXML)
+ eGrammar = GRAM_API;
+ else if (!bEnglish && bXML)
+ eGrammar = GRAM_NATIVE_ODF;
+ else // (!bEnglish && !bXML)
+ eGrammar = GRAM_NATIVE;
+ return eGrammar;
+}
+
+bool FormulaGrammar::isSupported( const Grammar eGrammar )
+{
+ switch (eGrammar)
+ {
+ case GRAM_ODFF :
+ case GRAM_PODF :
+ case GRAM_ENGLISH :
+ case GRAM_NATIVE :
+ case GRAM_ODFF_UI :
+ case GRAM_ODFF_A1 :
+ case GRAM_PODF_UI :
+ case GRAM_PODF_A1 :
+ case GRAM_NATIVE_UI :
+ case GRAM_NATIVE_ODF :
+ case GRAM_NATIVE_XL_A1 :
+ case GRAM_NATIVE_XL_R1C1 :
+ case GRAM_ENGLISH_XL_A1 :
+ case GRAM_ENGLISH_XL_R1C1:
+ case GRAM_ENGLISH_XL_OOX :
+ case GRAM_OOXML :
+ case GRAM_API :
+ return true;
+ default:
+ return extractFormulaLanguage( eGrammar) == GRAM_EXTERNAL;
+ }
+}
+
+FormulaGrammar::Grammar FormulaGrammar::setEnglishBit( const Grammar eGrammar, const bool bEnglish )
+{
+ if (bEnglish)
+ return static_cast<Grammar>( eGrammar | kEnglishBit);
+ else
+ return static_cast<Grammar>( eGrammar & ~kEnglishBit);
+}
+
+FormulaGrammar::Grammar FormulaGrammar::mergeToGrammar( const Grammar eGrammar, const AddressConvention eConv )
+{
+ bool bEnglish = isEnglish( eGrammar);
+ Grammar eGram = static_cast<Grammar>(
+ extractFormulaLanguage( eGrammar) |
+ ((eConv + kConventionOffset) << kConventionShift));
+ eGram = setEnglishBit( eGram, bEnglish);
+ assert( isSupported( eGram));
+ return eGram;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/formula/source/core/api/token.cxx b/formula/source/core/api/token.cxx
new file mode 100644
index 000000000..f194a4f74
--- /dev/null
+++ b/formula/source/core/api/token.cxx
@@ -0,0 +1,2032 @@
+/* -*- 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 <algorithm>
+
+#include <string.h>
+#include <limits.h>
+#include <osl/diagnose.h>
+#include <sal/log.hxx>
+
+#include <com/sun/star/sheet/FormulaToken.hpp>
+#include <formula/errorcodes.hxx>
+#include <formula/token.hxx>
+#include <formula/tokenarray.hxx>
+#include <formula/FormulaCompiler.hxx>
+#include <formula/compiler.hxx>
+#include <svl/sharedstringpool.hxx>
+#include <memory>
+
+namespace formula
+{
+ using namespace com::sun::star;
+
+
+// --- helpers --------------------------------------------------------------
+
+static bool lcl_IsReference( OpCode eOp, StackVar eType )
+{
+ return
+ (eOp == ocPush && (eType == svSingleRef || eType == svDoubleRef))
+ || (eOp == ocColRowNameAuto && eType == svDoubleRef)
+ || (eOp == ocColRowName && eType == svSingleRef)
+ || (eOp == ocMatRef && eType == svSingleRef)
+ ;
+}
+
+// --- class FormulaToken --------------------------------------------------------
+
+FormulaToken::FormulaToken( StackVar eTypeP, OpCode e ) :
+ eOp(e), eType( eTypeP ), mnRefCnt(0)
+{
+}
+
+FormulaToken::FormulaToken( const FormulaToken& r ) :
+ eOp(r.eOp), eType( r.eType ), mnRefCnt(0)
+{
+}
+
+FormulaToken::~FormulaToken()
+{
+}
+
+bool FormulaToken::IsFunction() const
+{
+ return (eOp != ocPush && eOp != ocBad && eOp != ocColRowName &&
+ eOp != ocColRowNameAuto && eOp != ocName && eOp != ocDBArea &&
+ eOp != ocTableRef &&
+ (GetByte() != 0 // x parameters
+ || (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR) // no parameter
+ || FormulaCompiler::IsOpCodeJumpCommand( eOp ) // @ jump commands
+ || (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR) // one parameter
+ || (SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR) // x parameters (cByte==0 in
+ // FuncAutoPilot)
+ || eOp == ocMacro || eOp == ocExternal // macros, AddIns
+ || eOp == ocAnd || eOp == ocOr // former binary, now x parameters
+ || (eOp >= ocInternalBegin && eOp <= ocInternalEnd) // internal
+ ));
+}
+
+
+sal_uInt8 FormulaToken::GetParamCount() const
+{
+ if ( eOp < SC_OPCODE_STOP_DIV && eOp != ocExternal && eOp != ocMacro &&
+ !FormulaCompiler::IsOpCodeJumpCommand( eOp ) &&
+ eOp != ocPercentSign )
+ return 0; // parameters and specials
+ // ocIf... jump commands not for FAP, have cByte then
+//2do: bool parameter whether FAP or not?
+ else if (GetByte())
+ return GetByte(); // all functions, also ocExternal and ocMacro
+ else if (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP && eOp != ocAnd && eOp != ocOr)
+ return 2; // binary operators, compiler checked; OR and AND legacy but are functions
+ else if ((SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP) || eOp == ocPercentSign)
+ return 1; // unary operators, compiler checked
+ else if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR)
+ return 0; // no parameter
+ else if (FormulaCompiler::IsOpCodeJumpCommand( eOp ))
+ return 1; // only the condition counts as parameter
+ else
+ return 0; // all the rest, no Parameter, or
+ // if so then it should be in cByte
+}
+
+bool FormulaToken::IsExternalRef() const
+{
+ bool bRet = false;
+ switch (eType)
+ {
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ case svExternalName:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+ return bRet;
+}
+
+bool FormulaToken::IsRef() const
+{
+ switch (eType)
+ {
+ case svSingleRef:
+ case svDoubleRef:
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ return true;
+ default:
+ if (eOp == ocTableRef)
+ return true;
+ }
+
+ return false;
+}
+
+bool FormulaToken::IsInForceArray() const
+{
+ ParamClass eParam = GetInForceArray();
+ return eParam == ParamClass::ForceArray || eParam == ParamClass::ReferenceOrForceArray
+ || eParam == ParamClass::ReferenceOrRefArray || eParam == ParamClass::ForceArrayReturn;
+}
+
+bool FormulaToken::operator==( const FormulaToken& rToken ) const
+{
+ // don't compare reference count!
+ return eType == rToken.eType && GetOpCode() == rToken.GetOpCode();
+}
+
+
+// --- virtual dummy methods -------------------------------------------------
+
+sal_uInt8 FormulaToken::GetByte() const
+{
+ // ok to be called for any derived class
+ return 0;
+}
+
+void FormulaToken::SetByte( sal_uInt8 )
+{
+ assert( !"virtual dummy called" );
+}
+
+ParamClass FormulaToken::GetInForceArray() const
+{
+ // ok to be called for any derived class
+ return (eOp == ocPush && eType == svMatrix) ? ParamClass::ForceArrayReturn : ParamClass::Unknown;
+}
+
+void FormulaToken::SetInForceArray( ParamClass )
+{
+ assert( !"virtual dummy called" );
+}
+
+double FormulaToken::GetDouble() const
+{
+ // This Get is worth an assert.
+ assert( !"virtual dummy called" );
+ return 0.0;
+}
+
+double & FormulaToken::GetDoubleAsReference()
+{
+ // This Get is worth an assert.
+ assert( !"virtual dummy called" );
+ static double fVal = 0.0;
+ return fVal;
+}
+
+sal_Int16 FormulaToken::GetDoubleType() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetDoubleType: virtual dummy called" );
+ return 0;
+}
+
+void FormulaToken::SetDoubleType( sal_Int16 )
+{
+ assert( !"virtual dummy called" );
+}
+
+const svl::SharedString INVALID_STRING;
+
+const svl::SharedString & FormulaToken::GetString() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetString: virtual dummy called" );
+ return INVALID_STRING; // invalid string
+}
+
+void FormulaToken::SetString( const svl::SharedString& )
+{
+ assert( !"virtual dummy called" );
+}
+
+sal_uInt16 FormulaToken::GetIndex() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetIndex: virtual dummy called" );
+ return 0;
+}
+
+void FormulaToken::SetIndex( sal_uInt16 )
+{
+ assert( !"virtual dummy called" );
+}
+
+sal_Int16 FormulaToken::GetSheet() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetSheet: virtual dummy called" );
+ return -1;
+}
+
+void FormulaToken::SetSheet( sal_Int16 )
+{
+ assert( !"virtual dummy called" );
+}
+
+sal_Unicode FormulaToken::GetChar() const
+{
+ // This Get is worth an assert.
+ assert( !"virtual dummy called" );
+ return 0;
+}
+
+short* FormulaToken::GetJump() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetJump: virtual dummy called" );
+ return nullptr;
+}
+
+
+const OUString& FormulaToken::GetExternal() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetExternal: virtual dummy called" );
+ static OUString aDummyString;
+ return aDummyString;
+}
+
+FormulaToken* FormulaToken::GetFAPOrigToken() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetFAPOrigToken: virtual dummy called" );
+ return nullptr;
+}
+
+FormulaError FormulaToken::GetError() const
+{
+ SAL_WARN( "formula.core", "FormulaToken::GetError: virtual dummy called" );
+ return FormulaError::NONE;
+}
+
+void FormulaToken::SetError( FormulaError )
+{
+ assert( !"virtual dummy called" );
+}
+
+const ScSingleRefData* FormulaToken::GetSingleRef() const
+{
+ OSL_FAIL( "FormulaToken::GetSingleRef: virtual dummy called" );
+ return nullptr;
+}
+
+ScSingleRefData* FormulaToken::GetSingleRef()
+{
+ OSL_FAIL( "FormulaToken::GetSingleRef: virtual dummy called" );
+ return nullptr;
+}
+
+const ScComplexRefData* FormulaToken::GetDoubleRef() const
+{
+ OSL_FAIL( "FormulaToken::GetDoubleRef: virtual dummy called" );
+ return nullptr;
+}
+
+ScComplexRefData* FormulaToken::GetDoubleRef()
+{
+ OSL_FAIL( "FormulaToken::GetDoubleRef: virtual dummy called" );
+ return nullptr;
+}
+
+const ScSingleRefData* FormulaToken::GetSingleRef2() const
+{
+ OSL_FAIL( "FormulaToken::GetSingleRef2: virtual dummy called" );
+ return nullptr;
+}
+
+ScSingleRefData* FormulaToken::GetSingleRef2()
+{
+ OSL_FAIL( "FormulaToken::GetSingleRef2: virtual dummy called" );
+ return nullptr;
+}
+
+const ScMatrix* FormulaToken::GetMatrix() const
+{
+ OSL_FAIL( "FormulaToken::GetMatrix: virtual dummy called" );
+ return nullptr;
+}
+
+ScMatrix* FormulaToken::GetMatrix()
+{
+ OSL_FAIL( "FormulaToken::GetMatrix: virtual dummy called" );
+ return nullptr;
+}
+
+ScJumpMatrix* FormulaToken::GetJumpMatrix() const
+{
+ OSL_FAIL( "FormulaToken::GetJumpMatrix: virtual dummy called" );
+ return nullptr;
+}
+const std::vector<ScComplexRefData>* FormulaToken::GetRefList() const
+{
+ OSL_FAIL( "FormulaToken::GetRefList: virtual dummy called" );
+ return nullptr;
+}
+
+std::vector<ScComplexRefData>* FormulaToken::GetRefList()
+{
+ OSL_FAIL( "FormulaToken::GetRefList: virtual dummy called" );
+ return nullptr;
+}
+
+bool FormulaToken::TextEqual( const FormulaToken& rToken ) const
+{
+ return *this == rToken;
+}
+
+// real implementations of virtual functions
+
+
+sal_uInt8 FormulaSpaceToken::GetByte() const { return nByte; }
+sal_Unicode FormulaSpaceToken::GetChar() const { return cChar; }
+bool FormulaSpaceToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && nByte == r.GetByte() &&
+ cChar == r.GetChar();
+}
+
+
+sal_uInt8 FormulaByteToken::GetByte() const { return nByte; }
+void FormulaByteToken::SetByte( sal_uInt8 n ) { nByte = n; }
+ParamClass FormulaByteToken::GetInForceArray() const { return eInForceArray; }
+void FormulaByteToken::SetInForceArray( ParamClass c ) { eInForceArray = c; }
+bool FormulaByteToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && nByte == r.GetByte() &&
+ eInForceArray == r.GetInForceArray();
+}
+
+
+FormulaToken* FormulaFAPToken::GetFAPOrigToken() const { return pOrigToken.get(); }
+bool FormulaFAPToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaByteToken::operator==( r ) && pOrigToken == r.GetFAPOrigToken();
+}
+
+
+short* FormulaJumpToken::GetJump() const { return pJump.get(); }
+ParamClass FormulaJumpToken::GetInForceArray() const { return eInForceArray; }
+void FormulaJumpToken::SetInForceArray( ParamClass c ) { eInForceArray = c; }
+bool FormulaJumpToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && pJump[0] == r.GetJump()[0] &&
+ memcmp( pJump.get()+1, r.GetJump()+1, pJump[0] * sizeof(short) ) == 0 &&
+ eInForceArray == r.GetInForceArray();
+}
+FormulaJumpToken::~FormulaJumpToken()
+{
+}
+
+
+bool FormulaTokenArray::AddFormulaToken(
+ const sheet::FormulaToken& rToken, svl::SharedStringPool& rSPool, ExternalReferenceHelper* /*pExtRef*/)
+{
+ bool bError = false;
+ const OpCode eOpCode = static_cast<OpCode>(rToken.OpCode); //! assuming equal values for the moment
+
+ const uno::TypeClass eClass = rToken.Data.getValueTypeClass();
+ switch ( eClass )
+ {
+ case uno::TypeClass_VOID:
+ // empty data -> use AddOpCode (does some special cases)
+ AddOpCode( eOpCode );
+ break;
+ case uno::TypeClass_DOUBLE:
+ // double is only used for "push"
+ if ( eOpCode == ocPush )
+ AddDouble( rToken.Data.get<double>() );
+ else
+ bError = true;
+ break;
+ case uno::TypeClass_LONG:
+ {
+ // long is svIndex, used for name / database area, or "byte" for spaces
+ sal_Int32 nValue = rToken.Data.get<sal_Int32>();
+ if ( eOpCode == ocDBArea )
+ Add( new formula::FormulaIndexToken( eOpCode, static_cast<sal_uInt16>(nValue) ) );
+ else if ( eOpCode == ocTableRef )
+ bError = true; /* TODO: implementation */
+ else if ( eOpCode == ocSpaces )
+ Add( new formula::FormulaByteToken( ocSpaces, static_cast<sal_uInt8>(nValue) ) );
+ else
+ bError = true;
+ }
+ break;
+ case uno::TypeClass_STRING:
+ {
+ OUString aStrVal( rToken.Data.get<OUString>() );
+ if ( eOpCode == ocPush )
+ AddString(rSPool.intern(aStrVal));
+ else if ( eOpCode == ocBad )
+ AddBad( aStrVal );
+ else if ( eOpCode == ocStringXML )
+ AddStringXML( aStrVal );
+ else if ( eOpCode == ocExternal || eOpCode == ocMacro )
+ Add( new formula::FormulaExternalToken( eOpCode, aStrVal ) );
+ else if ( eOpCode == ocWhitespace )
+ {
+ // Simply ignore empty string.
+ // Convention is one character repeated.
+ if (!aStrVal.isEmpty())
+ Add( new formula::FormulaSpaceToken( static_cast<sal_uInt8>(aStrVal.getLength()), aStrVal[0]));
+ }
+ else
+ bError = true; // unexpected string: don't know what to do with it
+ }
+ break;
+ default:
+ bError = true;
+ } // switch ( eClass )
+ return bError;
+}
+
+bool FormulaTokenArray::Fill(
+ const uno::Sequence<sheet::FormulaToken>& rSequence,
+ svl::SharedStringPool& rSPool, ExternalReferenceHelper* pExtRef )
+{
+ bool bError = false;
+ const sal_Int32 nCount = rSequence.getLength();
+ for (sal_Int32 nPos=0; nPos<nCount; nPos++)
+ {
+ bool bOneError = AddFormulaToken(rSequence[nPos], rSPool, pExtRef);
+ if (bOneError)
+ {
+ AddOpCode( ocErrName); // add something that indicates an error
+ bError = true;
+ }
+ }
+ return bError;
+}
+
+void FormulaTokenArray::DelRPN()
+{
+ if( nRPN )
+ {
+ FormulaToken** p = pRPN;
+ for( sal_uInt16 i = 0; i < nRPN; i++ )
+ {
+ (*p++)->DecRef();
+ }
+ delete [] pRPN;
+ }
+ pRPN = nullptr;
+ nRPN = 0;
+}
+
+FormulaToken* FormulaTokenArray::FirstToken() const
+{
+ if (!pCode || nLen == 0)
+ return nullptr;
+ return pCode[0];
+}
+
+FormulaToken* FormulaTokenArray::PeekPrev( sal_uInt16 & nIdx ) const
+{
+ if (0 < nIdx && nIdx <= nLen)
+ return pCode[--nIdx];
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArray::FirstRPNToken() const
+{
+ if (!pRPN || nRPN == 0)
+ return nullptr;
+ return pRPN[0];
+}
+
+FormulaToken* FormulaTokenArray::LastRPNToken() const
+{
+ if (!pRPN || nRPN == 0)
+ return nullptr;
+ return pRPN[nRPN - 1];
+}
+
+bool FormulaTokenArray::HasReferences() const
+{
+ for (auto i: Tokens())
+ {
+ if (i->IsRef())
+ return true;
+ }
+
+ for (auto i: RPNTokens())
+ {
+ if (i->IsRef())
+ return true;
+ }
+
+ return false;
+}
+
+bool FormulaTokenArray::HasExternalRef() const
+{
+ for (auto i: Tokens())
+ {
+ if (i->IsExternalRef())
+ return true;
+ }
+ return false;
+}
+
+bool FormulaTokenArray::HasOpCode( OpCode eOp ) const
+{
+ for (auto i: Tokens())
+ {
+ if (i->GetOpCode() == eOp)
+ return true;
+ }
+ return false;
+}
+
+bool FormulaTokenArray::HasOpCodeRPN( OpCode eOp ) const
+{
+ for (auto i: RPNTokens())
+ {
+ if (i->GetOpCode() == eOp)
+ return true;
+ }
+ return false;
+}
+
+bool FormulaTokenArray::HasNameOrColRowName() const
+{
+ for (auto i: Tokens())
+ {
+ if (i->GetType() == svIndex || i->GetOpCode() == ocColRowName )
+ return true;
+ }
+ return false;
+}
+
+bool FormulaTokenArray::HasOpCodes(const unordered_opcode_set& rOpCodes) const
+{
+ for (auto i: Tokens())
+ {
+ if (rOpCodes.count(i->GetOpCode()) > 0)
+ return true;
+ }
+
+ return false;
+}
+
+FormulaTokenArray::FormulaTokenArray() :
+ pRPN(nullptr),
+ nLen(0),
+ nRPN(0),
+ nError(FormulaError::NONE),
+ nMode(ScRecalcMode::NORMAL),
+ bHyperLink(false),
+ mbFromRangeName(false),
+ mbShareable(true),
+ mbFinalized(false)
+{
+}
+
+FormulaTokenArray::FormulaTokenArray( const FormulaTokenArray& rArr )
+{
+ Assign( rArr );
+}
+
+FormulaTokenArray::FormulaTokenArray( FormulaTokenArray&& rArr )
+{
+ Move( std::move(rArr) );
+}
+
+FormulaTokenArray::~FormulaTokenArray()
+{
+ FormulaTokenArray::Clear();
+}
+
+void FormulaTokenArray::Finalize()
+{
+ if( nLen && !mbFinalized )
+ {
+ // Add() overallocates, so reallocate to the minimum needed size.
+ std::unique_ptr<FormulaToken*[]> newCode(new FormulaToken*[ nLen ]);
+ std::copy(&pCode[0], &pCode[nLen], newCode.get());
+ pCode = std::move( newCode );
+ mbFinalized = true;
+ }
+}
+
+void FormulaTokenArray::Assign( const FormulaTokenArray& r )
+{
+ nLen = r.nLen;
+ nRPN = r.nRPN;
+ nError = r.nError;
+ nMode = r.nMode;
+ bHyperLink = r.bHyperLink;
+ mbFromRangeName = r.mbFromRangeName;
+ mbShareable = r.mbShareable;
+ mbFinalized = r.mbFinalized;
+ pCode = nullptr;
+ pRPN = nullptr;
+ FormulaToken** pp;
+ if( nLen )
+ {
+ pCode.reset(new FormulaToken*[ nLen ]);
+ pp = pCode.get();
+ memcpy( pp, r.pCode.get(), nLen * sizeof( FormulaToken* ) );
+ for( sal_uInt16 i = 0; i < nLen; i++ )
+ (*pp++)->IncRef();
+ mbFinalized = true;
+ }
+ if( nRPN )
+ {
+ pp = pRPN = new FormulaToken*[ nRPN ];
+ memcpy( pp, r.pRPN, nRPN * sizeof( FormulaToken* ) );
+ for( sal_uInt16 i = 0; i < nRPN; i++ )
+ (*pp++)->IncRef();
+ }
+}
+
+void FormulaTokenArray::Move( FormulaTokenArray&& r )
+{
+ pCode = std::move(r.pCode);
+ pRPN = r.pRPN;
+ r.pRPN = nullptr;
+ nLen = r.nLen;
+ r.nLen = 0;
+ nRPN = r.nRPN;
+ r.nRPN = 0;
+ nError = r.nError;
+ nMode = r.nMode;
+ bHyperLink = r.bHyperLink;
+ mbFromRangeName = r.mbFromRangeName;
+ mbShareable = r.mbShareable;
+ mbFinalized = r.mbFinalized;
+}
+
+/// Optimisation for efficiently creating StringXML placeholders
+void FormulaTokenArray::Assign( sal_uInt16 nCode, FormulaToken **pTokens )
+{
+ assert( nLen == 0 );
+ assert( pCode == nullptr );
+
+ nLen = nCode;
+ pCode.reset(new FormulaToken*[ nLen ]);
+ mbFinalized = true;
+
+ for( sal_uInt16 i = 0; i < nLen; i++ )
+ {
+ FormulaToken *t = pTokens[ i ];
+ assert( t->GetOpCode() == ocStringXML );
+ pCode[ i ] = t;
+ t->IncRef();
+ }
+}
+
+FormulaTokenArray& FormulaTokenArray::operator=( const FormulaTokenArray& rArr )
+{
+ if(this == &rArr)
+ return *this;
+
+ Clear();
+ Assign( rArr );
+ return *this;
+}
+
+FormulaTokenArray& FormulaTokenArray::operator=( FormulaTokenArray&& rArr )
+{
+ Clear();
+ Move( std::move(rArr) );
+ return *this;
+}
+
+void FormulaTokenArray::Clear()
+{
+ if( nRPN ) DelRPN();
+ if( pCode )
+ {
+ FormulaToken** p = pCode.get();
+ for( sal_uInt16 i = 0; i < nLen; i++ )
+ {
+ (*p++)->DecRef();
+ }
+ pCode.reset();
+ }
+ pRPN = nullptr;
+ nError = FormulaError::NONE;
+ nLen = nRPN = 0;
+ bHyperLink = false;
+ mbFromRangeName = false;
+ mbShareable = true;
+ mbFinalized = false;
+ ClearRecalcMode();
+}
+
+void FormulaTokenArray::CheckToken( const FormulaToken& /*r*/ )
+{
+ // Do nothing.
+}
+
+void FormulaTokenArray::CheckAllRPNTokens()
+{
+ if( nRPN )
+ {
+ FormulaToken** p = pRPN;
+ for( sal_uInt16 i = 0; i < nRPN; i++ )
+ {
+ CheckToken( *p[ i ] );
+ }
+ }
+}
+
+FormulaToken* FormulaTokenArray::AddToken( const FormulaToken& r )
+{
+ return Add( r.Clone() );
+}
+
+FormulaToken* FormulaTokenArray::MergeArray( )
+{
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArray::ReplaceToken( sal_uInt16 nOffset, FormulaToken* t,
+ FormulaTokenArray::ReplaceMode eMode )
+{
+ if (nOffset < nLen)
+ {
+ CheckToken(*t);
+ t->IncRef();
+ FormulaToken* p = pCode[nOffset];
+ pCode[nOffset] = t;
+ if (eMode == CODE_AND_RPN && p->GetRef() > 1)
+ {
+ for (sal_uInt16 i=0; i < nRPN; ++i)
+ {
+ if (pRPN[i] == p)
+ {
+ t->IncRef();
+ pRPN[i] = t;
+ p->DecRef();
+ if (p->GetRef() == 1)
+ break; // for
+ }
+ }
+ }
+ p->DecRef(); // may be dead now
+ return t;
+ }
+ else
+ {
+ t->DeleteIfZeroRef();
+ return nullptr;
+ }
+}
+
+sal_uInt16 FormulaTokenArray::RemoveToken( sal_uInt16 nOffset, sal_uInt16 nCount )
+{
+ if (nOffset < nLen)
+ {
+ SAL_WARN_IF( nOffset + nCount > nLen, "formula.core",
+ "FormulaTokenArray::RemoveToken - nOffset " << nOffset << " + nCount " << nCount << " > nLen " << nLen);
+ const sal_uInt16 nStop = ::std::min( static_cast<sal_uInt16>(nOffset + nCount), nLen);
+ nCount = nStop - nOffset;
+ for (sal_uInt16 j = nOffset; j < nStop; ++j)
+ {
+ FormulaToken* p = pCode[j];
+ if (p->GetRef() > 1)
+ {
+ for (sal_uInt16 i=0; i < nRPN; ++i)
+ {
+ if (pRPN[i] == p)
+ {
+ // Shift remaining tokens in pRPN down.
+ for (sal_uInt16 x=i+1; x < nRPN; ++x)
+ {
+ pRPN[x-1] = pRPN[x];
+ }
+ --nRPN;
+
+ p->DecRef();
+ if (p->GetRef() == 1)
+ break; // for
+ }
+ }
+ }
+ p->DecRef(); // may be dead now
+ }
+
+ // Shift remaining tokens in pCode down.
+ for (sal_uInt16 x = nStop; x < nLen; ++x)
+ {
+ pCode[x-nCount] = pCode[x];
+ }
+ nLen -= nCount;
+
+ return nCount;
+ }
+ else
+ {
+ SAL_WARN("formula.core","FormulaTokenArray::RemoveToken - nOffset " << nOffset << " >= nLen " << nLen);
+ return 0;
+ }
+}
+
+FormulaToken* FormulaTokenArray::Add( FormulaToken* t )
+{
+ assert(!mbFinalized);
+ if (mbFinalized)
+ {
+ t->DeleteIfZeroRef();
+ return nullptr;
+ }
+
+// Allocating an array of size FORMULA_MAXTOKENS is simple, but that results in relatively large
+// allocations that malloc() implementations usually do not handle as efficiently as smaller
+// sizes (not only in terms of memory usage but also speed). Since most token arrays are going
+// to be small, start with a small array and resize only if needed. Eventually Finalize() will
+// reallocate the memory to size exactly matching the requirements.
+ const size_t MAX_FAST_TOKENS = 32;
+ if( !pCode )
+ pCode.reset(new FormulaToken*[ MAX_FAST_TOKENS ]);
+ if( nLen == MAX_FAST_TOKENS )
+ {
+ FormulaToken** tmp = new FormulaToken*[ FORMULA_MAXTOKENS ];
+ std::copy(&pCode[0], &pCode[MAX_FAST_TOKENS], tmp);
+ pCode.reset(tmp);
+ }
+ if( nLen < FORMULA_MAXTOKENS - 1 )
+ {
+ CheckToken(*t);
+ pCode[ nLen++ ] = t;
+ t->IncRef();
+ if( t->GetOpCode() == ocArrayClose )
+ return MergeArray();
+ return t;
+ }
+ else
+ {
+ t->DeleteIfZeroRef();
+ if ( nLen == FORMULA_MAXTOKENS - 1 )
+ {
+ t = new FormulaByteToken( ocStop );
+ pCode[ nLen++ ] = t;
+ t->IncRef();
+ }
+ return nullptr;
+ }
+}
+
+FormulaToken* FormulaTokenArray::AddString( const svl::SharedString& rStr )
+{
+ return Add( new FormulaStringToken( rStr ) );
+}
+
+FormulaToken* FormulaTokenArray::AddDouble( double fVal )
+{
+ return Add( new FormulaDoubleToken( fVal ) );
+}
+
+void FormulaTokenArray::AddExternal( const sal_Unicode* pStr )
+{
+ AddExternal( OUString( pStr ) );
+}
+
+FormulaToken* FormulaTokenArray::AddExternal( const OUString& rStr,
+ OpCode eOp /* = ocExternal */ )
+{
+ return Add( new FormulaExternalToken( eOp, rStr ) );
+}
+
+FormulaToken* FormulaTokenArray::AddBad( const OUString& rStr )
+{
+ return Add( new FormulaStringOpToken( ocBad, svl::SharedString( rStr ) ) ); // string not interned
+}
+
+FormulaToken* FormulaTokenArray::AddStringXML( const OUString& rStr )
+{
+ return Add( new FormulaStringOpToken( ocStringXML, svl::SharedString( rStr ) ) ); // string not interned
+}
+
+
+void FormulaTokenArray::AddRecalcMode( ScRecalcMode nBits )
+{
+ const unsigned nExclusive = static_cast<sal_uInt8>(nBits & ScRecalcMode::EMask);
+ if (nExclusive)
+ {
+ unsigned nExBit;
+ if (nExclusive & (nExclusive - 1))
+ {
+ // More than one bit set, use highest priority.
+ for (nExBit = 1; (nExBit & static_cast<sal_uInt8>(ScRecalcMode::EMask)) != 0; nExBit <<= 1)
+ {
+ if (nExclusive & nExBit)
+ break;
+ }
+ }
+ else
+ {
+ // Only one bit is set.
+ nExBit = nExclusive;
+ }
+ // Set exclusive bit if priority is higher than existing.
+ if (nExBit < static_cast<sal_uInt8>(nMode & ScRecalcMode::EMask))
+ SetMaskedRecalcMode( static_cast<ScRecalcMode>(nExBit));
+ }
+ SetCombinedBitsRecalcMode( nBits );
+}
+
+
+bool FormulaTokenArray::HasMatrixDoubleRefOps() const
+{
+ if ( !pRPN || !nRPN )
+ return false;
+
+ // RPN-Interpreter simulation.
+ // Simply assumes a double as return value of each function.
+ std::unique_ptr<FormulaToken*[]> pStack(new FormulaToken* [nRPN]);
+ FormulaToken* pResult = new FormulaDoubleToken( 0.0 );
+ short sp = 0;
+ for ( auto t: RPNTokens() )
+ {
+ OpCode eOp = t->GetOpCode();
+ sal_uInt8 nParams = t->GetParamCount();
+ switch ( eOp )
+ {
+ case ocAdd :
+ case ocSub :
+ case ocMul :
+ case ocDiv :
+ case ocPow :
+ case ocPower :
+ case ocAmpersand :
+ case ocEqual :
+ case ocNotEqual :
+ case ocLess :
+ case ocGreater :
+ case ocLessEqual :
+ case ocGreaterEqual :
+ {
+ for ( sal_uInt8 k = nParams; k; k-- )
+ {
+ if ( sp >= k && pStack[sp-k]->GetType() == svDoubleRef )
+ {
+ pResult->Delete();
+ return true;
+ }
+ }
+ }
+ break;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ if ( eOp == ocPush || lcl_IsReference( eOp, t->GetType() ) )
+ pStack[sp++] = t;
+ else if (FormulaCompiler::IsOpCodeJumpCommand( eOp ))
+ { // ignore Jumps, pop previous Result (Condition)
+ if ( sp )
+ --sp;
+ }
+ else
+ { // pop parameters, push result
+ sp = sal::static_int_cast<short>( sp - nParams );
+ if ( sp < 0 )
+ {
+ SAL_WARN("formula.core", "FormulaTokenArray::HasMatrixDoubleRefOps: sp < 0" );
+ sp = 0;
+ }
+ pStack[sp++] = pResult;
+ }
+ }
+ pResult->Delete();
+
+ return false;
+}
+
+// --- Formula rewrite of a token array
+
+inline bool MissingConventionODF::isRewriteNeeded( OpCode eOp ) const
+{
+ switch (eOp)
+ {
+ case ocGammaDist:
+ case ocPoissonDist:
+ case ocAddress:
+ case ocLogInv:
+ case ocLogNormDist:
+ case ocNormDist:
+ return true;
+ case ocMissing:
+ case ocLog:
+ return isPODF(); // rewrite only for PODF
+ case ocDBCount:
+ case ocDBCount2:
+ return isODFF(); // rewrite only for ODFF
+ default:
+ return false;
+ }
+}
+
+/*
+ fdo 81596
+To be implemented yet:
+ ocExternal: ?
+ ocMacro: ?
+ ocIndex: INDEX() ?
+*/
+inline bool MissingConventionOOXML::isRewriteNeeded( OpCode eOp )
+{
+ switch (eOp)
+ {
+ case ocIf:
+
+ case ocExternal:
+ case ocEuroConvert:
+ case ocMacro:
+
+ case ocRound:
+ case ocRoundUp:
+ case ocRoundDown:
+
+ case ocIndex:
+
+ case ocCeil:
+ case ocFloor:
+
+ case ocGammaDist:
+ case ocFDist_LT:
+ case ocPoissonDist:
+ case ocNormDist:
+ case ocLogInv:
+ case ocLogNormDist:
+ case ocHypGeomDist:
+
+ case ocDBCount:
+ case ocDBCount2:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+namespace {
+
+class FormulaMissingContext
+{
+ public:
+ const FormulaToken* mpFunc;
+ int mnCurArg;
+
+ void Clear() { mpFunc = nullptr; mnCurArg = 0; }
+ inline bool AddDefaultArg( FormulaTokenArray* pNewArr, int nArg, double f ) const;
+ bool AddMissingExternal( FormulaTokenArray* pNewArr ) const;
+ bool AddMissing( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const;
+ void AddMoreArgs( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const;
+};
+
+}
+
+void FormulaMissingContext::AddMoreArgs( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const
+{
+ if ( !mpFunc )
+ return;
+
+ switch (rConv.getConvention())
+ {
+ case MissingConvention::FORMULA_MISSING_CONVENTION_ODFF:
+ case MissingConvention::FORMULA_MISSING_CONVENTION_PODF:
+ {
+ switch (mpFunc->GetOpCode())
+ {
+ case ocGammaDist:
+ if (mnCurArg == 2)
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true()
+ }
+ break;
+ case ocPoissonDist:
+ if (mnCurArg == 1)
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 3rd, Cumulative=true()
+ }
+ break;
+ case ocNormDist:
+ if ( mnCurArg == 2 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true()
+ }
+ break;
+ case ocLogInv:
+ case ocLogNormDist:
+ if ( mnCurArg == 0 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 0.0 ); // 2nd, mean = 0.0
+ }
+ if ( mnCurArg <= 1 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 3rd, standard deviation = 1.0
+ }
+ break;
+ case ocLog:
+ if ( rConv.isPODF() && mnCurArg == 0 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 10.0 ); // 2nd, basis 10
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case MissingConvention::FORMULA_MISSING_CONVENTION_OOXML:
+ {
+ switch (mpFunc->GetOpCode())
+ {
+ case ocIf:
+ if( mnCurArg == 0 )
+ {
+ // Excel needs at least two parameters in IF function
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddOpCode( ocTrue ); // 2nd, true() as function
+ pNewArr->AddOpCode( ocOpen ); // so the result is of logical type
+ pNewArr->AddOpCode( ocClose ); // and survives roundtrip
+ }
+ break;
+
+ case ocEuroConvert:
+ if ( mnCurArg == 2 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 0.0 ); // 4th, FullPrecision = false()
+ }
+ break;
+
+ case ocPoissonDist:
+ if (mnCurArg == 1)
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 3rd, Cumulative=true()
+ }
+ break;
+
+ case ocGammaDist:
+ case ocFDist_LT:
+ case ocNormDist:
+ if (mnCurArg == 2)
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 4th, Cumulative=true()
+ }
+ break;
+
+ case ocLogInv:
+ case ocLogNormDist:
+ if ( mnCurArg == 0 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 0.0 ); // 2nd, mean = 0.0
+ }
+ if ( mnCurArg <= 1 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 1.0 ); // 3rd, standard deviation = 1.0
+ }
+ break;
+
+ case ocHypGeomDist:
+ if ( mnCurArg == 3 )
+ {
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 0.0 ); // 5th, Cumulative = false()
+ }
+ break;
+
+ case ocRound:
+ case ocRoundUp:
+ case ocRoundDown:
+ if( mnCurArg == 0 )
+ {
+ // ROUND, ROUNDUP, ROUNDDOWN functions are fixed to 2 parameters in Excel
+ pNewArr->AddOpCode( ocSep );
+ pNewArr->AddDouble( 0.0 ); // 2nd, 0.0
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+ }
+
+}
+
+inline bool FormulaMissingContext::AddDefaultArg( FormulaTokenArray* pNewArr, int nArg, double f ) const
+{
+ if (mnCurArg == nArg)
+ {
+ pNewArr->AddDouble( f );
+ return true;
+ }
+ return false;
+}
+
+bool FormulaMissingContext::AddMissingExternal( FormulaTokenArray *pNewArr ) const
+{
+ // Only called for PODF, not ODFF. No need to distinguish.
+
+ const OUString &rName = mpFunc->GetExternal();
+
+ // initial (fast) checks:
+ sal_Int32 nLength = rName.getLength();
+ if (!nLength)
+ return false;
+
+ sal_Unicode nLastChar = rName[ nLength - 1];
+ if ( nLastChar != 't' && nLastChar != 'm' )
+ return false;
+
+ if (rName.equalsIgnoreAsciiCase(
+ "com.sun.star.sheet.addin.Analysis.getAccrint" ))
+ {
+ return AddDefaultArg( pNewArr, 4, 1000.0 );
+ }
+ if (rName.equalsIgnoreAsciiCase(
+ "com.sun.star.sheet.addin.Analysis.getAccrintm" ))
+ {
+ return AddDefaultArg( pNewArr, 3, 1000.0 );
+ }
+ return false;
+}
+
+bool FormulaMissingContext::AddMissing( FormulaTokenArray *pNewArr, const MissingConvention & rConv ) const
+{
+ if ( !mpFunc )
+ return false;
+
+ bool bRet = false;
+ const OpCode eOp = mpFunc->GetOpCode();
+
+ switch (rConv.getConvention())
+ {
+ case MissingConvention::FORMULA_MISSING_CONVENTION_ODFF:
+ {
+ // Add for ODFF
+ switch (eOp)
+ {
+ case ocAddress:
+ return AddDefaultArg( pNewArr, 2, 1.0 ); // abs
+ default:
+ break;
+ }
+ }
+ break;
+ case MissingConvention::FORMULA_MISSING_CONVENTION_PODF:
+ {
+ // Add for PODF
+ switch (eOp)
+ {
+ case ocAddress:
+ return AddDefaultArg( pNewArr, 2, 1.0 ); // abs
+ case ocFixed:
+ return AddDefaultArg( pNewArr, 1, 2.0 );
+ case ocBetaDist:
+ case ocBetaInv:
+ case ocPMT:
+ return AddDefaultArg( pNewArr, 3, 0.0 );
+ case ocIpmt:
+ case ocPpmt:
+ return AddDefaultArg( pNewArr, 4, 0.0 );
+ case ocPV:
+ case ocFV:
+ bRet |= AddDefaultArg( pNewArr, 2, 0.0 ); // pmt
+ bRet |= AddDefaultArg( pNewArr, 3, 0.0 ); // [fp]v
+ break;
+ case ocRate:
+ bRet |= AddDefaultArg( pNewArr, 1, 0.0 ); // pmt
+ bRet |= AddDefaultArg( pNewArr, 3, 0.0 ); // fv
+ bRet |= AddDefaultArg( pNewArr, 4, 0.0 ); // type
+ break;
+ case ocExternal:
+ return AddMissingExternal( pNewArr );
+
+ // --- more complex cases ---
+
+ case ocOffset:
+ // FIXME: rather tough
+ // if arg 3 (height) omitted, export arg1 (rows)
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ case MissingConvention::FORMULA_MISSING_CONVENTION_OOXML:
+ {
+ switch (eOp)
+ {
+ case ocExternal:
+ return AddMissingExternal( pNewArr );
+ default:
+ break;
+ }
+ }
+ break;
+ }
+
+ return bRet;
+}
+
+bool FormulaTokenArray::NeedsPodfRewrite( const MissingConventionODF & rConv )
+{
+ for ( auto i: Tokens() )
+ {
+ if ( rConv.isRewriteNeeded( i->GetOpCode()))
+ return true;
+ }
+ return false;
+}
+
+bool FormulaTokenArray::NeedsOoxmlRewrite()
+{
+ for ( auto i: Tokens() )
+ {
+ if ( MissingConventionOOXML::isRewriteNeeded( i->GetOpCode()))
+ return true;
+ }
+ return false;
+}
+
+
+FormulaTokenArray * FormulaTokenArray::RewriteMissing( const MissingConvention & rConv )
+{
+ const size_t nAlloc = 256;
+ FormulaMissingContext aCtx[ nAlloc ];
+
+ /* TODO: with some effort we might be able to merge the two almost
+ * identical function stacks into one and generalize things, otherwise
+ * adding yet another "omit argument" would be copypasta. */
+
+ int aOpCodeAddressStack[ nAlloc ]; // use of ADDRESS() function
+ const int nOmitAddressArg = 3; // ADDRESS() 4th parameter A1/R1C1
+
+ int aOpCodeDcountStack[ nAlloc ]; // use of DCOUNT()/DCOUNTA() function
+ const int nOmitDcountArg = 1; // DCOUNT() and DCOUNTA() 2nd parameter DatabaseField if 0
+
+ sal_uInt16 nTokens = GetLen() + 1;
+ FormulaMissingContext* pCtx = (nAlloc < nTokens ? new FormulaMissingContext[nTokens] : &aCtx[0]);
+ int* pOcas = (nAlloc < nTokens ? new int[nTokens] : &aOpCodeAddressStack[0]);
+ int* pOcds = (nAlloc < nTokens ? new int[nTokens] : &aOpCodeDcountStack[0]);
+ // Never go below 0, never use 0, mpFunc always NULL.
+ pCtx[0].Clear();
+ int nFn = 0;
+ int nOcas = 0;
+ int nOcds = 0;
+
+ FormulaTokenArray *pNewArr = new FormulaTokenArray;
+ // At least ScRecalcMode::ALWAYS needs to be set.
+ pNewArr->AddRecalcMode( GetRecalcMode());
+
+ FormulaTokenArrayPlainIterator aIter(*this);
+ for ( FormulaToken *pCur = aIter.First(); pCur; pCur = aIter.Next() )
+ {
+ bool bAdd = true;
+ // Don't write the expression of the new inserted ADDRESS() parameter.
+ // Do NOT omit the new second parameter of INDIRECT() though. If that
+ // was done for both, INDIRECT() actually could calculate different and
+ // valid (but wrong) results with the then changed return value of
+ // ADDRESS(). Better let it generate an error instead.
+ for (int i = nOcas; i-- > 0 && bAdd; )
+ {
+ if (pCtx[ pOcas[ i ] ].mnCurArg == nOmitAddressArg)
+ {
+ // Omit everything except a trailing separator, the leading
+ // separator is omitted below. The other way around would leave
+ // an extraneous separator if no parameter followed.
+ if (pOcas[ i ] != nFn || pCur->GetOpCode() != ocSep)
+ bAdd = false;
+ }
+ }
+ // Strip the 2nd argument (leaving empty) of DCOUNT() and DCOUNTA() if
+ // it is 0.
+ for (int i = nOcds; i-- > 0 && bAdd; )
+ {
+ if (pCtx[ pOcds[ i ] ].mnCurArg == nOmitDcountArg)
+ {
+ // Omit only a literal 0 value, nothing else.
+ if (pOcds[ i ] == nFn && pCur->GetOpCode() == ocPush && pCur->GetType() == svDouble &&
+ pCur->GetDouble() == 0.0)
+ {
+ // No other expression, between separators.
+ FormulaToken* p = aIter.PeekPrevNoSpaces();
+ if (p && p->GetOpCode() == ocSep)
+ {
+ p = aIter.PeekNextNoSpaces();
+ if (p && p->GetOpCode() == ocSep)
+ bAdd = false;
+ }
+ }
+ }
+ }
+ switch ( pCur->GetOpCode() )
+ {
+ case ocOpen:
+ {
+ ++nFn; // all following operations on _that_ function
+ pCtx[ nFn ].mpFunc = aIter.PeekPrevNoSpaces();
+ pCtx[ nFn ].mnCurArg = 0;
+ if (rConv.isPODF() && pCtx[ nFn ].mpFunc && pCtx[ nFn ].mpFunc->GetOpCode() == ocAddress)
+ pOcas[ nOcas++ ] = nFn; // entering ADDRESS() if PODF
+ else if ((rConv.isODFF() || rConv.isOOXML()) && pCtx[ nFn ].mpFunc)
+ {
+ OpCode eOp = pCtx[ nFn ].mpFunc->GetOpCode();
+ if (eOp == ocDBCount || eOp == ocDBCount2)
+ pOcds[ nOcds++ ] = nFn; // entering DCOUNT() or DCOUNTA() if ODFF or OOXML
+ }
+ }
+ break;
+ case ocClose:
+ pCtx[ nFn ].AddMoreArgs( pNewArr, rConv );
+ SAL_WARN_IF(nFn <= 0, "formula.core", "FormulaTokenArray::RewriteMissing: underflow");
+ if (nOcas > 0 && pOcas[ nOcas-1 ] == nFn)
+ --nOcas; // leaving ADDRESS()
+ else if (nOcds > 0 && pOcds[ nOcds-1 ] == nFn)
+ --nOcds; // leaving DCOUNT() or DCOUNTA()
+ if (nFn > 0)
+ --nFn;
+ break;
+ case ocSep:
+ pCtx[ nFn ].mnCurArg++;
+ // Omit leading separator of ADDRESS() parameter.
+ if (nOcas && pOcas[ nOcas-1 ] == nFn && pCtx[ nFn ].mnCurArg == nOmitAddressArg)
+ {
+ bAdd = false;
+ }
+ break;
+ case ocMissing:
+ if ( bAdd )
+ bAdd = !pCtx[ nFn ].AddMissing( pNewArr, rConv );
+ break;
+ default:
+ break;
+ }
+ if (bAdd)
+ {
+ OpCode eOp = pCur->GetOpCode();
+ if ( ( eOp == ocCeil || eOp == ocFloor ||
+ ( eOp == ocLogNormDist && pCur->GetByte() == 4 ) ) &&
+ rConv.getConvention() == MissingConvention::FORMULA_MISSING_CONVENTION_OOXML )
+ {
+ switch ( eOp )
+ {
+ case ocCeil :
+ eOp = ocCeil_Math;
+ break;
+ case ocFloor :
+ eOp = ocFloor_Math;
+ break;
+ case ocLogNormDist :
+ eOp = ocLogNormDist_MS;
+ break;
+ default :
+ eOp = ocNone;
+ break;
+ }
+ FormulaToken *pToken = new FormulaToken( svByte, eOp );
+ pNewArr->Add( pToken );
+ }
+ else if ( eOp == ocHypGeomDist &&
+ rConv.getConvention() == MissingConvention::FORMULA_MISSING_CONVENTION_OOXML )
+ {
+ FormulaToken *pToken = new FormulaToken( svByte, ocHypGeomDist_MS );
+ pNewArr->Add( pToken );
+ }
+ else
+ pNewArr->AddToken( *pCur );
+ }
+ }
+
+ if (pOcds != &aOpCodeDcountStack[0])
+ delete [] pOcds;
+ if (pOcas != &aOpCodeAddressStack[0])
+ delete [] pOcas;
+ if (pCtx != &aCtx[0])
+ delete [] pCtx;
+
+ return pNewArr;
+}
+
+namespace {
+inline bool isWhitespace( OpCode eOp ) { return eOp == ocSpaces || eOp == ocWhitespace; }
+}
+
+bool FormulaTokenArray::MayReferenceFollow()
+{
+ if ( !pCode || nLen <= 0 )
+ return false;
+
+ // ignore trailing spaces
+ sal_uInt16 i = nLen - 1;
+ while (i > 0 && isWhitespace( pCode[i]->GetOpCode()))
+ {
+ --i;
+ }
+ if (i > 0 || !isWhitespace( pCode[i]->GetOpCode()))
+ {
+ OpCode eOp = pCode[i]->GetOpCode();
+ if ( (SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP ) ||
+ (SC_OPCODE_START_UN_OP <= eOp && eOp < SC_OPCODE_STOP_UN_OP ) ||
+ eOp == SC_OPCODE_OPEN || eOp == SC_OPCODE_SEP )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+FormulaToken* FormulaTokenArray::AddOpCode( OpCode eOp )
+{
+ FormulaToken* pRet = nullptr;
+ switch ( eOp )
+ {
+ case ocOpen:
+ case ocClose:
+ case ocSep:
+ case ocArrayOpen:
+ case ocArrayClose:
+ case ocArrayRowSep:
+ case ocArrayColSep:
+ pRet = new FormulaToken( svSep,eOp );
+ break;
+ case ocIf:
+ case ocIfError:
+ case ocIfNA:
+ case ocChoose:
+ {
+ short nJump[FORMULA_MAXJUMPCOUNT + 1];
+ if ( eOp == ocIf )
+ nJump[ 0 ] = 3;
+ else if ( eOp == ocChoose )
+ nJump[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
+ else
+ nJump[ 0 ] = 2;
+ pRet = new FormulaJumpToken( eOp, nJump );
+ }
+ break;
+ default:
+ pRet = new FormulaByteToken( eOp, 0, ParamClass::Unknown );
+ break;
+ }
+ return Add( pRet );
+}
+
+void FormulaTokenArray::ReinternStrings( svl::SharedStringPool& rPool )
+{
+ for (auto i: Tokens())
+ {
+ switch (i->GetType())
+ {
+ case svString:
+ i->SetString( rPool.intern( i->GetString().getString()));
+ break;
+ default:
+ ; // nothing
+ }
+ }
+}
+
+
+/*----------------------------------------------------------------------*/
+
+FormulaTokenIterator::Item::Item(const FormulaTokenArray* pArray, short pc, short stop) :
+ pArr(pArray), nPC(pc), nStop(stop)
+{
+}
+
+FormulaTokenIterator::FormulaTokenIterator( const FormulaTokenArray& rArr )
+{
+ Push( &rArr );
+}
+
+FormulaTokenIterator::~FormulaTokenIterator()
+{
+}
+
+void FormulaTokenIterator::Push( const FormulaTokenArray* pArr )
+{
+ FormulaTokenIterator::Item item(pArr, -1, SHRT_MAX);
+
+ maStack.push_back(item);
+}
+
+void FormulaTokenIterator::Pop()
+{
+ maStack.pop_back();
+}
+
+void FormulaTokenIterator::Reset()
+{
+ while( maStack.size() > 1 )
+ maStack.pop_back();
+
+ maStack.back().nPC = -1;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::GetNextName()
+{
+ if( mpFTA->GetArray() )
+ {
+ while ( mnIndex < mpFTA->GetLen() )
+ {
+ FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ];
+ if( t->GetType() == svIndex )
+ return t;
+ }
+ }
+ return nullptr;
+}
+
+const FormulaToken* FormulaTokenIterator::Next()
+{
+ const FormulaToken* t = GetNonEndOfPathToken( ++maStack.back().nPC );
+ if( !t && maStack.size() > 1 )
+ {
+ Pop();
+ t = Next();
+ }
+ return t;
+}
+
+const FormulaToken* FormulaTokenIterator::PeekNextOperator()
+{
+ const FormulaToken* t = nullptr;
+ short nIdx = maStack.back().nPC;
+ for (;;)
+ {
+ t = GetNonEndOfPathToken( ++nIdx);
+ if (t == nullptr || t->GetOpCode() != ocPush)
+ break; // ignore operands
+ }
+ if (!t && maStack.size() > 1)
+ {
+ FormulaTokenIterator::Item aHere = maStack.back();
+ maStack.pop_back();
+ t = PeekNextOperator();
+ maStack.push_back(aHere);
+ }
+ return t;
+}
+
+//! The nPC counts after a Push() are -1
+
+void FormulaTokenIterator::Jump( short nStart, short nNext, short nStop )
+{
+ maStack.back().nPC = nNext;
+ if( nStart != nNext )
+ {
+ Push( maStack.back().pArr );
+ maStack.back().nPC = nStart;
+ maStack.back().nStop = nStop;
+ }
+}
+
+void FormulaTokenIterator::ReInit( const FormulaTokenArray& rArr )
+{
+ maStack.clear();
+ Push( &rArr );
+}
+
+const FormulaToken* FormulaTokenIterator::GetNonEndOfPathToken( short nIdx ) const
+{
+ FormulaTokenIterator::Item cur = maStack.back();
+
+ if (nIdx < cur.pArr->GetCodeLen() && nIdx < cur.nStop)
+ {
+ const FormulaToken* t = cur.pArr->GetCode()[ nIdx ];
+ // such an OpCode ends an IF() or CHOOSE() path
+ return (t->GetOpCode() == ocSep || t->GetOpCode() == ocClose) ? nullptr : t;
+ }
+ return nullptr;
+}
+
+bool FormulaTokenIterator::IsEndOfPath() const
+{
+ return GetNonEndOfPathToken( maStack.back().nPC + 1) == nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::GetNextReference()
+{
+ while( mnIndex < mpFTA->GetLen() )
+ {
+ FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ];
+ switch( t->GetType() )
+ {
+ case svSingleRef:
+ case svDoubleRef:
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ return t;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::GetNextColRowName()
+{
+ while( mnIndex < mpFTA->GetLen() )
+ {
+ FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ];
+ if ( t->GetOpCode() == ocColRowName )
+ return t;
+ }
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::GetNextReferenceRPN()
+{
+ while( mnIndex < mpFTA->GetCodeLen() )
+ {
+ FormulaToken* t = mpFTA->GetCode()[ mnIndex++ ];
+ switch( t->GetType() )
+ {
+ case svSingleRef:
+ case svDoubleRef:
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ return t;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::GetNextReferenceOrName()
+{
+ if( !mpFTA->GetArray() )
+ return nullptr;
+
+ while ( mnIndex < mpFTA->GetLen() )
+ {
+ FormulaToken* t = mpFTA->GetArray()[ mnIndex++ ];
+ switch( t->GetType() )
+ {
+ case svSingleRef:
+ case svDoubleRef:
+ case svIndex:
+ case svExternalSingleRef:
+ case svExternalDoubleRef:
+ case svExternalName:
+ return t;
+ default:
+ {
+ // added to avoid warnings
+ }
+ }
+ }
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::Next()
+{
+ if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() )
+ return mpFTA->GetArray()[ mnIndex++ ];
+ else
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::NextNoSpaces()
+{
+ if( mpFTA->GetArray() )
+ {
+ while ((mnIndex < mpFTA->GetLen()) && isWhitespace( mpFTA->GetArray()[ mnIndex ]->GetOpCode()))
+ ++mnIndex;
+ if( mnIndex < mpFTA->GetLen() )
+ return mpFTA->GetArray()[ mnIndex++ ];
+ }
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::NextRPN()
+{
+ if( mpFTA->GetCode() && mnIndex < mpFTA->GetCodeLen() )
+ return mpFTA->GetCode()[ mnIndex++ ];
+ else
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::PrevRPN()
+{
+ if( mpFTA->GetCode() && mnIndex )
+ return mpFTA->GetCode()[ --mnIndex ];
+ else
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::PeekNext()
+{
+ if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() )
+ return mpFTA->GetArray()[ mnIndex ];
+ else
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::PeekNextNoSpaces() const
+{
+ if( mpFTA->GetArray() && mnIndex < mpFTA->GetLen() )
+ {
+ sal_uInt16 j = mnIndex;
+ while (j < mpFTA->GetLen() && isWhitespace( mpFTA->GetArray()[j]->GetOpCode()))
+ j++;
+ if ( j < mpFTA->GetLen() )
+ return mpFTA->GetArray()[ j ];
+ else
+ return nullptr;
+ }
+ else
+ return nullptr;
+}
+
+FormulaToken* FormulaTokenArrayPlainIterator::PeekPrevNoSpaces() const
+{
+ if( mpFTA->GetArray() && mnIndex > 1 )
+ {
+ sal_uInt16 j = mnIndex - 2;
+ while (isWhitespace( mpFTA->GetArray()[j]->GetOpCode()) && j > 0 )
+ j--;
+ if (j > 0 || !isWhitespace( mpFTA->GetArray()[j]->GetOpCode()))
+ return mpFTA->GetArray()[ j ];
+ else
+ return nullptr;
+ }
+ else
+ return nullptr;
+}
+
+void FormulaTokenArrayPlainIterator::AfterRemoveToken( sal_uInt16 nOffset, sal_uInt16 nCount )
+{
+ const sal_uInt16 nStop = std::min( static_cast<sal_uInt16>(nOffset + nCount), mpFTA->GetLen());
+
+ if (mnIndex >= nOffset)
+ {
+ if (mnIndex < nStop)
+ mnIndex = nOffset + 1;
+ else
+ mnIndex -= nStop - nOffset;
+ }
+}
+
+// real implementations of virtual functions
+
+
+double FormulaDoubleToken::GetDouble() const { return fDouble; }
+double & FormulaDoubleToken::GetDoubleAsReference() { return fDouble; }
+
+sal_Int16 FormulaDoubleToken::GetDoubleType() const
+{
+ // This is a plain double value without type information, don't emit a
+ // warning via FormulaToken::GetDoubleType().
+ return 0;
+}
+
+bool FormulaDoubleToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && fDouble == r.GetDouble();
+}
+
+sal_Int16 FormulaTypedDoubleToken::GetDoubleType() const
+{
+ return mnType;
+}
+
+void FormulaTypedDoubleToken::SetDoubleType( sal_Int16 nType )
+{
+ mnType = nType;
+}
+
+bool FormulaTypedDoubleToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaDoubleToken::operator==( r ) && mnType == r.GetDoubleType();
+}
+
+FormulaStringToken::FormulaStringToken( const svl::SharedString& r ) :
+ FormulaToken( svString ), maString( r )
+{
+}
+
+FormulaStringToken::FormulaStringToken( const FormulaStringToken& r ) :
+ FormulaToken( r ), maString( r.maString ) {}
+
+FormulaToken* FormulaStringToken::Clone() const
+{
+ return new FormulaStringToken(*this);
+}
+
+const svl::SharedString & FormulaStringToken::GetString() const
+{
+ return maString;
+}
+
+void FormulaStringToken::SetString( const svl::SharedString& rStr )
+{
+ maString = rStr;
+}
+
+bool FormulaStringToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && maString == r.GetString();
+}
+
+FormulaStringOpToken::FormulaStringOpToken( OpCode e, const svl::SharedString& r ) :
+ FormulaByteToken( e, 0, svString, ParamClass::Unknown ), maString( r ) {}
+
+FormulaStringOpToken::FormulaStringOpToken( const FormulaStringOpToken& r ) :
+ FormulaByteToken( r ), maString( r.maString ) {}
+
+FormulaToken* FormulaStringOpToken::Clone() const
+{
+ return new FormulaStringOpToken(*this);
+}
+
+const svl::SharedString & FormulaStringOpToken::GetString() const
+{
+ return maString;
+}
+
+void FormulaStringOpToken::SetString( const svl::SharedString& rStr )
+{
+ maString = rStr;
+}
+
+bool FormulaStringOpToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaByteToken::operator==( r ) && maString == r.GetString();
+}
+
+sal_uInt16 FormulaIndexToken::GetIndex() const { return nIndex; }
+void FormulaIndexToken::SetIndex( sal_uInt16 n ) { nIndex = n; }
+sal_Int16 FormulaIndexToken::GetSheet() const { return mnSheet; }
+void FormulaIndexToken::SetSheet( sal_Int16 n ) { mnSheet = n; }
+bool FormulaIndexToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) && nIndex == r.GetIndex() &&
+ mnSheet == r.GetSheet();
+}
+const OUString& FormulaExternalToken::GetExternal() const { return aExternal; }
+bool FormulaExternalToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaByteToken::operator==( r ) && aExternal == r.GetExternal();
+}
+
+
+FormulaError FormulaErrorToken::GetError() const { return nError; }
+void FormulaErrorToken::SetError( FormulaError nErr ) { nError = nErr; }
+bool FormulaErrorToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r ) &&
+ nError == static_cast< const FormulaErrorToken & >(r).GetError();
+}
+double FormulaMissingToken::GetDouble() const { return 0.0; }
+
+const svl::SharedString & FormulaMissingToken::GetString() const
+{
+ return svl::SharedString::getEmptyString();
+}
+
+bool FormulaMissingToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r );
+}
+
+
+bool FormulaUnknownToken::operator==( const FormulaToken& r ) const
+{
+ return FormulaToken::operator==( r );
+}
+
+
+} // formula
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/formula/source/core/api/vectortoken.cxx b/formula/source/core/api/vectortoken.cxx
new file mode 100644
index 000000000..c1bd52157
--- /dev/null
+++ b/formula/source/core/api/vectortoken.cxx
@@ -0,0 +1,109 @@
+/* -*- 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/.
+ */
+
+#include <formula/vectortoken.hxx>
+#include <sal/log.hxx>
+
+namespace formula {
+
+VectorRefArray::VectorRefArray() :
+ mpNumericArray(nullptr),
+ mpStringArray(nullptr),
+ mbValid(true) {}
+
+VectorRefArray::VectorRefArray( InitInvalid ) :
+ mpNumericArray(nullptr),
+ mpStringArray(nullptr),
+ mbValid(false) {}
+
+VectorRefArray::VectorRefArray( const double* pArray ) :
+ mpNumericArray(pArray),
+ mpStringArray(nullptr),
+ mbValid(true) {}
+
+VectorRefArray::VectorRefArray( rtl_uString** pArray ) :
+ mpNumericArray(nullptr),
+ mpStringArray(pArray),
+ mbValid(true) {}
+
+VectorRefArray::VectorRefArray( const double* pNumArray, rtl_uString** pStrArray ) :
+ mpNumericArray(pNumArray),
+ mpStringArray(pStrArray),
+ mbValid(true) {}
+
+bool VectorRefArray::isValid() const
+{
+ return mbValid;
+}
+
+SingleVectorRefToken::SingleVectorRefToken( const VectorRefArray& rArray, size_t nArrayLength ) :
+ FormulaToken(svSingleVectorRef, ocPush), maArray(rArray), mnArrayLength(nArrayLength)
+{
+ SAL_INFO("formula.core", "Created SingleVectorRefToken nArrayLength=" << nArrayLength);
+}
+
+FormulaToken* SingleVectorRefToken::Clone() const
+{
+ return new SingleVectorRefToken(maArray, mnArrayLength);
+}
+
+const VectorRefArray& SingleVectorRefToken::GetArray() const
+{
+ return maArray;
+}
+
+size_t SingleVectorRefToken::GetArrayLength() const
+{
+ return mnArrayLength;
+}
+
+DoubleVectorRefToken::DoubleVectorRefToken(
+ std::vector<VectorRefArray>&& rArrays, size_t nArrayLength,
+ size_t nRefRowSize, bool bStartFixed, bool bEndFixed ) :
+ FormulaToken(svDoubleVectorRef, ocPush),
+ maArrays(std::move(rArrays)), mnArrayLength(nArrayLength),
+ mnRefRowSize(nRefRowSize), mbStartFixed(bStartFixed), mbEndFixed(bEndFixed)
+{
+ SAL_INFO("formula.core", "Created DoubleVectorRefToken nArrayLength=" << nArrayLength);
+}
+
+FormulaToken* DoubleVectorRefToken::Clone() const
+{
+ return new DoubleVectorRefToken(
+ std::vector(maArrays), mnArrayLength, mnRefRowSize, mbStartFixed, mbEndFixed);
+}
+
+const std::vector<VectorRefArray>& DoubleVectorRefToken::GetArrays() const
+{
+ return maArrays;
+}
+
+size_t DoubleVectorRefToken::GetArrayLength() const
+{
+ return mnArrayLength;
+}
+
+size_t DoubleVectorRefToken::GetRefRowSize() const
+{
+ return mnRefRowSize;
+}
+
+bool DoubleVectorRefToken::IsStartFixed() const
+{
+ return mbStartFixed;
+}
+
+bool DoubleVectorRefToken::IsEndFixed() const
+{
+ return mbEndFixed;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */