diff options
Diffstat (limited to '')
-rw-r--r-- | sc/source/core/data/formulacell.cxx | 5571 |
1 files changed, 5571 insertions, 0 deletions
diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx new file mode 100644 index 000000000..f2d840cb9 --- /dev/null +++ b/sc/source/core/data/formulacell.cxx @@ -0,0 +1,5571 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_feature_opencl.h> + +#include <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <cassert> +#include <cstdlib> + +#include <formulacell.hxx> +#include <grouptokenconverter.hxx> + +#include <compiler.hxx> +#include <document.hxx> +#include <cellvalue.hxx> +#include <interpre.hxx> +#include <macromgr.hxx> +#include <refupdat.hxx> +#include <recursionhelper.hxx> +#include <docoptio.hxx> +#include <rangenam.hxx> +#include <rangelst.hxx> +#include <dbdata.hxx> +#include <progress.hxx> +#include <scmatrix.hxx> +#include <rechead.hxx> +#include <scitems.hxx> +#include <validat.hxx> +#include <editutil.hxx> +#include <chgtrack.hxx> +#include <tokenarray.hxx> + +#include <comphelper/threadpool.hxx> +#include <editeng/editobj.hxx> +#include <formula/errorcodes.hxx> +#include <svl/intitem.hxx> +#include <svl/numformat.hxx> +#include <formulagroup.hxx> +#include <listenercontext.hxx> +#include <types.hxx> +#include <scopetools.hxx> +#include <refupdatecontext.hxx> +#include <tokenstringcontext.hxx> +#include <refhint.hxx> +#include <listenerquery.hxx> +#include <listenerqueryids.hxx> +#include <grouparealistener.hxx> +#include <formulalogger.hxx> +#include <com/sun/star/sheet/FormulaLanguage.hpp> + +#if HAVE_FEATURE_OPENCL +#include <opencl/openclwrapper.hxx> +#endif + +#include <memory> +#include <map> + +using namespace formula; + +#define DEBUG_CALCULATION 0 +#if DEBUG_CALCULATION +static bool bDebugCalculationActive = false; // Set to true for global active init, +static ScAddress aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like + +struct DebugCalculationEntry +{ + ScAddress maPos; + OUString maResult; + const ScDocument& mrDoc; + sal_uInt32 mnGroup; + sal_uInt16 mnRecursion; + + DebugCalculationEntry( const ScAddress& rPos, ScDocument& rDoc, sal_uInt32 nGroup ) : + maPos(rPos), + mrDoc(rDoc), + mnGroup(nGroup), + mnRecursion(rDoc.GetRecursionHelper().GetRecursionCount()) + { + } +}; + +/** Debug/dump formula cell calculation chain. + Either, somewhere set aDC.mbActive=true, or + aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start. + This does not work for deep recursion > MAXRECURSION, the results are + somewhat... funny... ;) + */ +static struct DebugCalculation +{ + std::vector< DebugCalculationEntry > mvPos; + std::vector< DebugCalculationEntry > mvResults; + ScAddress maTrigger; + sal_uInt32 mnGroup; + bool mbActive; + bool mbSwitchOff; + bool mbPrint; + bool mbPrintResults; + + DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive), mbSwitchOff(false), + mbPrint(true), mbPrintResults(false) {} + + /** Print chain in encountered dependency order. */ + void print() const + { + for (auto const& it : mvPos) + { + OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc) + + " [" + OUString::number( it.mnRecursion) + "," + OUString::number( it.mnGroup) + "]"); + fprintf( stderr, "%s -> ", aStr.toUtf8().getStr()); + } + fprintf( stderr, "%s", "END\n"); + } + + /** Print chain results. */ + void printResults() const + { + for (auto const& it : mvResults) + { + OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc)); + aStr += " (" + it.maResult + ")"; + fprintf( stderr, "%s, ", aStr.toUtf8().getStr()); + } + fprintf( stderr, "%s", "END\n"); + } + + void storeResult( const svl::SharedString& rStr ) + { + if (mbActive && !mvPos.empty()) + mvPos.back().maResult = "\"" + rStr.getString() + "\""; + } + + void storeResult( const double& fVal ) + { + if (mbActive && !mvPos.empty()) + mvPos.back().maResult = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_G, 2, '.', true); + } + + void storeResultError( FormulaError nErr ) + { + if (mbActive && !mvPos.empty()) + mvPos.back().maResult = "Err:" + OUString::number( int( nErr )); + } + + void enterGroup() + { + ++mnGroup; + } + + void leaveGroup() + { + --mnGroup; + } + +} aDC; + +struct DebugCalculationStacker +{ + DebugCalculationStacker( const ScAddress& rPos, ScDocument& rDoc ) + { + if (!aDC.mbActive && rPos == aDC.maTrigger) + aDC.mbActive = aDC.mbSwitchOff = true; + if (aDC.mbActive) + { + aDC.mvPos.push_back( DebugCalculationEntry( rPos, rDoc, aDC.mnGroup)); + aDC.mbPrint = true; + } + } + + ~DebugCalculationStacker() + { + if (aDC.mbActive) + { + if (!aDC.mvPos.empty()) + { + if (aDC.mbPrint) + { + aDC.print(); + aDC.mbPrint = false; + } + if (aDC.mbPrintResults) + { + // Store results until final result is available, reversing order. + aDC.mvResults.push_back( aDC.mvPos.back()); + } + aDC.mvPos.pop_back(); + if (aDC.mbPrintResults && aDC.mvPos.empty()) + { + aDC.printResults(); + std::vector< DebugCalculationEntry >().swap( aDC.mvResults); + } + if (aDC.mbSwitchOff && aDC.mvPos.empty()) + aDC.mbActive = false; + } + } + } +}; +#endif + +namespace { + +// More or less arbitrary, of course all recursions must fit into available +// stack space (which is what on all systems we don't know yet?). Choosing a +// lower value may be better than trying a much higher value that also isn't +// sufficient but temporarily leads to high memory consumption. On the other +// hand, if the value fits all recursions, execution is quicker as no resumes +// are necessary. Could be made a configurable option. +// Allow for a year's calendar (366). +const sal_uInt16 MAXRECURSION = 400; + +typedef SCCOLROW(*DimensionSelector)(const ScDocument&, const ScAddress&, const ScSingleRefData&); + +SCCOLROW lcl_GetCol(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData) +{ + return rData.toAbs(rDoc, rPos).Col(); +} + +SCCOLROW lcl_GetRow(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData) +{ + return rData.toAbs(rDoc, rPos).Row(); +} + +SCCOLROW lcl_GetTab(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData) +{ + return rData.toAbs(rDoc, rPos).Tab(); +} + +/** Check if both references span the same range in selected dimension. + */ +bool +lcl_checkRangeDimension( + const ScDocument& rDoc, + const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2, + const DimensionSelector aWhich) +{ + return aWhich(rDoc, rPos, rRef1.Ref1) == aWhich(rDoc, rPos, rRef2.Ref1) && + aWhich(rDoc, rPos, rRef1.Ref2) == aWhich(rDoc, rPos, rRef2.Ref2); +} + +bool +lcl_checkRangeDimensions( + const ScDocument& rDoc, + const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2, + bool& bCol, bool& bRow, bool& bTab) +{ + const bool bSameCols(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetCol)); + const bool bSameRows(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetRow)); + const bool bSameTabs(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetTab)); + + // Test if exactly two dimensions are equal + if (int(bSameCols) + int(bSameRows) + int(bSameTabs) == 2) + { + bCol = !bSameCols; + bRow = !bSameRows; + bTab = !bSameTabs; + return true; + } + return false; +} + +/** Check if references in given reference list can possibly + form a range. To do that, two of their dimensions must be the same. + */ +bool +lcl_checkRangeDimensions( + const ScDocument& rDoc, const ScAddress& rPos, + const std::vector<formula::FormulaToken*>::const_iterator& rBegin, + const std::vector<formula::FormulaToken*>::const_iterator& rEnd, + bool& bCol, bool& bRow, bool& bTab) +{ + std::vector<formula::FormulaToken*>::const_iterator aCur(rBegin); + ++aCur; + const SingleDoubleRefProvider aRef(**rBegin); + bool bOk(false); + { + const SingleDoubleRefProvider aRefCur(**aCur); + bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bCol, bRow, bTab); + } + while (bOk && aCur != rEnd) + { + const SingleDoubleRefProvider aRefCur(**aCur); + bool bColTmp(false); + bool bRowTmp(false); + bool bTabTmp(false); + bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bColTmp, bRowTmp, bTabTmp); + bOk = bOk && (bCol == bColTmp && bRow == bRowTmp && bTab == bTabTmp); + ++aCur; + } + + return bOk && aCur == rEnd; +} + +class LessByReference +{ + const ScDocument& mrDoc; + ScAddress maPos; + DimensionSelector maFunc; +public: + LessByReference(const ScDocument& rDoc, const ScAddress& rPos, const DimensionSelector& rFunc) : + mrDoc(rDoc), maPos(rPos), maFunc(rFunc) {} + + bool operator() (const formula::FormulaToken* pRef1, const formula::FormulaToken* pRef2) + { + const SingleDoubleRefProvider aRef1(*pRef1); + const SingleDoubleRefProvider aRef2(*pRef2); + return maFunc(mrDoc, maPos, aRef1.Ref1) < maFunc(mrDoc, maPos, aRef2.Ref1); + } +}; + +/** + * Returns true if range denoted by token p2 starts immediately after range + * denoted by token p1. Dimension, in which the comparison takes place, is + * given by maFunc. + */ +class AdjacentByReference +{ + const ScDocument& mrDoc; + ScAddress maPos; + DimensionSelector maFunc; +public: + AdjacentByReference(const ScDocument& rDoc, const ScAddress& rPos, DimensionSelector aFunc) : + mrDoc(rDoc), maPos(rPos), maFunc(aFunc) {} + + bool operator() (const formula::FormulaToken* p1, const formula::FormulaToken* p2) + { + const SingleDoubleRefProvider aRef1(*p1); + const SingleDoubleRefProvider aRef2(*p2); + return maFunc(mrDoc, maPos, aRef2.Ref1) - maFunc(mrDoc, maPos, aRef1.Ref2) == 1; + } +}; + +bool +lcl_checkIfAdjacent( + const ScDocument& rDoc, + const ScAddress& rPos, const std::vector<formula::FormulaToken*>& rReferences, const DimensionSelector aWhich) +{ + auto aBegin(rReferences.cbegin()); + auto aEnd(rReferences.cend()); + auto aBegin1(aBegin); + ++aBegin1; + --aEnd; + return std::equal(aBegin, aEnd, aBegin1, AdjacentByReference(rDoc, rPos, aWhich)); +} + +void +lcl_fillRangeFromRefList( + const ScDocument& rDoc, + const ScAddress& aPos, const std::vector<formula::FormulaToken*>& rReferences, ScRange& rRange) +{ + const ScSingleRefData aStart( + SingleDoubleRefProvider(*rReferences.front()).Ref1); + rRange.aStart = aStart.toAbs(rDoc, aPos); + const ScSingleRefData aEnd( + SingleDoubleRefProvider(*rReferences.back()).Ref2); + rRange.aEnd = aEnd.toAbs(rDoc, aPos); +} + +bool +lcl_refListFormsOneRange( + const ScDocument& rDoc, + const ScAddress& rPos, std::vector<formula::FormulaToken*>& rReferences, + ScRange& rRange) +{ + if (rReferences.size() == 1) + { + lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange); + return true; + } + + bool bCell(false); + bool bRow(false); + bool bTab(false); + if (lcl_checkRangeDimensions(rDoc, rPos, rReferences.begin(), rReferences.end(), bCell, bRow, bTab)) + { + DimensionSelector aWhich; + if (bCell) + { + aWhich = lcl_GetCol; + } + else if (bRow) + { + aWhich = lcl_GetRow; + } + else if (bTab) + { + aWhich = lcl_GetTab; + } + else + { + OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!"); + aWhich = lcl_GetRow; // initialize to avoid warning + } + + // Sort the references by start of range + std::sort(rReferences.begin(), rReferences.end(), LessByReference(rDoc, rPos, aWhich)); + if (lcl_checkIfAdjacent(rDoc, rPos, rReferences, aWhich)) + { + lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange); + return true; + } + } + return false; +} + +bool lcl_isReference(const FormulaToken& rToken) +{ + return + rToken.GetType() == svSingleRef || + rToken.GetType() == svDoubleRef; +} + +void adjustRangeName(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc, + const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal) +{ + ScRangeData* pRangeData = nullptr; + SCTAB nSheet = pToken->GetSheet(); + sal_uInt16 nIndex = pToken->GetIndex(); + if (!rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, rNewPos, rOldPos, bGlobalNamesToLocal, true)) + return; // nothing to do + + if (!pRangeData) + { + // If this happened we have a real problem. + pToken->SetIndex(0); + assert(!"inserting the range name should not fail"); + return; + } + + pToken->SetIndex(nIndex); + pToken->SetSheet(nSheet); +} + +void adjustDBRange(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc) +{ + ScDBCollection* pOldDBCollection = rOldDoc.GetDBCollection(); + if (!pOldDBCollection) + return;//strange error case, don't do anything + ScDBCollection::NamedDBs& aOldNamedDBs = pOldDBCollection->getNamedDBs(); + ScDBData* pDBData = aOldNamedDBs.findByIndex(pToken->GetIndex()); + if (!pDBData) + return; //invalid index + OUString aDBName = pDBData->GetUpperName(); + + //search in new document + ScDBCollection* pNewDBCollection = rNewDoc.GetDBCollection(); + if (!pNewDBCollection) + { + rNewDoc.SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(rNewDoc))); + pNewDBCollection = rNewDoc.GetDBCollection(); + } + ScDBCollection::NamedDBs& aNewNamedDBs = pNewDBCollection->getNamedDBs(); + ScDBData* pNewDBData = aNewNamedDBs.findByUpperName(aDBName); + if (!pNewDBData) + { + pNewDBData = new ScDBData(*pDBData); + bool ins = aNewNamedDBs.insert(std::unique_ptr<ScDBData>(pNewDBData)); + assert(ins); (void)ins; + } + pToken->SetIndex(pNewDBData->GetIndex()); +} + +} + +bool AreaListenerKey::operator < ( const AreaListenerKey& r ) const +{ + if (maRange.aStart.Tab() != r.maRange.aStart.Tab()) + return maRange.aStart.Tab() < r.maRange.aStart.Tab(); + if (maRange.aStart.Col() != r.maRange.aStart.Col()) + return maRange.aStart.Col() < r.maRange.aStart.Col(); + if (maRange.aStart.Row() != r.maRange.aStart.Row()) + return maRange.aStart.Row() < r.maRange.aStart.Row(); + if (maRange.aEnd.Tab() != r.maRange.aEnd.Tab()) + return maRange.aEnd.Tab() < r.maRange.aEnd.Tab(); + if (maRange.aEnd.Col() != r.maRange.aEnd.Col()) + return maRange.aEnd.Col() < r.maRange.aEnd.Col(); + if (maRange.aEnd.Row() != r.maRange.aEnd.Row()) + return maRange.aEnd.Row() < r.maRange.aEnd.Row(); + if (mbStartFixed != r.mbStartFixed) + return r.mbStartFixed; + if (mbEndFixed != r.mbEndFixed) + return r.mbEndFixed; + + return false; +} + +ScFormulaCellGroup::ScFormulaCellGroup() : + mnRefCount(0), + mpTopCell(nullptr), + mnLength(0), + mnWeight(0), + mnFormatType(SvNumFormatType::NUMBER), + mbInvariant(false), + mbSubTotal(false), + mbPartOfCycle(false), + meCalcState(sc::GroupCalcEnabled) +{ +} + +ScFormulaCellGroup::~ScFormulaCellGroup() +{ +} + +void ScFormulaCellGroup::setCode( const ScTokenArray& rCode ) +{ + mpCode = rCode.CloneValue(); + mbInvariant = mpCode->IsInvariant(); + mpCode->GenHash(); +} + +void ScFormulaCellGroup::compileCode( + ScDocument& rDoc, const ScAddress& rPos, FormulaGrammar::Grammar eGram ) +{ + if (!mpCode) + return; + + if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen()) + { + bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE; + ScCompiler aComp(rDoc, rPos, *mpCode, eGram, true, bMatrixFormula); + mbSubTotal = aComp.CompileTokenArray(); + mnFormatType = aComp.GetNumFormatType(); + } + else + { + mbSubTotal = mpCode->HasOpCodeRPN( ocSubTotal ) || mpCode->HasOpCodeRPN( ocAggregate ); + } +} + +sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener( + ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed ) +{ + AreaListenerKey aKey(rRange, bStartFixed, bEndFixed); + + AreaListenersType::iterator it = m_AreaListeners.lower_bound(aKey); + if (it == m_AreaListeners.end() || m_AreaListeners.key_comp()(aKey, it->first)) + { + // Insert a new one. + it = m_AreaListeners.insert( + it, std::make_pair(aKey, std::make_unique<sc::FormulaGroupAreaListener>( + rRange, (*ppTopCell)->GetDocument(), (*ppTopCell)->aPos, mnLength, bStartFixed, bEndFixed))); + } + + return it->second.get(); +} + +void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc ) +{ + for (const auto& rEntry : m_AreaListeners) + { + sc::FormulaGroupAreaListener *const pListener = rEntry.second.get(); + ScRange aListenRange = pListener->getListeningRange(); + // This "always listen" special range is never grouped. + bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS); + rDoc.EndListeningArea(aListenRange, bGroupListening, pListener); + } + + m_AreaListeners.clear(); +} + +ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos ) : + bDirty(false), + bTableOpDirty(false), + bChanged(false), + bRunning(false), + bCompile(false), + bSubTotal(false), + bIsIterCell(false), + bInChangeTrack(false), + bNeedListening(false), + mbNeedsNumberFormat(false), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag(ScMatrixMode::NONE), + nSeenInIteration(0), + nFormatType(SvNumFormatType::NUMBER), + eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT), + pCode(new ScTokenArray(rDoc)), + rDocument(rDoc), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ +} + +ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos, + const OUString& rFormula, + const FormulaGrammar::Grammar eGrammar, + ScMatrixMode cMatInd ) : + bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0 + bTableOpDirty( false ), + bChanged( false ), + bRunning( false ), + bCompile( false ), + bSubTotal( false ), + bIsIterCell( false ), + bInChangeTrack( false ), + bNeedListening( false ), + mbNeedsNumberFormat( false ), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag ( cMatInd ), + nSeenInIteration(0), + nFormatType ( SvNumFormatType::NUMBER ), + eTempGrammar( eGrammar), + pCode( nullptr ), + rDocument( rDoc ), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ + Compile( rFormula, true, eGrammar ); // bNoListening, Insert does that + if (!pCode) + // We need to have a non-NULL token array instance at all times. + pCode = new ScTokenArray(rDoc); +} + +ScFormulaCell::ScFormulaCell( + ScDocument& rDoc, const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray, + const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) : + bDirty( true ), + bTableOpDirty( false ), + bChanged( false ), + bRunning( false ), + bCompile( false ), + bSubTotal( false ), + bIsIterCell( false ), + bInChangeTrack( false ), + bNeedListening( false ), + mbNeedsNumberFormat( false ), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag ( cMatInd ), + nSeenInIteration(0), + nFormatType ( SvNumFormatType::NUMBER ), + eTempGrammar( eGrammar), + pCode(pArray.release()), + rDocument( rDoc ), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ + assert(pCode); // Never pass a NULL pointer here. + + pCode->Finalize(); // Reduce memory usage if needed. + + // Generate RPN token array. + if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen()) + { + ScCompiler aComp(rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE); + bSubTotal = aComp.CompileTokenArray(); + nFormatType = aComp.GetNumFormatType(); + } + else + { + if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) ) + bSubTotal = true; + } + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + + pCode->GenHash(); +} + +ScFormulaCell::ScFormulaCell( + ScDocument& rDoc, const ScAddress& rPos, const ScTokenArray& rArray, + const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) : + bDirty( true ), + bTableOpDirty( false ), + bChanged( false ), + bRunning( false ), + bCompile( false ), + bSubTotal( false ), + bIsIterCell( false ), + bInChangeTrack( false ), + bNeedListening( false ), + mbNeedsNumberFormat( false ), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag ( cMatInd ), + nSeenInIteration(0), + nFormatType ( SvNumFormatType::NUMBER ), + eTempGrammar( eGrammar), + pCode(new ScTokenArray(rArray)), // also implicitly does Finalize() on the array + rDocument( rDoc ), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ + // RPN array generation + if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() ) + { + ScCompiler aComp( rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE ); + bSubTotal = aComp.CompileTokenArray(); + nFormatType = aComp.GetNumFormatType(); + } + else + { + if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) ) + bSubTotal = true; + } + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + + pCode->GenHash(); +} + +ScFormulaCell::ScFormulaCell( + ScDocument& rDoc, const ScAddress& rPos, const ScFormulaCellGroupRef& xGroup, + const FormulaGrammar::Grammar eGrammar, ScMatrixMode cInd ) : + mxGroup(xGroup), + bDirty(true), + bTableOpDirty( false ), + bChanged( false ), + bRunning( false ), + bCompile( false ), + bSubTotal(xGroup->mbSubTotal), + bIsIterCell( false ), + bInChangeTrack( false ), + bNeedListening( false ), + mbNeedsNumberFormat( false ), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag ( cInd ), + nSeenInIteration(0), + nFormatType(xGroup->mnFormatType), + eTempGrammar( eGrammar), + pCode(xGroup->mpCode ? &*xGroup->mpCode : new ScTokenArray(rDoc)), + rDocument( rDoc ), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ + if (bSubTotal) + rDocument.AddSubTotalCell(this); +} + +ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const ScAddress& rPos, ScCloneFlags nCloneFlags) : + bDirty( rCell.bDirty ), + bTableOpDirty( false ), + bChanged( rCell.bChanged ), + bRunning( false ), + bCompile( rCell.bCompile ), + bSubTotal( rCell.bSubTotal ), + bIsIterCell( false ), + bInChangeTrack( false ), + bNeedListening( false ), + mbNeedsNumberFormat( rCell.mbNeedsNumberFormat ), + mbAllowNumberFormatChange(false), + mbPostponedDirty(false), + mbIsExtRef(false), + mbSeenInPath(false), + mbFreeFlying(false), + cMatrixFlag ( rCell.cMatrixFlag ), + nSeenInIteration(0), + nFormatType( rCell.nFormatType ), + aResult( rCell.aResult ), + eTempGrammar( rCell.eTempGrammar), + rDocument( rDoc ), + pPrevious(nullptr), + pNext(nullptr), + pPreviousTrack(nullptr), + pNextTrack(nullptr), + aPos(rPos) +{ + pCode = rCell.pCode->Clone().release(); + + // set back any errors and recompile + // not in the Clipboard - it must keep the received error flag + // Special Length=0: as bad cells are generated, then they are also retained + if ( pCode->GetCodeError() != FormulaError::NONE && !rDocument.IsClipboard() && pCode->GetLen() ) + { + pCode->SetCodeError( FormulaError::NONE ); + bCompile = true; + } + // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference ! + bool bCompileLater = false; + bool bClipMode = rCell.rDocument.IsClipboard(); + + //update ScNameTokens + if (!rDocument.IsClipOrUndo() || rDoc.IsUndo()) + { + if (!rDocument.IsClipboardSource() || aPos.Tab() != rCell.aPos.Tab()) + { + bool bGlobalNamesToLocal = ((nCloneFlags & ScCloneFlags::NamesToLocal) != ScCloneFlags::Default); + formula::FormulaToken* pToken = nullptr; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + while((pToken = aIter.GetNextName())!= nullptr) + { + OpCode eOpCode = pToken->GetOpCode(); + if (eOpCode == ocName) + adjustRangeName(pToken, rDoc, rCell.rDocument, aPos, rCell.aPos, bGlobalNamesToLocal); + else if (eOpCode == ocDBArea || eOpCode == ocTableRef) + adjustDBRange(pToken, rDoc, rCell.rDocument); + } + } + + bool bCopyBetweenDocs = rDocument.GetPool() != rCell.rDocument.GetPool(); + if (bCopyBetweenDocs && !(nCloneFlags & ScCloneFlags::NoMakeAbsExternal)) + { + pCode->ReadjustAbsolute3DReferences(rCell.rDocument, rDoc, rCell.aPos); + } + + pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs ); + } + + if (!rDocument.IsClipOrUndo()) + { + if (&rDocument.GetSharedStringPool() != &rCell.rDocument.GetSharedStringPool()) + pCode->ReinternStrings( rDocument.GetSharedStringPool()); + pCode->AdjustReferenceOnCopy( aPos); + } + + if( !bCompile ) + { // Name references with references and ColRowNames + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + for (;;) + { + formula::FormulaToken* t = aIter.GetNextReferenceOrName(); + if (!t || bCompile) + break; + if ( t->IsExternalRef() ) + { + // External name, cell, and area references. + bCompile = true; + } + else if ( t->GetType() == svIndex ) + { + const ScRangeData* pRangeData = rDoc.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()); + if( pRangeData ) + { + if( pRangeData->HasReferences() ) + bCompile = true; + } + else + bCompile = true; // invalid reference! + } + else if ( t->GetOpCode() == ocColRowName ) + { + bCompile = true; // new lookup needed + bCompileLater = bClipMode; + } + } + } + if( bCompile ) + { + if ( !bCompileLater && bClipMode ) + { + // Merging ranges needs the actual positions after UpdateReference. + // ColRowNames and TableRefs need new lookup after positions are + // adjusted. + bCompileLater = pCode->HasOpCode( ocRange) || pCode->HasOpCode( ocColRowName) || + pCode->HasOpCode( ocTableRef); + } + if ( !bCompileLater ) + { + // bNoListening, not at all if in Clipboard/Undo, + // and not from Clipboard either, instead after Insert(Clone) and UpdateReference. + CompileTokenArray( true ); + } + } + + if( nCloneFlags & ScCloneFlags::StartListening ) + StartListeningTo( rDoc ); + + if (bSubTotal) + rDocument.AddSubTotalCell(this); +} + +ScFormulaCell::~ScFormulaCell() +{ + rDocument.RemoveFromFormulaTrack( this ); + rDocument.RemoveFromFormulaTree( this ); + rDocument.RemoveSubTotalCell(this); + if (pCode->HasOpCode(ocMacro)) + rDocument.GetMacroManager()->RemoveDependentCell(this); + + if (rDocument.HasExternalRefManager()) + rDocument.GetExternalRefManager()->removeRefCell(this); + + if (!mxGroup || !mxGroup->mpCode) + // Formula token is not shared. + delete pCode; + + if (mxGroup && mxGroup->mpTopCell == this) + mxGroup->mpTopCell = nullptr; +} + +ScFormulaCell* ScFormulaCell::Clone() const +{ + return new ScFormulaCell(*this, rDocument, aPos); +} + +ScFormulaCell* ScFormulaCell::Clone( const ScAddress& rPos ) const +{ + return new ScFormulaCell(*this, rDocument, rPos, ScCloneFlags::Default); +} + +size_t ScFormulaCell::GetHash() const +{ + return pCode->GetHash(); +} + +OUString ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar, const ScInterpreterContext* pContext ) const +{ + if( pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen() ) + { + return ScGlobal::GetErrorString(pCode->GetCodeError()); + } + OUStringBuffer buffer; + if( cMatrixFlag == ScMatrixMode::Reference ) + { + // Reference to another cell that contains a matrix formula. + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* p = aIter.GetNextReferenceRPN(); + if( p ) + { + /* FIXME: original GetFormula() code obtained + * pCell only if (!IsInChangeTrack()), + * GetEnglishFormula() omitted that test. + * Can we live without in all cases? */ + ScFormulaCell* pCell = nullptr; + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(rDocument, aPos); + if (rDocument.ValidAddress(aAbs)) + pCell = rDocument.GetFormulaCell(aAbs); + + if (pCell) + { + return pCell->GetFormula( eGrammar, pContext ); + } + else + { + ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext ); + aComp.CreateStringFromTokenArray( buffer ); + } + } + else + { + OSL_FAIL("ScFormulaCell::GetFormula: not a matrix"); + } + } + else + { + ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext ); + aComp.CreateStringFromTokenArray( buffer ); + } + + buffer.insert( 0, '='); + if( cMatrixFlag != ScMatrixMode::NONE ) + { + buffer.insert( 0, '{'); + buffer.append( '}'); + } + return buffer.makeStringAndClear(); +} + +OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInterpreterContext* pContext ) const +{ + OUStringBuffer aBuf; + if (pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen()) + { + ScTokenArray aCode(rCxt.getDoc()); + aCode.AddToken( FormulaErrorToken( pCode->GetCodeError())); + ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext); + aComp.CreateStringFromTokenArray(aBuf); + return aBuf.makeStringAndClear(); + } + else if( cMatrixFlag == ScMatrixMode::Reference ) + { + // Reference to another cell that contains a matrix formula. + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* p = aIter.GetNextReferenceRPN(); + if( p ) + { + /* FIXME: original GetFormula() code obtained + * pCell only if (!IsInChangeTrack()), + * GetEnglishFormula() omitted that test. + * Can we live without in all cases? */ + ScFormulaCell* pCell = nullptr; + ScSingleRefData& rRef = *p->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(rDocument, aPos); + if (rDocument.ValidAddress(aAbs)) + pCell = rDocument.GetFormulaCell(aAbs); + + if (pCell) + { + return pCell->GetFormula(rCxt); + } + else + { + ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext); + aComp.CreateStringFromTokenArray(aBuf); + } + } + else + { + OSL_FAIL("ScFormulaCell::GetFormula: not a matrix"); + } + } + else + { + ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext); + aComp.CreateStringFromTokenArray(aBuf); + } + + aBuf.insert( 0, '='); + if( cMatrixFlag != ScMatrixMode::NONE ) + { + aBuf.insert( 0, '{'); + aBuf.append( '}'); + } + + return aBuf.makeStringAndClear(); +} + +void ScFormulaCell::GetResultDimensions( SCSIZE& rCols, SCSIZE& rRows ) +{ + MaybeInterpret(); + + if (pCode->GetCodeError() == FormulaError::NONE && aResult.GetType() == svMatrixCell) + { + const ScMatrix* pMat = aResult.GetToken()->GetMatrix(); + if (pMat) + { + pMat->GetDimensions( rCols, rRows ); + if (pCode->IsHyperLink()) + { + // Row 2 element is the URL that is not to be displayed and the + // result dimension not to be extended. + assert(rRows == 2); + rRows = 1; + } + return; + } + } + rCols = 0; + rRows = 0; +} + +void ScFormulaCell::ResetDirty() { bDirty = bTableOpDirty = mbPostponedDirty = false; } +void ScFormulaCell::SetNeedsListening( bool bVar ) { bNeedListening = bVar; } + +void ScFormulaCell::SetNeedsDirty( bool bVar ) +{ + mbPostponedDirty = bVar; +} + +void ScFormulaCell::SetNeedNumberFormat( bool bVal ) +{ + mbNeedsNumberFormat = mbAllowNumberFormatChange = bVal; +} + +void ScFormulaCell::Compile( const OUString& rFormula, bool bNoListening, + const FormulaGrammar::Grammar eGrammar ) +{ + if ( rDocument.IsClipOrUndo() ) + return; + bool bWasInFormulaTree = rDocument.IsInFormulaTree( this ); + if ( bWasInFormulaTree ) + rDocument.RemoveFromFormulaTree( this ); + // pCode may not deleted for queries, but must be empty + if ( pCode ) + pCode->Clear(); + ScTokenArray* pCodeOld = pCode; + ScCompiler aComp( rDocument, aPos, eGrammar); + pCode = aComp.CompileString( rFormula ).release(); + assert(!mxGroup); + delete pCodeOld; + if( pCode->GetCodeError() == FormulaError::NONE ) + { + if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() ) + { // not recursive CompileTokenArray/Compile/CompileTokenArray + if ( rFormula[0] == '=' ) + pCode->AddBad( rFormula.copy(1) ); + else + pCode->AddBad( rFormula ); + } + bCompile = true; + CompileTokenArray( bNoListening ); + } + else + bChanged = true; + + if ( bWasInFormulaTree ) + rDocument.PutInFormulaTree( this ); +} + +void ScFormulaCell::Compile( + sc::CompileFormulaContext& rCxt, const OUString& rFormula, bool bNoListening ) +{ + if ( rDocument.IsClipOrUndo() ) + return; + bool bWasInFormulaTree = rDocument.IsInFormulaTree( this ); + if ( bWasInFormulaTree ) + rDocument.RemoveFromFormulaTree( this ); + // pCode may not deleted for queries, but must be empty + if ( pCode ) + pCode->Clear(); + ScTokenArray* pCodeOld = pCode; + ScCompiler aComp(rCxt, aPos); + pCode = aComp.CompileString( rFormula ).release(); + assert(!mxGroup); + delete pCodeOld; + if( pCode->GetCodeError() == FormulaError::NONE ) + { + if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() ) + { // not recursive CompileTokenArray/Compile/CompileTokenArray + if ( rFormula[0] == '=' ) + pCode->AddBad( rFormula.copy(1) ); + else + pCode->AddBad( rFormula ); + } + bCompile = true; + CompileTokenArray(rCxt, bNoListening); + } + else + bChanged = true; + + if ( bWasInFormulaTree ) + rDocument.PutInFormulaTree( this ); +} + +void ScFormulaCell::CompileTokenArray( bool bNoListening ) +{ + // Not already compiled? + if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() ) + { + Compile( aResult.GetHybridFormula(), bNoListening, eTempGrammar); + } + else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE ) + { + // RPN length may get changed + bool bWasInFormulaTree = rDocument.IsInFormulaTree( this ); + if ( bWasInFormulaTree ) + rDocument.RemoveFromFormulaTree( this ); + + // Loading from within filter? No listening yet! + if( rDocument.IsInsertingFromOtherDoc() ) + bNoListening = true; + + if( !bNoListening && pCode->GetCodeLen() ) + EndListeningTo( rDocument ); + ScCompiler aComp(rDocument, aPos, *pCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE); + bSubTotal = aComp.CompileTokenArray(); + if( pCode->GetCodeError() == FormulaError::NONE ) + { + nFormatType = aComp.GetNumFormatType(); + bChanged = true; + aResult.SetToken( nullptr); + bCompile = false; + if ( !bNoListening ) + StartListeningTo( rDocument ); + } + if ( bWasInFormulaTree ) + rDocument.PutInFormulaTree( this ); + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + } +} + +void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNoListening ) +{ + // Not already compiled? + if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() ) + { + rCxt.setGrammar(eTempGrammar); + Compile(rCxt, aResult.GetHybridFormula(), bNoListening); + } + else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE) + { + // RPN length may get changed + bool bWasInFormulaTree = rDocument.IsInFormulaTree( this ); + if ( bWasInFormulaTree ) + rDocument.RemoveFromFormulaTree( this ); + + // Loading from within filter? No listening yet! + if( rDocument.IsInsertingFromOtherDoc() ) + bNoListening = true; + + if( !bNoListening && pCode->GetCodeLen() ) + EndListeningTo( rDocument ); + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); + bSubTotal = aComp.CompileTokenArray(); + if( pCode->GetCodeError() == FormulaError::NONE ) + { + nFormatType = aComp.GetNumFormatType(); + bChanged = true; + aResult.SetToken( nullptr); + bCompile = false; + if ( !bNoListening ) + StartListeningTo( rDocument ); + } + if ( bWasInFormulaTree ) + rDocument.PutInFormulaTree( this ); + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + } +} + +void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress ) +{ + if ( cMatrixFlag == ScMatrixMode::Reference ) + { // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula + // just establish listeners + StartListeningTo( rDocument ); + return ; + } + + // Error constant formula cell stays as is. + if (!pCode->GetLen() && pCode->GetCodeError() != FormulaError::NONE) + return; + + // Compilation changes RPN count, remove and reinsert to FormulaTree if it + // was in to update its count. + bool bWasInFormulaTree = rDocument.IsInFormulaTree( this); + if (bWasInFormulaTree) + rDocument.RemoveFromFormulaTree( this); + rCxt.setGrammar(eTempGrammar); + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); + OUString aFormula, aFormulaNmsp; + aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp ); + rDocument.DecXMLImportedFormulaCount( aFormula.getLength() ); + rProgress.SetStateCountDownOnPercent( rDocument.GetXMLImportedFormulaCount() ); + // pCode may not deleted for queries, but must be empty + pCode->Clear(); + + bool bDoCompile = true; + + if ( !mxGroup && aFormulaNmsp.isEmpty() ) // optimization + { + ScAddress aPreviousCell( aPos ); + aPreviousCell.IncRow( -1 ); + ScFormulaCell *pPreviousCell = rDocument.GetFormulaCell( aPreviousCell ); + if (pPreviousCell && pPreviousCell->GetCode()->IsShareable()) + { + // Build formula string using the tokens from the previous cell, + // but use the current cell position. + ScCompiler aBackComp( rCxt, aPos, *(pPreviousCell->pCode) ); + OUStringBuffer aShouldBeBuf; + aBackComp.CreateStringFromTokenArray( aShouldBeBuf ); + + // The initial '=' is optional in ODFF. + const sal_Int32 nLeadingEqual = (aFormula.getLength() > 0 && aFormula[0] == '=') ? 1 : 0; + OUString aShouldBe = aShouldBeBuf.makeStringAndClear(); + if (aFormula.getLength() == aShouldBe.getLength() + nLeadingEqual && + aFormula.match( aShouldBe, nLeadingEqual)) + { + // Put them in the same formula group. + ScFormulaCellGroupRef xGroup = pPreviousCell->GetCellGroup(); + if (!xGroup) // Last cell is not grouped yet. Start a new group. + xGroup = pPreviousCell->CreateCellGroup(1, false); + ++xGroup->mnLength; + SetCellGroup( xGroup ); + + // Do setup here based on previous cell. + + nFormatType = pPreviousCell->nFormatType; + bSubTotal = pPreviousCell->bSubTotal; + bChanged = true; + bCompile = false; + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + + bDoCompile = false; + pCode = pPreviousCell->pCode; + if (pPreviousCell->mbIsExtRef) + rDocument.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell, this ); + } + } + } + + if (bDoCompile) + { + ScTokenArray* pCodeOld = pCode; + pCode = aComp.CompileString( aFormula, aFormulaNmsp ).release(); + assert(!mxGroup); + delete pCodeOld; + + if( pCode->GetCodeError() == FormulaError::NONE ) + { + if ( !pCode->GetLen() ) + { + if ( !aFormula.isEmpty() && aFormula[0] == '=' ) + pCode->AddBad( aFormula.copy( 1 ) ); + else + pCode->AddBad( aFormula ); + } + bSubTotal = aComp.CompileTokenArray(); + if( pCode->GetCodeError() == FormulaError::NONE ) + { + nFormatType = aComp.GetNumFormatType(); + bChanged = true; + bCompile = false; + } + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + } + else + bChanged = true; + } + + // After loading, it must be known if ocDde/ocWebservice is in any formula + // (for external links warning, CompileXML is called at the end of loading XML file) + rDocument.CheckLinkFormulaNeedingCheck(*pCode); + + //volatile cells must be added here for import + if( !pCode->IsRecalcModeNormal() || pCode->IsRecalcModeForced()) + { + // During load, only those cells that are marked explicitly dirty get + // recalculated. So we need to set it dirty here. + SetDirtyVar(); + rDocument.AppendToFormulaTrack(this); + // Do not call TrackFormulas() here, not all listeners may have been + // established, postponed until ScDocument::CompileXML() finishes. + } + else if (bWasInFormulaTree) + rDocument.PutInFormulaTree(this); +} + +void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening ) +{ + bool bNewCompiled = false; + // If a Calc 1.0-doc is read, we have a result, but no token array + if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() ) + { + rCxt.setGrammar(eTempGrammar); + Compile(rCxt, aResult.GetHybridFormula(), true); + aResult.SetToken( nullptr); + bDirty = true; + bNewCompiled = true; + } + // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now. + if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE ) + { + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); + bSubTotal = aComp.CompileTokenArray(); + nFormatType = aComp.GetNumFormatType(); + bDirty = true; + bCompile = false; + bNewCompiled = true; + + if (bSubTotal) + rDocument.AddSubTotalCell(this); + } + + // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in + // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #). + // We iron this out here for all systems, such that we also have an Err503 here. + if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) ) + { + OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?"); + aResult.SetResultError( FormulaError::IllegalFPOperation ); + bDirty = true; + } + + // DoubleRefs for binary operators were always a Matrix before version v5.0. + // Now this is only the case when in an array formula, otherwise it's an implicit intersection + if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF && + GetMatrixFlag() == ScMatrixMode::NONE && pCode->HasMatrixDoubleRefOps() ) + { + cMatrixFlag = ScMatrixMode::Formula; + SetMatColsRows( 1, 1); + } + + // Do the cells need to be calculated? After Load cells can contain an error code, and then start + // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL + if( !bNewCompiled || pCode->GetCodeError() == FormulaError::NONE ) + { + if (bStartListening) + StartListeningTo(rDocument); + + if( !pCode->IsRecalcModeNormal() ) + bDirty = true; + } + if ( pCode->IsRecalcModeAlways() ) + { // random(), today(), now() always stay in the FormulaTree, so that they are calculated + // for each F9 + bDirty = true; + } + // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad) +} + +bool ScFormulaCell::MarkUsedExternalReferences() +{ + return pCode && rDocument.MarkUsedExternalReferences(*pCode, aPos); +} + +namespace { +class RecursionCounter +{ + ScRecursionHelper& rRec; + bool bStackedInIteration; +#if defined DBG_UTIL && !defined NDEBUG + const ScFormulaCell* cell; +#endif +public: + RecursionCounter( ScRecursionHelper& r, ScFormulaCell* p ) + : rRec(r) +#if defined DBG_UTIL && !defined NDEBUG + , cell(p) +#endif + { + bStackedInIteration = rRec.IsDoingIteration(); + if (bStackedInIteration) + rRec.GetRecursionInIterationStack().push( p); + rRec.IncRecursionCount(); + } + ~RecursionCounter() + { + rRec.DecRecursionCount(); + if (bStackedInIteration) + { +#if defined DBG_UTIL && !defined NDEBUG + assert(rRec.GetRecursionInIterationStack().top() == cell); +#endif + rRec.GetRecursionInIterationStack().pop(); + } + } +}; + +// Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group". +// Remove the group again at the end, since there are some places throughout the code +// that do not handle well groups with just 1 cell. Remove the groups only when the recursion level +// reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing +// a group immediately would remove the info), for this reason affected cells are stored in the recursion +// helper. +struct TemporaryCellGroupMaker +{ + TemporaryCellGroupMaker( ScFormulaCell* cell, bool enable ) + : mCell( cell ) + , mEnabled( enable ) + { + if( mEnabled && mCell->GetCellGroup() == nullptr ) + { + mCell->CreateCellGroup( 1, false ); + mCell->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell ); + } + } + ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE + { + if( mEnabled ) + mCell->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells(); + } + ScFormulaCell* mCell; + const bool mEnabled; +}; + +} // namespace + +bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset) +{ + ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper(); + bool bGroupInterpreted = false; + + // The result would possibly depend on a cell without a valid value, bail out + // the entire dependency computation. + if (rRecursionHelper.IsAbortingDependencyComputation()) + return false; + + if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent()) + return bGroupInterpreted; + + static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType(); + TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore ); + + ScFormulaCell* pTopCell = mxGroup ? mxGroup->mpTopCell : this; + + if (pTopCell->mbSeenInPath && rRecursionHelper.GetDepComputeLevel() && + rRecursionHelper.AnyCycleMemberInDependencyEvalMode(pTopCell)) + { + // This call arose from a dependency calculation and we just found a cycle. + // This will mark all elements in the cycle as parts-of-cycle. + ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell); + // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet. + // If there is a genuine circular reference, it will be marked so when all groups + // in the cycle get out of dependency evaluation mode. + // But returning without calculation a new value means other cells depending + // on this one would use a possibly invalid value, so ensure the dependency + // computation is aborted without resetting the dirty flag of any cell. + rRecursionHelper.AbortDependencyComputation(); + return bGroupInterpreted; + } + +#if DEBUG_CALCULATION + static bool bDebugCalculationInit = true; + if (bDebugCalculationInit) + { + aDC.maTrigger = aDebugCalculationTriggerAddress; + aDC.mbPrintResults = true; + bDebugCalculationInit = false; + } + DebugCalculationStacker aDebugEntry(aPos, rDocument); +#endif + + if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn()) + return bGroupInterpreted; // no double/triple processing + + //FIXME: + // If the call originates from a Reschedule in DdeLink update, leave dirty + // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously! + if ( rDocument.IsInDdeLinkUpdate() ) + return bGroupInterpreted; + + if (bRunning) + { + if (!rDocument.GetDocOptions().IsIter()) + { + aResult.SetResultError( FormulaError::CircularReference ); + return bGroupInterpreted; + } + + if (aResult.GetResultError() == FormulaError::CircularReference) + aResult.SetResultError( FormulaError::NONE ); + + // Start or add to iteration list. + if (!rRecursionHelper.IsDoingIteration() || + !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell) + rRecursionHelper.SetInIterationReturn( true); + + return bGroupInterpreted; + } + // no multiple interprets for GetErrCode, IsValue, GetValue and + // different entry point recursions. Would also lead to premature + // convergence in iterations. + if (rRecursionHelper.GetIteration() && nSeenInIteration == + rRecursionHelper.GetIteration()) + return bGroupInterpreted; + + bool bOldRunning = bRunning; + if (rRecursionHelper.GetRecursionCount() > MAXRECURSION) + { + bRunning = true; + rRecursionHelper.SetInRecursionReturn( true); + } + else + { + rDocument.IncInterpretLevel(); + +#if DEBUG_CALCULATION + aDC.enterGroup(); +#endif + bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle; + bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset); + bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle; + +#if DEBUG_CALCULATION + aDC.leaveGroup(); +#endif + if (!bGroupInterpreted) + { + // This call resulted from a dependency calculation for a multigroup-threading attempt, + // but found dependency among the groups. + if (!rRecursionHelper.AreGroupsIndependent()) + { + rDocument.DecInterpretLevel(); + return bGroupInterpreted; + } + // Dependency calc inside InterpretFormulaGroup() failed due to + // detection of a cycle and there are parent FG's in the cycle. + // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG + if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle()) + { + rDocument.DecInterpretLevel(); + return bGroupInterpreted; + } + + ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this); + ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable()); + InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL); + } + + rDocument.DecInterpretLevel(); + } + + // While leaving a recursion or iteration stack, insert its cells to the + // recursion list in reverse order. + if (rRecursionHelper.IsInReturn()) + { + bool bFreeFlyingInserted = false; + if (rRecursionHelper.GetRecursionCount() > 0 || !rRecursionHelper.IsDoingRecursion()) + { + rRecursionHelper.Insert( this, bOldRunning, aResult); + bFreeFlyingInserted = mbFreeFlying; + } + bool bIterationFromRecursion = false; + bool bResumeIteration = false; + do + { + if ((rRecursionHelper.IsInIterationReturn() && + rRecursionHelper.GetRecursionCount() == 0 && + !rRecursionHelper.IsDoingIteration()) || + bIterationFromRecursion || bResumeIteration) + { + bool & rDone = rRecursionHelper.GetConvergingReference(); + rDone = false; + if (!bIterationFromRecursion && bResumeIteration) + { + bResumeIteration = false; + // Resuming iteration expands the range. + ScFormulaRecursionList::const_iterator aOldStart( + rRecursionHelper.GetLastIterationStart()); + rRecursionHelper.ResumeIteration(); + // Mark new cells being in iteration. + for (ScFormulaRecursionList::const_iterator aIter( + rRecursionHelper.GetIterationStart()); aIter != + aOldStart; ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + pIterCell->bIsIterCell = true; + } + // Mark older cells dirty again, in case they converted + // without accounting for all remaining cells in the circle + // that weren't touched so far, e.g. conditional. Restore + // backupped result. + sal_uInt16 nIteration = rRecursionHelper.GetIteration(); + for (ScFormulaRecursionList::const_iterator aIter( + aOldStart); aIter != + rRecursionHelper.GetIterationEnd(); ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + if (pIterCell->nSeenInIteration == nIteration) + { + if (!pIterCell->bDirty || aIter == aOldStart) + { + pIterCell->aResult = (*aIter).aPreviousResult; + } + --pIterCell->nSeenInIteration; + } + pIterCell->bDirty = true; + } + } + else + { + bResumeIteration = false; + // Close circle once. If 'this' is self-referencing only + // (e.g. counter or self-adder) then it is already + // implicitly closed. + /* TODO: does this even make sense anymore? The last cell + * added above with rRecursionHelper.Insert() should always + * be 'this', shouldn't it? */ + if (rRecursionHelper.GetList().size() > 1) + { + ScFormulaCell* pLastCell = rRecursionHelper.GetList().back().pCell; + if (pLastCell != this) + { + rDocument.IncInterpretLevel(); + ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable()); + pLastCell->InterpretTail( + *aContextGetterGuard.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE); + rDocument.DecInterpretLevel(); + } + } + // Start at 1, init things. + rRecursionHelper.StartIteration(); + // Mark all cells being in iteration. Reset results to + // original values, formula cells have been interpreted + // already, discard that step. + for (ScFormulaRecursionList::const_iterator aIter( + rRecursionHelper.GetIterationStart()); aIter != + rRecursionHelper.GetIterationEnd(); ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + pIterCell->aResult = (*aIter).aPreviousResult; + pIterCell->bIsIterCell = true; + } + } + bIterationFromRecursion = false; + sal_uInt16 nIterMax = rDocument.GetDocOptions().GetIterCount(); + for ( ; rRecursionHelper.GetIteration() <= nIterMax && !rDone; + rRecursionHelper.IncIteration()) + { + rDone = false; + bool bFirst = true; + for ( ScFormulaRecursionList::iterator aIter( + rRecursionHelper.GetIterationStart()); aIter != + rRecursionHelper.GetIterationEnd() && + !rRecursionHelper.IsInReturn(); ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + if (pIterCell->IsDirtyOrInTableOpDirty() && + rRecursionHelper.GetIteration() != + pIterCell->GetSeenInIteration()) + { + (*aIter).aPreviousResult = pIterCell->aResult; + rDocument.IncInterpretLevel(); + ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable()); + pIterCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_FROM_ITERATION); + rDocument.DecInterpretLevel(); + } + if (bFirst) + { + rDone = !pIterCell->IsDirtyOrInTableOpDirty(); + bFirst = false; + } + else if (rDone) + { + rDone = !pIterCell->IsDirtyOrInTableOpDirty(); + } + } + if (rRecursionHelper.IsInReturn()) + { + bResumeIteration = true; + break; // for + // Don't increment iteration. + } + } + if (!bResumeIteration) + { + if (rDone) + { + for (ScFormulaRecursionList::const_iterator aIter( + rRecursionHelper.GetIterationStart()); + aIter != rRecursionHelper.GetIterationEnd(); + ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + pIterCell->bIsIterCell = false; + pIterCell->nSeenInIteration = 0; + pIterCell->bRunning = (*aIter).bOldRunning; + } + } + else + { + for (ScFormulaRecursionList::const_iterator aIter( + rRecursionHelper.GetIterationStart()); + aIter != rRecursionHelper.GetIterationEnd(); + ++aIter) + { + ScFormulaCell* pIterCell = (*aIter).pCell; + pIterCell->bIsIterCell = false; + pIterCell->nSeenInIteration = 0; + pIterCell->bRunning = (*aIter).bOldRunning; + pIterCell->ResetDirty(); + // The difference to Excel is that Excel does not + // produce an error for non-convergence thus a + // delta of 0.001 still works to execute the + // maximum number of iterations and display the + // results no matter if the result anywhere reached + // near delta, but also never indicates whether the + // result actually makes sense in case of + // non-counter context. Calc does check the delta + // in every case. If we wanted to support what + // Excel does then add another option "indicate + // non-convergence error" (default on) and execute + // the following block only if set. +#if 1 + // If one cell didn't converge, all cells of this + // circular dependency don't, no matter whether + // single cells did. + pIterCell->aResult.SetResultError( FormulaError::NoConvergence); + pIterCell->bChanged = true; +#endif + } + } + // End this iteration and remove entries. + rRecursionHelper.EndIteration(); + bResumeIteration = rRecursionHelper.IsDoingIteration(); + } + } + if (rRecursionHelper.IsInRecursionReturn() && + rRecursionHelper.GetRecursionCount() == 0 && + !rRecursionHelper.IsDoingRecursion()) + { + bIterationFromRecursion = false; + // Iterate over cells known so far, start with the last cell + // encountered, inserting new cells if another recursion limit + // is reached. Repeat until solved. + rRecursionHelper.SetDoingRecursion( true); + do + { + rRecursionHelper.SetInRecursionReturn( false); + for (ScFormulaRecursionList::const_iterator aIter( + rRecursionHelper.GetIterationStart()); + !rRecursionHelper.IsInReturn() && aIter != + rRecursionHelper.GetIterationEnd(); ++aIter) + { + ScFormulaCell* pCell = (*aIter).pCell; + if (pCell->IsDirtyOrInTableOpDirty()) + { + rDocument.IncInterpretLevel(); + ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable()); + pCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL); + rDocument.DecInterpretLevel(); + if (!pCell->IsDirtyOrInTableOpDirty() && !pCell->IsIterCell()) + pCell->bRunning = (*aIter).bOldRunning; + } + } + } while (rRecursionHelper.IsInRecursionReturn()); + rRecursionHelper.SetDoingRecursion( false); + if (rRecursionHelper.IsInIterationReturn()) + { + if (!bResumeIteration) + bIterationFromRecursion = true; + } + else if (bResumeIteration || + rRecursionHelper.IsDoingIteration()) + rRecursionHelper.GetList().erase( + rRecursionHelper.GetIterationStart(), + rRecursionHelper.GetLastIterationStart()); + else + rRecursionHelper.Clear(); + } + } while (bIterationFromRecursion || bResumeIteration); + + if (bFreeFlyingInserted) + { + // Remove this from recursion list, it may get deleted. + // It additionally also should mean that the recursion/iteration + // ends here as it must had been triggered by this free-flying + // out-of-sheets cell + /* TODO: replace by a simple rRecursionHelper.EndIteration() call + * if the assertions hold. */ + const bool bOnlyThis = (rRecursionHelper.GetList().size() == 1); + assert(bOnlyThis); + rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;}); + if (bOnlyThis) + { + assert(rRecursionHelper.GetList().empty()); + if (rRecursionHelper.GetList().empty()) + rRecursionHelper.EndIteration(); + } + } + } + +#if DEBUG_CALCULATION + FormulaError nErr = aResult.GetResultError(); + if (nErr != FormulaError::NONE) + aDC.storeResultError( nErr); + else if (aResult.IsValue()) + aDC.storeResult( aResult.GetDouble()); + else + aDC.storeResult( aResult.GetString()); +#endif + + return bGroupInterpreted; +} + +void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam ) +{ + RecursionCounter aRecursionCounter( rDocument.GetRecursionHelper(), this); + // TODO If this cell is not an iteration cell, add it to the list of iteration cells? + if(bIsIterCell) + nSeenInIteration = rDocument.GetRecursionHelper().GetIteration(); + if( !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE ) + { + // #i11719# no RPN and no error and no token code but result string present + // => interpretation of this cell during name-compilation and unknown names + // => can't exchange underlying code array in CompileTokenArray() / + // Compile() because interpreter's token iterator would crash or pCode + // would be deleted twice if this cell was interpreted during + // compilation. + // This should only be a temporary condition and, since we set an + // error, if ran into it again we'd bump into the dirty-clearing + // condition further down. + if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() ) + { + pCode->SetCodeError( FormulaError::NoCode ); + // This is worth an assertion; if encountered in daily work + // documents we might need another solution. Or just confirm correctness. + return; + } + CompileTokenArray(); + } + + if( pCode->GetCodeLen() ) + { + std::unique_ptr<ScInterpreter> pScopedInterpreter; + ScInterpreter* pInterpreter; + if (rContext.pInterpreter) + { + pInterpreter = rContext.pInterpreter; + pInterpreter->Init(this, aPos, *pCode); + } + else + { + pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rContext, aPos, *pCode )); + pInterpreter = pScopedInterpreter.get(); + } + + FormulaError nOldErrCode = aResult.GetResultError(); + if ( nSeenInIteration == 0 ) + { // Only the first time + // With bChanged=false, if a newly compiled cell has a result of + // 0.0, no change is detected and the cell will not be repainted. + // bChanged = false; + aResult.SetResultError( FormulaError::NONE ); + } + + switch ( aResult.GetResultError() ) + { + case FormulaError::CircularReference : // will be determined again if so + aResult.SetResultError( FormulaError::NONE ); + break; + default: break; + } + + bool bOldRunning = bRunning; + bRunning = true; + pInterpreter->Interpret(); + if (rDocument.GetRecursionHelper().IsInReturn() && eTailParam != SCITP_CLOSE_ITERATION_CIRCLE) + { + if (nSeenInIteration > 0) + --nSeenInIteration; // retry when iteration is resumed + + if ( aResult.GetType() == formula::svUnknown ) + aResult.SetToken( pInterpreter->GetResultToken().get() ); + + return; + } + bRunning = bOldRunning; + + // The result may be invalid or depend on another invalid result, just abort + // without updating the cell value. Since the dirty flag will not be reset, + // the proper value will be computed later. + if(rDocument.GetRecursionHelper().IsAbortingDependencyComputation()) + return; + + // #i102616# For single-sheet saving consider only content changes, not format type, + // because format type isn't set on loading (might be changed later) + bool bContentChanged = false; + + // Do not create a HyperLink() cell if the formula results in an error. + if( pInterpreter->GetError() != FormulaError::NONE && pCode->IsHyperLink()) + pCode->SetHyperLink(false); + + if( pInterpreter->GetError() != FormulaError::NONE && pInterpreter->GetError() != FormulaError::CircularReference) + { + bChanged = true; + + if (pInterpreter->GetError() == FormulaError::RetryCircular) + { + // Array formula matrix calculation corner case. Keep dirty + // state, do not remove from formula tree or anything else, but + // store FormulaError::CircularReference in case this cell does not get + // recalculated. + aResult.SetResultError( FormulaError::CircularReference); + return; + } + + ResetDirty(); + } + + if (eTailParam == SCITP_FROM_ITERATION && IsDirtyOrInTableOpDirty()) + { + bool bIsValue = aResult.IsValue(); // the previous type + // Did it converge? + if ((bIsValue && pInterpreter->GetResultType() == svDouble && fabs( + pInterpreter->GetNumResult() - aResult.GetDouble()) <= + rDocument.GetDocOptions().GetIterEps()) || + (!bIsValue && pInterpreter->GetResultType() == svString && + pInterpreter->GetStringResult() == aResult.GetString())) + { + // A convergence in the first iteration doesn't necessarily + // mean that it's done, it may be as not all related cells + // of a circle changed their values yet. If the set really + // converges it will do so also during the next iteration. This + // fixes situations like of #i44115#. If this wasn't wanted an + // initial "uncalculated" value would be needed for all cells + // of a circular dependency => graph needed before calculation. + if (nSeenInIteration > 1 || + rDocument.GetDocOptions().GetIterCount() == 1) + { + ResetDirty(); + } + } + } + + // New error code? + if( pInterpreter->GetError() != nOldErrCode ) + { + bChanged = true; + // bContentChanged only has to be set if the file content would be changed + if ( aResult.GetCellResultType() != svUnknown ) + bContentChanged = true; + } + + ScFormulaResult aNewResult( pInterpreter->GetResultToken().get()); + + // For IF() and other jumps or changed formatted source data the result + // format may change for different runs, e.g. =IF(B1,B1) with first + // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23 + // displayed as TRUE. Do not force a general format though if + // mbNeedsNumberFormat is set (because there was a general format..). + // Note that nFormatType may be out of sync here if a format was + // applied or cleared after the last run, but obtaining the current + // format always just to check would be expensive. There may be + // cases where the format should be changed but is not. If that turns + // out to be a real problem then obtain the current format type after + // the initial check when needed. + bool bForceNumberFormat = (mbAllowNumberFormatChange && !mbNeedsNumberFormat && + !SvNumberFormatter::IsCompatible( nFormatType, pInterpreter->GetRetFormatType())); + + // We have some requirements additionally to IsCompatible(). + // * Do not apply a NumberFormat::LOGICAL if the result value is not + // 1.0 or 0.0 + // * Do not override an already set numeric number format if the result + // is of type NumberFormat::LOGICAL, it could be user applied. + // On the other hand, for an empty jump path instead of FALSE an + // unexpected for example 0% could be displayed. YMMV. + // * Never override a non-standard number format that indicates user + // applied. + // * NumberFormat::TEXT does not force a change. + if (bForceNumberFormat) + { + sal_uInt32 nOldFormatIndex = NUMBERFORMAT_ENTRY_NOT_FOUND; + const SvNumFormatType nRetType = pInterpreter->GetRetFormatType(); + if (nRetType == SvNumFormatType::LOGICAL) + { + double fVal = aNewResult.GetDouble(); + if (fVal != 1.0 && fVal != 0.0) + bForceNumberFormat = false; + else + { + nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos); + nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex); + switch (nFormatType) + { + case SvNumFormatType::PERCENT: + case SvNumFormatType::CURRENCY: + case SvNumFormatType::SCIENTIFIC: + case SvNumFormatType::FRACTION: + bForceNumberFormat = false; + break; + case SvNumFormatType::NUMBER: + if ((nOldFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0) + bForceNumberFormat = false; + break; + default: break; + } + } + } + else if (nRetType == SvNumFormatType::TEXT) + { + bForceNumberFormat = false; + } + if (bForceNumberFormat) + { + if (nOldFormatIndex == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos); + nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex); + } + if (nOldFormatIndex != + ScGlobal::GetStandardFormat( *rContext.GetFormatTable(), nOldFormatIndex, nFormatType)) + bForceNumberFormat = false; + } + } + + if( mbNeedsNumberFormat || bForceNumberFormat ) + { + bool bSetFormat = true; + const SvNumFormatType nOldFormatType = nFormatType; + nFormatType = pInterpreter->GetRetFormatType(); + sal_uInt32 nFormatIndex = pInterpreter->GetRetFormatIndex(); + + if (nFormatType == SvNumFormatType::TEXT) + { + // Don't set text format as hard format. + bSetFormat = false; + } + else if (nFormatType == SvNumFormatType::LOGICAL && cMatrixFlag != ScMatrixMode::NONE) + { + // In a matrix range do not set an (inherited) logical format + // as hard format if the value does not represent a strict TRUE + // or FALSE value. But do set for a top left error value so + // following matrix cells can inherit for non-error values. + // This solves a problem with IF() expressions in array context + // where incidentally the top left element results in logical + // type but some others don't. It still doesn't solve the + // reverse case though, where top left is not logical type but + // some other elements should be. We'd need to transport type + // or format information on arrays. + StackVar eNewCellResultType = aNewResult.GetCellResultType(); + if (eNewCellResultType != svError || cMatrixFlag == ScMatrixMode::Reference) + { + if (eNewCellResultType != svDouble) + { + bSetFormat = false; + nFormatType = nOldFormatType; // that? or number? + } + else + { + double fVal = aNewResult.GetDouble(); + if (fVal != 1.0 && fVal != 0.0) + { + bSetFormat = false; + nFormatType = SvNumFormatType::NUMBER; + } + } + } + } + + if (bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) == 0))) + nFormatIndex = ScGlobal::GetStandardFormat(*rContext.GetFormatTable(), + nFormatIndex, nFormatType); + + // Do not replace a General format (which was the reason why + // mbNeedsNumberFormat was set) with a General format. + // 1. setting a format has quite some overhead in the + // ScPatternAttr/ScAttrArray handling, even if identical. + // 2. the General formats may be of different locales. + // XXX if mbNeedsNumberFormat was set even if the current format + // was not General then we'd have to obtain the current format here + // and check at least the types. + const bool bSetNumberFormat = bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0)); + if (bSetNumberFormat && !rDocument.IsInLayoutStrings()) + { + // set number format explicitly + if (!rDocument.IsThreadedGroupCalcInProgress()) + rDocument.SetNumberFormat( aPos, nFormatIndex ); + else + { + // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work + // to the main thread. Since thread calculations operate on formula groups, + // it's enough to store just the row. + DelayedSetNumberFormat data = { aPos.Col(), aPos.Row(), nFormatIndex }; + rContext.maDelayedSetNumberFormat.push_back( data ); + } + bChanged = true; + } + + // Currently (2019-05-10) nothing else can cope with a duration + // format type, change to time as it was before. + if (nFormatType == SvNumFormatType::DURATION) + nFormatType = SvNumFormatType::TIME; + + mbNeedsNumberFormat = false; + } + + // In case of changes just obtain the result, no temporary and + // comparison needed anymore. + if (bChanged) + { + // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving + // Also handle special cases of initial results after loading. + if ( !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) ) + { + StackVar eOld = aResult.GetCellResultType(); + StackVar eNew = aNewResult.GetCellResultType(); + if ( eOld == svUnknown && ( eNew == svError || ( eNew == svDouble && aNewResult.GetDouble() == 0.0 ) ) ) + { + // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0 + // -> no change + } + else + { + if ( eOld == svHybridCell ) // string result from SetFormulaResultString? + eOld = svString; // ScHybridCellToken has a valid GetString method + + // #i106045# use approxEqual to compare with stored value + bContentChanged = (eOld != eNew || + (eNew == svDouble && !rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble() )) || + (eNew == svString && aResult.GetString() != aNewResult.GetString())); + } + } + + aResult.SetToken( pInterpreter->GetResultToken().get() ); + } + else + { + StackVar eOld = aResult.GetCellResultType(); + StackVar eNew = aNewResult.GetCellResultType(); + bChanged = (eOld != eNew || + (eNew == svDouble && aResult.GetDouble() != aNewResult.GetDouble()) || + (eNew == svString && aResult.GetString() != aNewResult.GetString())); + + // #i102616# handle special cases of initial results after loading + // (only if the sheet is still marked unchanged) + if ( bChanged && !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) ) + { + if ((eOld == svUnknown && (eNew == svError || (eNew == svDouble && aNewResult.GetDouble() == 0.0))) || + ((eOld == svHybridCell) && + eNew == svString && aResult.GetString() == aNewResult.GetString()) || + (eOld == svDouble && eNew == svDouble && + rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble()))) + { + // no change, see above + } + else + bContentChanged = true; + } + + aResult.Assign( aNewResult); + } + + // Precision as shown? + if ( aResult.IsValue() && pInterpreter->GetError() == FormulaError::NONE + && rDocument.GetDocOptions().IsCalcAsShown() + && nFormatType != SvNumFormatType::DATE + && nFormatType != SvNumFormatType::TIME + && nFormatType != SvNumFormatType::DATETIME ) + { + sal_uInt32 nFormat = rDocument.GetNumberFormat( rContext, aPos ); + aResult.SetDouble( rDocument.RoundValueAsShown( + aResult.GetDouble(), nFormat, &rContext)); + } + if (eTailParam == SCITP_NORMAL) + { + ResetDirty(); + } + if( aResult.GetMatrix() ) + { + // If the formula wasn't entered as a matrix formula, live on with + // the upper left corner and let reference counting delete the matrix. + if( cMatrixFlag != ScMatrixMode::Formula && !pCode->IsHyperLink() ) + aResult.SetToken( aResult.GetCellResultToken().get()); + } + if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) ) + { + // Coded double error may occur via filter import. + FormulaError nErr = GetDoubleErrorValue( aResult.GetDouble()); + aResult.SetResultError( nErr); + bChanged = bContentChanged = true; + } + + if (bContentChanged && rDocument.IsStreamValid(aPos.Tab())) + { + // pass bIgnoreLock=true, because even if called from pending row height update, + // a changed result must still reset the stream flag + rDocument.SetStreamValid(aPos.Tab(), false, true); + } + if ( !rDocument.IsThreadedGroupCalcInProgress() && !pCode->IsRecalcModeAlways() ) + rDocument.RemoveFromFormulaTree( this ); + + // FORCED cells also immediately tested for validity (start macro possibly) + + if ( pCode->IsRecalcModeForced() ) + { + sal_uLong nValidation = rDocument.GetAttr( + aPos.Col(), aPos.Row(), aPos.Tab(), ATTR_VALIDDATA )->GetValue(); + if ( nValidation ) + { + const ScValidationData* pData = rDocument.GetValidationEntry( nValidation ); + ScRefCellValue aTmpCell(this); + if ( pData && !pData->IsDataValid(aTmpCell, aPos)) + pData->DoCalcError( this ); + } + } + + // Reschedule slows the whole thing down considerably, thus only execute on percent change + if (!rDocument.IsThreadedGroupCalcInProgress()) + { + ScProgress *pProgress = ScProgress::GetInterpretProgress(); + if (pProgress && pProgress->Enabled()) + { + pProgress->SetStateCountDownOnPercent( + rDocument.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE ); + } + + switch (pInterpreter->GetVolatileType()) + { + case ScInterpreter::VOLATILE: + // Volatile via built-in volatile functions. No actions needed. + break; + case ScInterpreter::VOLATILE_MACRO: + // The formula contains a volatile macro. + pCode->SetExclusiveRecalcModeAlways(); + rDocument.PutInFormulaTree(this); + StartListeningTo(rDocument); + break; + case ScInterpreter::NOT_VOLATILE: + if (pCode->IsRecalcModeAlways()) + { + // The formula was previously volatile, but no more. + EndListeningTo(rDocument); + pCode->SetExclusiveRecalcModeNormal(); + } + else + { + // non-volatile formula. End listening to the area in case + // it's listening due to macro module change. + rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this); + } + rDocument.RemoveFromFormulaTree(this); + break; + default: + ; + } + } + } + else + { + // Cells with compiler errors should not be marked dirty forever + OSL_ENSURE( pCode->GetCodeError() != FormulaError::NONE, "no RPN code and no errors ?!?!" ); + ResetDirty(); + } +} + +void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter* pInterpreter) +{ + if( !pCode->GetCodeLen() ) + return; + + if ( !pCode->IsRecalcModeAlways() ) + rDocument.RemoveFromFormulaTree( this ); + + std::unique_ptr<ScInterpreter> pScopedInterpreter; + if (pInterpreter) + pInterpreter->Init(this, aPos, *pCode); + else + { + pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode )); + pInterpreter = pScopedInterpreter.get(); + } + + switch (pInterpreter->GetVolatileType()) + { + case ScInterpreter::VOLATILE_MACRO: + // The formula contains a volatile macro. + pCode->SetExclusiveRecalcModeAlways(); + rDocument.PutInFormulaTree(this); + StartListeningTo(rDocument); + break; + case ScInterpreter::NOT_VOLATILE: + if (pCode->IsRecalcModeAlways()) + { + // The formula was previously volatile, but no more. + EndListeningTo(rDocument); + pCode->SetExclusiveRecalcModeNormal(); + } + else + { + // non-volatile formula. End listening to the area in case + // it's listening due to macro module change. + rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this); + } + rDocument.RemoveFromFormulaTree(this); + break; + default: + ; + } +} + +void ScFormulaCell::SetCompile( bool bVal ) +{ + bCompile = bVal; +} + +void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows ) +{ + ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst(); + if (pMat) + pMat->SetMatColsRows( nCols, nRows ); + else if (nCols || nRows) + { + aResult.SetToken( new ScMatrixFormulaCellToken( nCols, nRows)); + // Setting the new token actually forces an empty result at this top + // left cell, so have that recalculated. + SetDirty(); + } +} + +void ScFormulaCell::GetMatColsRows( SCCOL & nCols, SCROW & nRows ) const +{ + const ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellToken(); + if (pMat) + pMat->GetMatColsRows( nCols, nRows); + else + { + nCols = 0; + nRows = 0; + } +} + +void ScFormulaCell::SetInChangeTrack( bool bVal ) +{ + bInChangeTrack = bVal; +} + +void ScFormulaCell::Notify( const SfxHint& rHint ) +{ + if (rDocument.IsInDtorClear()) + return; + + const SfxHintId nHint = rHint.GetId(); + if (nHint == SfxHintId::ScReference) + { + const sc::RefHint& rRefHint = static_cast<const sc::RefHint&>(rHint); + + switch (rRefHint.getType()) + { + case sc::RefHint::ColumnReordered: + { + const sc::RefColReorderHint& rRefColReorder = + static_cast<const sc::RefColReorderHint&>(rRefHint); + if (!IsShared() || IsSharedTop()) + pCode->MoveReferenceColReorder( + aPos, rRefColReorder.getTab(), + rRefColReorder.getStartRow(), + rRefColReorder.getEndRow(), + rRefColReorder.getColMap()); + } + break; + case sc::RefHint::RowReordered: + { + const sc::RefRowReorderHint& rRefRowReorder = + static_cast<const sc::RefRowReorderHint&>(rRefHint); + if (!IsShared() || IsSharedTop()) + pCode->MoveReferenceRowReorder( + aPos, rRefRowReorder.getTab(), + rRefRowReorder.getStartColumn(), + rRefRowReorder.getEndColumn(), + rRefRowReorder.getRowMap()); + } + break; + case sc::RefHint::StartListening: + { + StartListeningTo(rDocument); + } + break; + case sc::RefHint::StopListening: + { + EndListeningTo(rDocument); + } + break; + default: + ; + } + + return; + } + + if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF ) + return; + + if (!(nHint == SfxHintId::ScDataChanged || nHint == SfxHintId::ScTableOpDirty || (bSubTotal && nHint == SfxHintId::ScHiddenRowsChanged))) + return; + + bool bForceTrack = false; + if ( nHint == SfxHintId::ScTableOpDirty ) + { + bForceTrack = !bTableOpDirty; + if ( !bTableOpDirty ) + { + rDocument.AddTableOpFormulaCell( this ); + bTableOpDirty = true; + } + } + else + { + bForceTrack = !bDirty; + SetDirtyVar(); + } + // Don't remove from FormulaTree to put in FormulaTrack to + // put in FormulaTree again and again, only if necessary. + // Any other means except ScRecalcMode::ALWAYS by which a cell could + // be in FormulaTree if it would notify other cells through + // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!? + // Yes. The new TableOpDirty made it necessary to have a + // forced mode where formulas may still be in FormulaTree from + // TableOpDirty but have to notify dependents for normal dirty. + if ( (bForceTrack || !rDocument.IsInFormulaTree( this ) + || pCode->IsRecalcModeAlways()) + && !rDocument.IsInFormulaTrack( this ) ) + rDocument.AppendToFormulaTrack( this ); +} + +void ScFormulaCell::Query( SvtListener::QueryBase& rQuery ) const +{ + switch (rQuery.getId()) + { + case SC_LISTENER_QUERY_FORMULA_GROUP_POS: + { + sc::RefQueryFormulaGroup& rRefQuery = + static_cast<sc::RefQueryFormulaGroup&>(rQuery); + if (IsShared()) + rRefQuery.add(aPos); + } + break; + default: + ; + } +} + +void ScFormulaCell::SetDirty( bool bDirtyFlag ) +{ + if (IsInChangeTrack()) + return; + + if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF ) + { + SetDirtyVar(); + rDocument.SetStreamValid(aPos.Tab(), false); + return; + } + + // Avoid multiple formula tracking in Load() and in CompileAll() + // after CopyScenario() and CopyBlockFromClip(). + // If unconditional formula tracking is needed, set bDirty=false + // before calling SetDirty(), for example in CompileTokenArray(). + if ( !bDirty || mbPostponedDirty || !rDocument.IsInFormulaTree( this ) ) + { + if( bDirtyFlag ) + SetDirtyVar(); + rDocument.AppendToFormulaTrack( this ); + + // While loading a document listeners have not been established yet. + // Tracking would remove this cell from the FormulaTrack and add it to + // the FormulaTree, once in there it would be assumed that its + // dependents already had been tracked and it would be skipped on a + // subsequent notify. Postpone tracking until all listeners are set. + if (!rDocument.IsImportingXML()) + rDocument.TrackFormulas(); + } + + rDocument.SetStreamValid(aPos.Tab(), false); +} + +void ScFormulaCell::SetDirtyVar() +{ + bDirty = true; + mbPostponedDirty = false; + if (mxGroup && mxGroup->meCalcState == sc::GroupCalcRunning) + { + mxGroup->meCalcState = sc::GroupCalcEnabled; + mxGroup->mbPartOfCycle = false; + } + + // mark the sheet of this cell to be calculated + //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() ); +} + +void ScFormulaCell::SetDirtyAfterLoad() +{ + bDirty = true; + if ( rDocument.GetHardRecalcState() == ScDocument::HardRecalcState::OFF ) + rDocument.PutInFormulaTree( this ); +} + +void ScFormulaCell::ResetTableOpDirtyVar() +{ + bTableOpDirty = false; +} + +void ScFormulaCell::SetTableOpDirty() +{ + if ( IsInChangeTrack() ) + return; + + if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF ) + bTableOpDirty = true; + else + { + if ( !bTableOpDirty || !rDocument.IsInFormulaTree( this ) ) + { + if ( !bTableOpDirty ) + { + rDocument.AddTableOpFormulaCell( this ); + bTableOpDirty = true; + } + rDocument.AppendToFormulaTrack( this ); + rDocument.TrackFormulas( SfxHintId::ScTableOpDirty ); + } + } +} + +void ScFormulaCell::SetResultDouble( double n ) +{ + aResult.SetDouble(n); +} + +void ScFormulaCell::SetResultToken( const formula::FormulaToken* pToken ) +{ + aResult.SetToken(pToken); +} + +const svl::SharedString & ScFormulaCell::GetResultString() const +{ + return aResult.GetString(); +} + +bool ScFormulaCell::HasHybridStringResult() const +{ + return aResult.GetType() == formula::svHybridCell && !aResult.GetString().isEmpty(); +} + +void ScFormulaCell::SetResultMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL ) +{ + aResult.SetMatrix(nCols, nRows, pMat, pUL); +} + +void ScFormulaCell::SetErrCode( FormulaError n ) +{ + /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is + * used whether it is solely for transport of a simple result error and get + * rid of that abuse. */ + pCode->SetCodeError( n ); + // Hard set errors are transported as result type value per convention, + // e.g. via clipboard. ScFormulaResult::IsValue() and + // ScFormulaResult::GetDouble() handle that. + aResult.SetResultError( n ); +} + +void ScFormulaCell::SetResultError( FormulaError n ) +{ + aResult.SetResultError( n ); +} + +void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits ) +{ + if ( (nBits & ScRecalcMode::EMask) != ScRecalcMode::NORMAL ) + SetDirtyVar(); + if ( nBits & ScRecalcMode::ONLOAD_ONCE ) + { // OnLoadOnce is used only to set Dirty after filter import. + nBits = (nBits & ~ScRecalcMode::EMask) | ScRecalcMode::NORMAL; + } + pCode->AddRecalcMode( nBits ); +} + +void ScFormulaCell::SetHybridDouble( double n ) +{ + aResult.SetHybridDouble( n); +} + +void ScFormulaCell::SetHybridString( const svl::SharedString& r ) +{ + aResult.SetHybridString( r); +} + +void ScFormulaCell::SetHybridEmptyDisplayedAsString() +{ + aResult.SetHybridEmptyDisplayedAsString(); +} + +void ScFormulaCell::SetHybridFormula( const OUString& r, + const formula::FormulaGrammar::Grammar eGrammar ) +{ + aResult.SetHybridFormula( r); eTempGrammar = eGrammar; +} + +OUString ScFormulaCell::GetHybridFormula() const +{ + return aResult.GetHybridFormula(); +} + +// Dynamically create the URLField on a mouse-over action on a hyperlink() cell. +void ScFormulaCell::GetURLResult( OUString& rURL, OUString& rCellText ) +{ + OUString aCellString; + + const Color* pColor; + + // Cell Text uses the Cell format while the URL uses + // the default format for the type. + const sal_uInt32 nCellFormat = rDocument.GetNumberFormat( aPos ); + SvNumberFormatter* pFormatter = rDocument.GetFormatTable(); + + const sal_uInt32 nURLFormat = ScGlobal::GetStandardFormat( *pFormatter, nCellFormat, SvNumFormatType::NUMBER); + + if ( IsValue() ) + { + double fValue = GetValue(); + pFormatter->GetOutputString( fValue, nCellFormat, rCellText, &pColor ); + } + else + { + aCellString = GetString().getString(); + pFormatter->GetOutputString( aCellString, nCellFormat, rCellText, &pColor ); + } + ScConstMatrixRef xMat( aResult.GetMatrix()); + if (xMat) + { + // determine if the matrix result is a string or value. + if (!xMat->IsValue(0, 1)) + rURL = xMat->GetString(0, 1).getString(); + else + pFormatter->GetOutputString( + xMat->GetDouble(0, 1), nURLFormat, rURL, &pColor); + } + + if(rURL.isEmpty()) + { + if(IsValue()) + pFormatter->GetOutputString( GetValue(), nURLFormat, rURL, &pColor ); + else + pFormatter->GetOutputString( aCellString, nURLFormat, rURL, &pColor ); + } +} + +bool ScFormulaCell::IsMultilineResult() +{ + if (!IsValue()) + return aResult.IsMultiline(); + return false; +} + +bool ScFormulaCell::IsHyperLinkCell() const +{ + return pCode && pCode->IsHyperLink(); +} + +std::unique_ptr<EditTextObject> ScFormulaCell::CreateURLObject() +{ + OUString aCellText; + OUString aURL; + GetURLResult( aURL, aCellText ); + + return ScEditUtil::CreateURLObjectFromURL( rDocument, aURL, aCellText ); +} + +bool ScFormulaCell::IsEmpty() +{ + MaybeInterpret(); + return aResult.GetCellResultType() == formula::svEmptyCell; +} + +bool ScFormulaCell::IsEmptyDisplayedAsString() +{ + MaybeInterpret(); + return aResult.IsEmptyDisplayedAsString(); +} + +bool ScFormulaCell::IsValue() +{ + MaybeInterpret(); + return aResult.IsValue(); +} + +bool ScFormulaCell::IsValueNoError() +{ + MaybeInterpret(); + if (pCode->GetCodeError() != FormulaError::NONE) + return false; + + return aResult.IsValueNoError(); +} + +bool ScFormulaCell::IsValueNoError() const +{ + if (NeedsInterpret()) + // false if the cell is dirty & needs to be interpreted. + return false; + + if (pCode->GetCodeError() != FormulaError::NONE) + return false; + + return aResult.IsValueNoError(); +} + +double ScFormulaCell::GetValue() +{ + MaybeInterpret(); + return GetRawValue(); +} + +const svl::SharedString & ScFormulaCell::GetString() +{ + MaybeInterpret(); + return GetRawString(); +} + +double ScFormulaCell::GetRawValue() const +{ + if ((pCode->GetCodeError() == FormulaError::NONE) && + aResult.GetResultError() == FormulaError::NONE) + return aResult.GetDouble(); + return 0.0; +} + +const svl::SharedString & ScFormulaCell::GetRawString() const +{ + if ((pCode->GetCodeError() == FormulaError::NONE) && + aResult.GetResultError() == FormulaError::NONE) + return aResult.GetString(); + + return svl::SharedString::getEmptyString(); +} + +const ScMatrix* ScFormulaCell::GetMatrix() +{ + if ( rDocument.GetAutoCalc() ) + { + if( IsDirtyOrInTableOpDirty() + // Was stored !bDirty but an accompanying matrix cell was bDirty? + || (!bDirty && cMatrixFlag == ScMatrixMode::Formula && !aResult.GetMatrix())) + Interpret(); + } + return aResult.GetMatrix().get(); +} + +bool ScFormulaCell::GetMatrixOrigin( const ScDocument& rDoc, ScAddress& rPos ) const +{ + switch ( cMatrixFlag ) + { + case ScMatrixMode::Formula : + rPos = aPos; + return true; + case ScMatrixMode::Reference : + { + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* t = aIter.GetNextReferenceRPN(); + if( t ) + { + ScSingleRefData& rRef = *t->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(rDoc, aPos); + if (rDoc.ValidAddress(aAbs)) + { + rPos = aAbs; + return true; + } + } + } + break; + default: break; + } + return false; +} + +sc::MatrixEdge ScFormulaCell::GetMatrixEdge( const ScDocument& rDoc, ScAddress& rOrgPos ) const +{ + switch ( cMatrixFlag ) + { + case ScMatrixMode::Formula : + case ScMatrixMode::Reference : + { + static thread_local SCCOL nC; + static thread_local SCROW nR; + ScAddress aOrg; + if ( !GetMatrixOrigin( rDoc, aOrg ) ) + return sc::MatrixEdge::Nothing; + if ( aOrg != rOrgPos ) + { // First time or a different matrix than last time. + rOrgPos = aOrg; + const ScFormulaCell* pFCell; + if ( cMatrixFlag == ScMatrixMode::Reference ) + pFCell = rDocument.GetFormulaCell(aOrg); + else + pFCell = this; // this ScMatrixMode::Formula + // There's only one this, don't compare pFCell==this. + if (pFCell && pFCell->cMatrixFlag == ScMatrixMode::Formula) + { + pFCell->GetMatColsRows( nC, nR ); + if ( nC == 0 || nR == 0 ) + { + // No ScMatrixFormulaCellToken available yet, calculate new. + nC = 1; + nR = 1; + ScAddress aTmpOrg; + ScFormulaCell* pCell; + ScAddress aAdr( aOrg ); + aAdr.IncCol(); + bool bCont = true; + do + { + pCell = rDocument.GetFormulaCell(aAdr); + if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference && + pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg) + { + nC++; + aAdr.IncCol(); + } + else + bCont = false; + } while ( bCont ); + aAdr = aOrg; + aAdr.IncRow(); + bCont = true; + do + { + pCell = rDocument.GetFormulaCell(aAdr); + if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference && + pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg) + { + nR++; + aAdr.IncRow(); + } + else + bCont = false; + } while ( bCont ); + + const_cast<ScFormulaCell*>(pFCell)->SetMatColsRows(nC, nR); + } + } + else + { +#if OSL_DEBUG_LEVEL > 0 + SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: " + << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) + << ", MatOrg: " + << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) ); +#endif + return sc::MatrixEdge::Nothing; + } + } + // here we are, healthy and clean, somewhere in between + SCCOL dC = aPos.Col() - aOrg.Col(); + SCROW dR = aPos.Row() - aOrg.Row(); + sc::MatrixEdge nEdges = sc::MatrixEdge::Nothing; + if ( dC >= 0 && dR >= 0 && dC < nC && dR < nR ) + { + if ( dC == 0 ) + nEdges |= sc::MatrixEdge::Left; + if ( dC+1 == nC ) + nEdges |= sc::MatrixEdge::Right; + if ( dR == 0 ) + nEdges |= sc::MatrixEdge::Top; + if ( dR+1 == nR ) + nEdges |= sc::MatrixEdge::Bottom; + if ( nEdges == sc::MatrixEdge::Nothing ) + nEdges = sc::MatrixEdge::Inside; + } + else + { + SAL_WARN( "sc", "broken Matrix, Pos: " + << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) + << ", MatOrg: " + << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) + << ", MatCols: " << static_cast<sal_Int32>( nC ) + << ", MatRows: " << static_cast<sal_Int32>( nR ) + << ", DiffCols: " << static_cast<sal_Int32>( dC ) + << ", DiffRows: " << static_cast<sal_Int32>( dR )); + } + return nEdges; + } + default: + return sc::MatrixEdge::Nothing; + } +} + +FormulaError ScFormulaCell::GetErrCode() +{ + MaybeInterpret(); + + /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors + * and not also abused for signaling other error conditions we could bail + * out even before attempting to interpret broken code. */ + FormulaError nErr = pCode->GetCodeError(); + if (nErr != FormulaError::NONE) + return nErr; + return aResult.GetResultError(); +} + +FormulaError ScFormulaCell::GetRawError() const +{ + FormulaError nErr = pCode->GetCodeError(); + if (nErr != FormulaError::NONE) + return nErr; + return aResult.GetResultError(); +} + +bool ScFormulaCell::GetErrorOrValue( FormulaError& rErr, double& rVal ) +{ + MaybeInterpret(); + + rErr = pCode->GetCodeError(); + if (rErr != FormulaError::NONE) + return true; + + return aResult.GetErrorOrDouble(rErr, rVal); +} + +sc::FormulaResultValue ScFormulaCell::GetResult() +{ + MaybeInterpret(); + + FormulaError nErr = pCode->GetCodeError(); + if (nErr != FormulaError::NONE) + return sc::FormulaResultValue(nErr); + + return aResult.GetResult(); +} + +sc::FormulaResultValue ScFormulaCell::GetResult() const +{ + FormulaError nErr = pCode->GetCodeError(); + if (nErr != FormulaError::NONE) + return sc::FormulaResultValue(nErr); + + return aResult.GetResult(); +} + +bool ScFormulaCell::HasOneReference( ScRange& r ) const +{ + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* p = aIter.GetNextReferenceRPN(); + if( p && !aIter.GetNextReferenceRPN() ) // only one! + { + SingleDoubleRefProvider aProv( *p ); + r.aStart = aProv.Ref1.toAbs(rDocument, aPos); + r.aEnd = aProv.Ref2.toAbs(rDocument, aPos); + return true; + } + else + return false; +} + +bool +ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange& rRange) const +{ + /* If there appears just one reference in the formula, it's the same + as HasOneReference(). If there are more of them, they can denote + one range if they are (sole) arguments of one function. + Union of these references must form one range and their + intersection must be empty set. + */ + + // Detect the simple case of exactly one reference in advance without all + // overhead. + // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference) + // work again, where the function does not have only references. + if (HasOneReference( rRange)) + return true; + + // Get first reference, if any + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* const pFirstReference(aIter.GetNextReferenceRPN()); + if (pFirstReference) + { + // Collect all consecutive references, starting by the one + // already found + std::vector<formula::FormulaToken*> aReferences { pFirstReference }; + FormulaToken* pToken(aIter.NextRPN()); + FormulaToken* pFunction(nullptr); + while (pToken) + { + if (lcl_isReference(*pToken)) + { + aReferences.push_back(pToken); + pToken = aIter.NextRPN(); + } + else + { + if (pToken->IsFunction()) + { + pFunction = pToken; + } + break; + } + } + if (pFunction && !aIter.GetNextReferenceRPN() + && (pFunction->GetParamCount() == aReferences.size())) + { + return lcl_refListFormsOneRange(rDocument, aPos, aReferences, rRange); + } + } + return false; +} + +ScFormulaCell::RelNameRef ScFormulaCell::HasRelNameReference() const +{ + RelNameRef eRelNameRef = RelNameRef::NONE; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + { + switch (t->GetType()) + { + case formula::svSingleRef: + if (t->GetSingleRef()->IsRelName() && eRelNameRef == RelNameRef::NONE) + eRelNameRef = RelNameRef::SINGLE; + break; + case formula::svDoubleRef: + if (t->GetDoubleRef()->Ref1.IsRelName() || t->GetDoubleRef()->Ref2.IsRelName()) + // May originate from individual cell names, in which case + // it needs recompilation. + return RelNameRef::DOUBLE; + /* TODO: have an extra flag at ScComplexRefData if range was + * extended? or too cumbersome? might narrow recompilation to + * only needed cases. + * */ + break; + default: + ; // nothing + } + } + return eRelNameRef; +} + +bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext& rCxt ) +{ + if (rCxt.meMode != URM_INSDEL) + // Just in case... + return false; + + if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta) + // No movement. + return false; + + if (!rCxt.maRange.Contains(aPos)) + return false; + + // This formula cell itself is being shifted during cell range + // insertion or deletion. Update its position. + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc)) + { + assert(!"can't move ScFormulaCell"); + } + + return true; +} + +namespace { + +/** + * Check if we need to re-compile column or row names. + */ +bool checkCompileColRowName( + const sc::RefUpdateContext& rCxt, ScDocument& rDoc, const ScTokenArray& rCode, + const ScAddress& aOldPos, const ScAddress& aPos, bool bValChanged) +{ + switch (rCxt.meMode) + { + case URM_INSDEL: + { + if (rCxt.mnColDelta <= 0 && rCxt.mnRowDelta <= 0) + return false; + + formula::FormulaTokenArrayPlainIterator aIter(rCode); + formula::FormulaToken* t; + ScRangePairList* pColList = rDoc.GetColNameRanges(); + ScRangePairList* pRowList = rDoc.GetRowNameRanges(); + while ((t = aIter.GetNextColRowName()) != nullptr) + { + ScSingleRefData& rRef = *t->GetSingleRef(); + if (rCxt.mnRowDelta > 0 && rRef.IsColRel()) + { // ColName + ScAddress aAdr = rRef.toAbs(rDoc, aPos); + ScRangePair* pR = pColList->Find( aAdr ); + if ( pR ) + { // defined + if (pR->GetRange(1).aStart.Row() == rCxt.maRange.aStart.Row()) + return true; + } + else + { // on the fly + if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row()) + return true; + } + } + if (rCxt.mnColDelta > 0 && rRef.IsRowRel()) + { // RowName + ScAddress aAdr = rRef.toAbs(rDoc, aPos); + ScRangePair* pR = pRowList->Find( aAdr ); + if ( pR ) + { // defined + if ( pR->GetRange(1).aStart.Col() == rCxt.maRange.aStart.Col()) + return true; + } + else + { // on the fly + if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col()) + return true; + } + } + } + } + break; + case URM_MOVE: + { // Recompile for Move/D&D when ColRowName was moved or this Cell + // points to one and was moved. + bool bMoved = (aPos != aOldPos); + if (bMoved) + return true; + + formula::FormulaTokenArrayPlainIterator aIter(rCode); + const formula::FormulaToken* t = aIter.GetNextColRowName(); + for (; t; t = aIter.GetNextColRowName()) + { + const ScSingleRefData& rRef = *t->GetSingleRef(); + ScAddress aAbs = rRef.toAbs(rDoc, aPos); + if (rDoc.ValidAddress(aAbs)) + { + if (rCxt.maRange.Contains(aAbs)) + return true; + } + } + } + break; + case URM_COPY: + return bValChanged; + default: + ; + } + + return false; +} + +void setOldCodeToUndo( + ScDocument& rUndoDoc, const ScAddress& aUndoPos, const ScTokenArray* pOldCode, FormulaGrammar::Grammar eTempGrammar, ScMatrixMode cMatrixFlag) +{ + // Copy the cell to aUndoPos, which is its current position in the document, + // so this works when UpdateReference is called before moving the cells + // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference + // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed). + + // If there is already a formula cell in the undo document, don't overwrite it, + // the first (oldest) is the important cell. + if (rUndoDoc.GetCellType(aUndoPos) == CELLTYPE_FORMULA) + return; + + ScFormulaCell* pFCell = + new ScFormulaCell( + rUndoDoc, aUndoPos, pOldCode ? *pOldCode : ScTokenArray(rUndoDoc), eTempGrammar, cMatrixFlag); + + pFCell->SetResultToken(nullptr); // to recognize it as changed later (Cut/Paste!) + rUndoDoc.SetFormulaCell(aUndoPos, pFCell); +} + +} + +bool ScFormulaCell::UpdateReferenceOnShift( + const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos ) +{ + if (rCxt.meMode != URM_INSDEL) + // Just in case... + return false; + + bool bCellStateChanged = false; + ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc + if ( pUndoCellPos ) + aUndoPos = *pUndoCellPos; + ScAddress aOldPos( aPos ); + bCellStateChanged = UpdatePosOnShift(rCxt); + + // Check presence of any references or column row names. + bool bHasRefs = pCode->HasReferences(); + bool bHasColRowNames = false; + if (!bHasRefs) + { + bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr); + bHasRefs = bHasColRowNames; + } + bool bOnRefMove = pCode->IsRecalcModeOnRefMove(); + + if (!bHasRefs && !bOnRefMove) + // This formula cell contains no references, nor needs recalculating + // on reference update. Bail out. + return bCellStateChanged; + + std::unique_ptr<ScTokenArray> pOldCode; + if (pUndoDoc) + pOldCode = pCode->Clone(); + + bool bValChanged = false; + bool bRefModified = false; + bool bRecompile = bCompile; + + if (bHasRefs) + { + // Update cell or range references. + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(rCxt, aOldPos); + bRefModified = aRes.mbReferenceModified; + bValChanged = aRes.mbValueChanged; + if (aRes.mbNameModified) + bRecompile = true; + } + + if (bValChanged || bRefModified) + bCellStateChanged = true; + + if (bOnRefMove) + // Cell may reference itself, e.g. ocColumn, ocRow without parameter + bOnRefMove = (bValChanged || (aPos != aOldPos) || bRefModified); + + bool bNewListening = false; + bool bInDeleteUndo = false; + + if (bHasRefs) + { + // Upon Insert ColRowNames have to be recompiled in case the + // insertion occurs right in front of the range. + if (bHasColRowNames && !bRecompile) + bRecompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged); + + ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack(); + bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo()); + + // RelNameRefs are always moved + bool bHasRelName = false; + if (!bRecompile) + { + RelNameRef eRelNameRef = HasRelNameReference(); + bHasRelName = (eRelNameRef != RelNameRef::NONE); + bRecompile = (eRelNameRef == RelNameRef::DOUBLE); + } + // Reference changed and new listening needed? + // Except in Insert/Delete without specialities. + bNewListening = (bRefModified || bRecompile + || (bValChanged && bInDeleteUndo) || bHasRelName); + + if ( bNewListening ) + EndListeningTo(rDocument, pOldCode.get(), aOldPos); + } + + // NeedDirty for changes except for Copy and Move/Insert without RelNames + bool bNeedDirty = (bValChanged || bRecompile || bOnRefMove); + + if (pUndoDoc && (bValChanged || bOnRefMove)) + setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag); + + bCompile |= bRecompile; + if (bCompile) + { + CompileTokenArray( bNewListening ); // no Listening + bNeedDirty = true; + } + + if ( !bInDeleteUndo ) + { // In ChangeTrack Delete-Reject listeners are established in + // InsertCol/InsertRow + if ( bNewListening ) + { + // Inserts/Deletes re-establish listeners after all + // UpdateReference calls. + // All replaced shared formula listeners have to be + // established after an Insert or Delete. Do nothing here. + SetNeedsListening( true); + } + } + + if (bNeedDirty) + { // Cut off references, invalid or similar? + // Postpone SetDirty() until all listeners have been re-established in + // Inserts/Deletes. + mbPostponedDirty = true; + } + + return bCellStateChanged; +} + +bool ScFormulaCell::UpdateReferenceOnMove( + const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos ) +{ + if (rCxt.meMode != URM_MOVE) + return false; + + ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc + if ( pUndoCellPos ) + aUndoPos = *pUndoCellPos; + ScAddress aOldPos( aPos ); + + bool bCellInMoveTarget = rCxt.maRange.Contains(aPos); + + if ( bCellInMoveTarget ) + { + // The cell is being moved or copied to a new position. I guess the + // position has been updated prior to this call? Determine + // its original position before the move which will be used to adjust + // relative references later. + aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta); + } + + // Check presence of any references or column row names. + bool bHasRefs = pCode->HasReferences(); + bool bHasColRowNames = false; + if (!bHasRefs) + { + bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr); + bHasRefs = bHasColRowNames; + } + bool bOnRefMove = pCode->IsRecalcModeOnRefMove(); + + if (!bHasRefs && !bOnRefMove) + // This formula cell contains no references, nor needs recalculating + // on reference update. Bail out. + return false; + + bool bCellStateChanged = false; + std::unique_ptr<ScTokenArray> pOldCode; + if (pUndoDoc) + pOldCode = pCode->Clone(); + + bool bValChanged = false; + bool bRefModified = false; + + if (bHasRefs) + { + // Update cell or range references. + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(rCxt, aOldPos, aPos); + bRefModified = aRes.mbReferenceModified || aRes.mbNameModified; + bValChanged = aRes.mbValueChanged; + if (aRes.mbNameModified) + // Re-compile to get the RPN token regenerated to reflect updated names. + bCompile = true; + } + + if (bValChanged || bRefModified) + bCellStateChanged = true; + + if (bOnRefMove) + // Cell may reference itself, e.g. ocColumn, ocRow without parameter + bOnRefMove = (bValChanged || (aPos != aOldPos)); + + bool bColRowNameCompile = false; + bool bHasRelName = false; + bool bNewListening = false; + bool bInDeleteUndo = false; + + if (bHasRefs) + { + // Upon Insert ColRowNames have to be recompiled in case the + // insertion occurs right in front of the range. + if (bHasColRowNames) + bColRowNameCompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged); + + ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack(); + bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo()); + + // RelNameRefs are always moved + RelNameRef eRelNameRef = HasRelNameReference(); + bHasRelName = (eRelNameRef != RelNameRef::NONE); + bCompile |= (eRelNameRef == RelNameRef::DOUBLE); + // Reference changed and new listening needed? + // Except in Insert/Delete without specialties. + bNewListening = (bRefModified || bColRowNameCompile + || bValChanged || bHasRelName) + // #i36299# Don't duplicate action during cut&paste / drag&drop + // on a cell in the range moved, start/end listeners is done + // via ScDocument::DeleteArea() and ScDocument::CopyFromClip(). + && !(rDocument.IsInsertingFromOtherDoc() && rCxt.maRange.Contains(aPos)); + + if ( bNewListening ) + EndListeningTo(rDocument, pOldCode.get(), aOldPos); + } + + bool bNeedDirty = false; + // NeedDirty for changes except for Copy and Move/Insert without RelNames + if ( bRefModified || bColRowNameCompile || + (bValChanged && bHasRelName ) || bOnRefMove) + bNeedDirty = true; + + if (pUndoDoc && !bCellInMoveTarget && (bValChanged || bRefModified || bOnRefMove)) + setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag); + + bValChanged = false; + + bCompile = (bCompile || bValChanged || bColRowNameCompile); + if ( bCompile ) + { + CompileTokenArray( bNewListening ); // no Listening + bNeedDirty = true; + } + + if ( !bInDeleteUndo ) + { // In ChangeTrack Delete-Reject listeners are established in + // InsertCol/InsertRow + if ( bNewListening ) + { + StartListeningTo( rDocument ); + } + } + + if (bNeedDirty) + { // Cut off references, invalid or similar? + sc::AutoCalcSwitch aACSwitch(rDocument, false); + SetDirty(); + } + + return bCellStateChanged; +} + +bool ScFormulaCell::UpdateReferenceOnCopy( + const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos ) +{ + if (rCxt.meMode != URM_COPY) + return false; + + ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc + if ( pUndoCellPos ) + aUndoPos = *pUndoCellPos; + ScAddress aOldPos( aPos ); + + if (rCxt.maRange.Contains(aPos)) + { + // The cell is being moved or copied to a new position. I guess the + // position has been updated prior to this call? Determine + // its original position before the move which will be used to adjust + // relative references later. + aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta); + } + + // Check presence of any references or column row names. + bool bHasRefs = pCode->HasReferences(); + bool bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr); + bHasRefs = bHasRefs || bHasColRowNames; + bool bOnRefMove = pCode->IsRecalcModeOnRefMove(); + + if (!bHasRefs && !bOnRefMove) + // This formula cell contains no references, nor needs recalculating + // on reference update. Bail out. + return false; + + std::unique_ptr<ScTokenArray> pOldCode; + if (pUndoDoc) + pOldCode = pCode->Clone(); + + if (bOnRefMove) + // Cell may reference itself, e.g. ocColumn, ocRow without parameter + bOnRefMove = (aPos != aOldPos); + + bool bNeedDirty = bOnRefMove; + + if (pUndoDoc && bOnRefMove) + setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag); + + if (bCompile) + { + CompileTokenArray(); // no Listening + bNeedDirty = true; + } + + if (bNeedDirty) + { // Cut off references, invalid or similar? + sc::AutoCalcSwitch aACSwitch(rDocument, false); + SetDirty(); + } + + return false; +} + +bool ScFormulaCell::UpdateReference( + const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos ) +{ + if (rDocument.IsClipOrUndo()) + return false; + + if (mxGroup && mxGroup->mpTopCell != this) + { + // This is not a top cell of a formula group. Don't update references. + + switch (rCxt.meMode) + { + case URM_INSDEL: + return UpdatePosOnShift(rCxt); + default: + ; + } + return false; + } + + switch (rCxt.meMode) + { + case URM_INSDEL: + return UpdateReferenceOnShift(rCxt, pUndoDoc, pUndoCellPos); + case URM_MOVE: + return UpdateReferenceOnMove(rCxt, pUndoDoc, pUndoCellPos); + case URM_COPY: + return UpdateReferenceOnCopy(rCxt, pUndoDoc, pUndoCellPos); + default: + ; + } + + return false; +} + +void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt ) +{ + // Adjust tokens only when it's not grouped or grouped top cell. + bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this; + bool bPosChanged = (rCxt.mnInsertPos <= aPos.Tab()); + if (rDocument.IsClipOrUndo() || !pCode->HasReferences()) + { + if (bPosChanged) + aPos.IncTab(rCxt.mnSheets); + + return; + } + + EndListeningTo( rDocument ); + ScAddress aOldPos = aPos; + // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab! + if (bPosChanged) + aPos.IncTab(rCxt.mnSheets); + + if (!bAdjustCode) + return; + + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aOldPos); + if (aRes.mbNameModified) + // Re-compile after new sheet(s) have been inserted. + bCompile = true; + + // no StartListeningTo because the new sheets have not been inserted yet. +} + +void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt ) +{ + // Adjust tokens only when it's not grouped or grouped top cell. + bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this; + bool bPosChanged = (aPos.Tab() >= rCxt.mnDeletePos + rCxt.mnSheets); + if (rDocument.IsClipOrUndo() || !pCode->HasReferences()) + { + if (bPosChanged) + aPos.IncTab(-1*rCxt.mnSheets); + return; + } + + EndListeningTo( rDocument ); + // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab! + ScAddress aOldPos = aPos; + if (bPosChanged) + aPos.IncTab(-1*rCxt.mnSheets); + + if (!bAdjustCode) + return; + + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aOldPos); + if (aRes.mbNameModified) + // Re-compile after sheet(s) have been deleted. + bCompile = true; +} + +void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo ) +{ + // Adjust tokens only when it's not grouped or grouped top cell. + bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this; + + if (!pCode->HasReferences() || rDocument.IsClipOrUndo()) + { + aPos.SetTab(nTabNo); + return; + } + + EndListeningTo(rDocument); + ScAddress aOldPos = aPos; + // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab ! + aPos.SetTab(nTabNo); + + // no StartListeningTo because pTab[nTab] not yet correct! + + if (!bAdjustCode) + return; + + sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aOldPos); + if (aRes.mbNameModified) + // Re-compile after sheet(s) have been deleted. + bCompile = true; +} + +void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable) +{ + if (rDocument.IsClipOrUndo()) + return; + + bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this; + if (!bAdjustCode) + return; + + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* p = aIter.GetNextReferenceRPN(); + while (p) + { + ScSingleRefData& rRef1 = *p->GetSingleRef(); + if (!rRef1.IsTabRel() && nTable <= rRef1.Tab()) + rRef1.IncTab(1); + if (p->GetType() == formula::svDoubleRef) + { + ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2; + if (!rRef2.IsTabRel() && nTable <= rRef2.Tab()) + rRef2.IncTab(1); + } + p = aIter.GetNextReferenceRPN(); + } +} + +bool ScFormulaCell::TestTabRefAbs(SCTAB nTable) +{ + if (rDocument.IsClipOrUndo()) + return false; + + bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this; + if (!bAdjustCode) + return false; + + bool bRet = false; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* p = aIter.GetNextReferenceRPN(); + while (p) + { + ScSingleRefData& rRef1 = *p->GetSingleRef(); + if (!rRef1.IsTabRel()) + { + if (nTable != rRef1.Tab()) + bRet = true; + else if (nTable != aPos.Tab()) + rRef1.SetAbsTab(aPos.Tab()); + } + if (p->GetType() == formula::svDoubleRef) + { + ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2; + if (!rRef2.IsTabRel()) + { + if(nTable != rRef2.Tab()) + bRet = true; + else if (nTable != aPos.Tab()) + rRef2.SetAbsTab(aPos.Tab()); + } + } + p = aIter.GetNextReferenceRPN(); + } + return bRet; +} + +void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse ) +{ + if ( bForceIfNameInUse && !bCompile ) + bCompile = pCode->HasNameOrColRowName(); + if ( bCompile ) + pCode->SetCodeError( FormulaError::NONE ); // make sure it will really be compiled + CompileTokenArray(); +} + +static void lcl_TransposeReference(ScSingleRefData& rRef) +{ + // References to or over filtered rows are not adjusted + // analog to the normal (non-transposed) case + SCCOLROW nTemp = rRef.Col(); + rRef.SetRelCol(rRef.Row()); + rRef.SetRelRow(nTemp); +} + +// Reference transposition is only called in Clipboard Document +void ScFormulaCell::TransposeReference() +{ + bool bFound = false; + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReference() ) != nullptr ) + { + ScSingleRefData& rRef1 = *t->GetSingleRef(); + if ( rRef1.IsColRel() && rRef1.IsRowRel() ) + { + bool bDouble = (t->GetType() == formula::svDoubleRef); + ScSingleRefData& rRef2 = (bDouble ? t->GetDoubleRef()->Ref2 : rRef1); + if ( !bDouble || (rRef2.IsColRel() && rRef2.IsRowRel()) ) + { + lcl_TransposeReference(rRef1); + + if ( bDouble ) + lcl_TransposeReference(rRef2); + + bFound = true; + } + } + } + + if (bFound) + bCompile = true; +} + +void ScFormulaCell::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest, + ScDocument* pUndoDoc ) +{ + EndListeningTo( rDocument ); + + ScAddress aOldPos = aPos; + bool bPosChanged = false; // Whether this cell has been moved + + // Dest range is transposed + ScRange aDestRange( rDest, ScAddress( + static_cast<SCCOL>(rDest.Col() + rSource.aEnd.Row() - rSource.aStart.Row()), + static_cast<SCROW>(rDest.Row() + rSource.aEnd.Col() - rSource.aStart.Col()), + rDest.Tab() + rSource.aEnd.Tab() - rSource.aStart.Tab() ) ); + + // cell within range + if ( aDestRange.Contains( aOldPos ) ) + { + // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove() + // Count back Positions + SCCOL nRelPosX = aOldPos.Col(); + SCROW nRelPosY = aOldPos.Row(); + SCTAB nRelPosZ = aOldPos.Tab(); + ScRefUpdate::DoTranspose( nRelPosX, nRelPosY, nRelPosZ, rDocument, aDestRange, rSource.aStart ); + aOldPos.Set( nRelPosX, nRelPosY, nRelPosZ ); + bPosChanged = true; + } + + std::unique_ptr<ScTokenArray> pOld; + if (pUndoDoc) + pOld = pCode->Clone(); + bool bRefChanged = false; + + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* t; + while( (t = aIter.GetNextReferenceOrName()) != nullptr ) + { + if( t->GetOpCode() == ocName ) + { + const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()); + if (pName && pName->IsModified()) + bRefChanged = true; + } + else if( t->GetType() != svIndex ) + { + SingleDoubleRefModifier aMod(*t); + ScComplexRefData& rRef = aMod.Ref(); + ScRange aAbs = rRef.toAbs(rDocument, aOldPos); + bool bMod = (ScRefUpdate::UpdateTranspose(rDocument, rSource, rDest, aAbs) != UR_NOTHING || bPosChanged); + if (bMod) + { + rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); // based on the new anchor position. + bRefChanged = true; + + // Absolute sheet reference => set 3D flag. + // More than one sheet referenced => has to have both 3D flags. + // If end part has 3D flag => start part must have it too. + // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags. + rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel()); + rRef.Ref1.SetFlag3D( + (rSource.aStart.Tab() != rDest.Tab() && !bPosChanged) + || !rRef.Ref1.IsTabRel() || rRef.Ref2.IsFlag3D()); + } + } + } + + if (bRefChanged) + { + if (pUndoDoc) + { + // Similar to setOldCodeToUndo(), but it cannot be used due to the check + // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA + ScFormulaCell* pFCell = new ScFormulaCell( + *pUndoDoc, aPos, pOld ? *pOld : ScTokenArray(*pUndoDoc), eTempGrammar, cMatrixFlag); + + pFCell->aResult.SetToken( nullptr); // to recognize it as changed later (Cut/Paste!) + pUndoDoc->SetFormulaCell(aPos, pFCell); + } + + bCompile = true; + CompileTokenArray(); // also call StartListeningTo + SetDirty(); + } + else + StartListeningTo( rDocument ); // Listener as previous +} + +void ScFormulaCell::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY ) +{ + EndListeningTo( rDocument ); + + bool bRefChanged = false; + + formula::FormulaTokenArrayPlainIterator aIter(*pCode); + formula::FormulaToken* t; + + while( (t = aIter.GetNextReferenceOrName()) != nullptr ) + { + if( t->GetOpCode() == ocName ) + { + const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex()); + if (pName && pName->IsModified()) + bRefChanged = true; + } + else if( t->GetType() != svIndex ) + { + SingleDoubleRefModifier aMod(*t); + ScComplexRefData& rRef = aMod.Ref(); + ScRange aAbs = rRef.toAbs(rDocument, aPos); + bool bMod = (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING); + if (bMod) + { + rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); + bRefChanged = true; + } + } + } + + if (bRefChanged) + { + bCompile = true; + CompileTokenArray(); // Also call StartListeningTo + SetDirty(); + } + else + StartListeningTo( rDocument ); // Listener as previous +} + +// See also ScDocument::FindRangeNamesReferencingSheet() +static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes, const ScTokenArray* pCode, const ScDocument& rDoc, + int nRecursion) +{ + FormulaTokenArrayPlainIterator aIter(*pCode); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetOpCode() == ocName) + { + sal_uInt16 nTokenIndex = p->GetIndex(); + SCTAB nTab = p->GetSheet(); + rIndexes.setUpdatedName( nTab, nTokenIndex); + + if (nRecursion < 126) // whatever... 42*3 + { + ScRangeData* pSubName = rDoc.FindRangeNameBySheetAndIndex( nTab, nTokenIndex); + if (pSubName) + lcl_FindRangeNamesInUse(rIndexes, pSubName->GetCode(), rDoc, nRecursion+1); + } + } + } +} + +void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes) const +{ + lcl_FindRangeNamesInUse( rIndexes, pCode, rDocument, 0); +} + +void ScFormulaCell::SetChanged(bool b) +{ + bChanged = b; +} + +void ScFormulaCell::SetCode( std::unique_ptr<ScTokenArray> pNew ) +{ + assert(!mxGroup); // Don't call this if it's shared. + delete pCode; + pCode = pNew.release(); // takes ownership. +} + +void ScFormulaCell::SetRunning( bool bVal ) +{ + bRunning = bVal; +} + +void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext& rCxt ) +{ + FormulaTokenArrayPlainIterator aIter(*pCode); + for( FormulaToken* p = aIter.First(); p; p = aIter.Next() ) + { + OpCode eOp = p->GetOpCode(); + if ( eOp == ocDBArea || eOp == ocTableRef ) + { + bCompile = true; + CompileTokenArray(rCxt); + SetDirty(); + break; + } + } +} + +void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt ) +{ + FormulaTokenArrayPlainIterator aIter(*pCode); + for ( FormulaToken* p = aIter.First(); p; p = aIter.Next() ) + { + if ( p->GetOpCode() == ocColRowName ) + { + bCompile = true; + CompileTokenArray(rCxt); + SetDirty(); + break; + } + } +} + +void ScFormulaCell::SetPrevious( ScFormulaCell* pF ) { pPrevious = pF; } +void ScFormulaCell::SetNext( ScFormulaCell* pF ) { pNext = pF; } +void ScFormulaCell::SetPreviousTrack( ScFormulaCell* pF ) { pPreviousTrack = pF; } +void ScFormulaCell::SetNextTrack( ScFormulaCell* pF ) { pNextTrack = pF; } + +ScFormulaCellGroupRef ScFormulaCell::CreateCellGroup( SCROW nLen, bool bInvariant ) +{ + if (mxGroup) + { + // You can't create a new group if the cell is already a part of a group. + // Is this a sign of some inconsistent or incorrect data structures? Or normal? + SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group"); + return ScFormulaCellGroupRef(); + } + + mxGroup.reset(new ScFormulaCellGroup); + mxGroup->mpTopCell = this; + mxGroup->mbInvariant = bInvariant; + mxGroup->mnLength = nLen; + mxGroup->mpCode = std::move(*pCode); // Move this to the shared location. + delete pCode; + pCode = &*mxGroup->mpCode; + return mxGroup; +} + +void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef &xRef ) +{ + if (!xRef) + { + // Make this cell a non-grouped cell. + if (mxGroup) + pCode = mxGroup->mpCode->Clone().release(); + + mxGroup = xRef; + return; + } + + // Group object has shared token array. + if (!mxGroup) + // Currently not shared. Delete the existing token array first. + delete pCode; + + mxGroup = xRef; + pCode = &*mxGroup->mpCode; + mxGroup->mnWeight = 0; // invalidate +} + +ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaCell& rOther ) const +{ + // no Matrix formulae yet. + if ( GetMatrixFlag() != ScMatrixMode::NONE ) + return NotEqual; + + // are these formulas at all similar ? + if ( GetHash() != rOther.GetHash() ) + return NotEqual; + + if (!pCode->IsShareable() || !rOther.pCode->IsShareable()) + return NotEqual; + + FormulaToken **pThis = pCode->GetCode(); + sal_uInt16 nThisLen = pCode->GetCodeLen(); + FormulaToken **pOther = rOther.pCode->GetCode(); + sal_uInt16 nOtherLen = rOther.pCode->GetCodeLen(); + + if ( !pThis || !pOther ) + { + // Error: no compiled code for cells !" + return NotEqual; + } + + if ( nThisLen != nOtherLen ) + return NotEqual; + + // No tokens can be an error cell so check error code, otherwise we could + // end up with a series of equal error values instead of individual error + // values. Also if for any reason different errors are set even if all + // tokens are equal, the cells are not equal. + if (pCode->GetCodeError() != rOther.pCode->GetCodeError()) + return NotEqual; + + bool bInvariant = true; + + // check we are basically the same function + for ( sal_uInt16 i = 0; i < nThisLen; i++ ) + { + formula::FormulaToken *pThisTok = pThis[i]; + formula::FormulaToken *pOtherTok = pOther[i]; + + if ( pThisTok->GetType() != pOtherTok->GetType() || + pThisTok->GetOpCode() != pOtherTok->GetOpCode() || + pThisTok->GetParamCount() != pOtherTok->GetParamCount() ) + { + // Incompatible type, op-code or param counts. + return NotEqual; + } + + switch (pThisTok->GetType()) + { + case formula::svMatrix: + case formula::svExternalSingleRef: + case formula::svExternalDoubleRef: + // Ignoring matrix and external references for now. + return NotEqual; + + case formula::svSingleRef: + { + // Single cell reference. + const ScSingleRefData& rRef = *pThisTok->GetSingleRef(); + if (rRef != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef.IsRowRel()) + bInvariant = false; + } + break; + case formula::svDoubleRef: + { + // Range reference. + const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef(); + const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2(); + if (rRef1 != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef2 != *pOtherTok->GetSingleRef2()) + return NotEqual; + + if (rRef1.IsRowRel()) + bInvariant = false; + + if (rRef2.IsRowRel()) + bInvariant = false; + } + break; + case formula::svDouble: + { + if(!rtl::math::approxEqual(pThisTok->GetDouble(), pOtherTok->GetDouble())) + return NotEqual; + } + break; + case formula::svString: + { + if(pThisTok->GetString() != pOtherTok->GetString()) + return NotEqual; + } + break; + case formula::svIndex: + { + if(pThisTok->GetIndex() != pOtherTok->GetIndex() || pThisTok->GetSheet() != pOtherTok->GetSheet()) + return NotEqual; + } + break; + case formula::svByte: + { + if(pThisTok->GetByte() != pOtherTok->GetByte()) + return NotEqual; + } + break; + case formula::svExternal: + { + if (pThisTok->GetExternal() != pOtherTok->GetExternal()) + return NotEqual; + + if (pThisTok->GetByte() != pOtherTok->GetByte()) + return NotEqual; + } + break; + case formula::svError: + { + if (pThisTok->GetError() != pOtherTok->GetError()) + return NotEqual; + } + break; + default: + ; + } + } + + // If still the same, check lexical names as different names may result in + // identical RPN code. + + pThis = pCode->GetArray(); + nThisLen = pCode->GetLen(); + pOther = rOther.pCode->GetArray(); + nOtherLen = rOther.pCode->GetLen(); + + if ( !pThis || !pOther ) + { + // Error: no code for cells !" + return NotEqual; + } + + if ( nThisLen != nOtherLen ) + return NotEqual; + + for ( sal_uInt16 i = 0; i < nThisLen; i++ ) + { + formula::FormulaToken *pThisTok = pThis[i]; + formula::FormulaToken *pOtherTok = pOther[i]; + + if ( pThisTok->GetType() != pOtherTok->GetType() || + pThisTok->GetOpCode() != pOtherTok->GetOpCode() || + pThisTok->GetParamCount() != pOtherTok->GetParamCount() ) + { + // Incompatible type, op-code or param counts. + return NotEqual; + } + + switch (pThisTok->GetType()) + { + // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code, + // resulting in identical RPN references that could lead to creating + // a formula group from formulas that should not be merged into a group, + // so check also the formula itself. + case formula::svSingleRef: + { + // Single cell reference. + const ScSingleRefData& rRef = *pThisTok->GetSingleRef(); + if (rRef != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef.IsRowRel()) + bInvariant = false; + } + break; + case formula::svDoubleRef: + { + // Range reference. + const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef(); + const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2(); + if (rRef1 != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef2 != *pOtherTok->GetSingleRef2()) + return NotEqual; + + if (rRef1.IsRowRel()) + bInvariant = false; + + if (rRef2.IsRowRel()) + bInvariant = false; + } + break; + // All index tokens are names. Different categories already had + // different OpCode values. + case formula::svIndex: + { + if (pThisTok->GetIndex() != pOtherTok->GetIndex()) + return NotEqual; + switch (pThisTok->GetOpCode()) + { + case ocTableRef: + // nothing, sheet value assumed as -1, silence + // ScTableRefToken::GetSheet() SAL_WARN about + // unhandled + ; + break; + default: // ocName, ocDBArea + if (pThisTok->GetSheet() != pOtherTok->GetSheet()) + return NotEqual; + } + } + break; + default: + ; + } + } + + return bInvariant ? EqualInvariant : EqualRelativeRef; +} + +namespace { + +// Split N into optimally equal-sized pieces, each not larger than K. +// Return value P is number of pieces. A returns the number of pieces +// one larger than N/P, 0..P-1. + +int splitup(int N, int K, int& A) +{ + assert(N > 0); + assert(K > 0); + + A = 0; + + if (N <= K) + return 1; + + const int ideal_num_parts = N / K; + if (ideal_num_parts * K == N) + return ideal_num_parts; + + const int num_parts = ideal_num_parts + 1; + const int nominal_part_size = N / num_parts; + + A = N - num_parts * nominal_part_size; + + return num_parts; +} + +struct ScDependantsCalculator +{ + ScDocument& mrDoc; + const ScTokenArray& mrCode; + const ScFormulaCellGroupRef& mxGroup; + const SCROW mnLen; + const ScAddress& mrPos; + const bool mFromFirstRow; + const SCROW mnStartOffset; + const SCROW mnEndOffset; + const SCROW mnSpanLen; + + ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell, + const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) : + mrDoc(rDoc), + mrCode(rCode), + mxGroup(rCell.GetCellGroup()), + mnLen(mxGroup->mnLength), + mrPos(rPos), + // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used + // only from further rows. This data fetching could also lead to Interpret() calls, so + // in OpenCL mode the formula in practice depends on those cells too. + mFromFirstRow(fromFirstRow), + mnStartOffset(nStartOffset), + mnEndOffset(nEndOffset), + mnSpanLen(nEndOffset - nStartOffset + 1) + { + } + + // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else + // (note already modified a bit, mFromFirstRow) + + // I think what this function does is to check whether the relative row reference nRelRow points + // to a row that is inside the range of rows covered by the formula group. + + bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow) + { + if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab()) + return false; + + SCROW nEndRow = mrPos.Row() + mnLen - 1; + + if (nRelRow <= 0) + { + SCROW nTest = nEndRow; + nTest += nRelRow; + if (nTest >= mrPos.Row()) + return true; + } + else + { + SCROW nTest = mrPos.Row(); // top row. + nTest += nRelRow; + if (nTest <= nEndRow) + return true; + // If pointing below the formula, it's always included if going from first row. + if (mFromFirstRow) + return true; + } + + return false; + } + + // FIXME: another copy-paste + + // And this correspondingly checks whether an absolute row is inside the range of rows covered + // by the formula group. + + bool isSelfReferenceAbsolute(const ScAddress& rRefPos) + { + if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab()) + return false; + + SCROW nEndRow = mrPos.Row() + mnLen - 1; + + if (rRefPos.Row() < mrPos.Row()) + return false; + + // If pointing below the formula, it's always included if going from first row. + if (rRefPos.Row() > nEndRow && !mFromFirstRow) + return false; + + return true; + } + + // Checks if the doubleref engulfs all of formula group cells + // Note : does not check if there is a partial overlap, that can be done by calling + // isSelfReference[Absolute|Relative]() on both the start and end of the double ref + bool isDoubleRefSpanGroupRange(const ScRange& rAbs, bool bIsRef1RowRel, bool bIsRef2RowRel) + { + if (rAbs.aStart.Col() > mrPos.Col() || rAbs.aEnd.Col() < mrPos.Col() + || rAbs.aStart.Tab() > mrPos.Tab() || rAbs.aEnd.Tab() < mrPos.Tab()) + { + return false; + } + + SCROW nStartRow = mrPos.Row(); + SCROW nEndRow = nStartRow + mnLen - 1; + SCROW nRefStartRow = rAbs.aStart.Row(); + SCROW nRefEndRow = rAbs.aEnd.Row(); + + if (bIsRef1RowRel && bIsRef2RowRel && + ((nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) || + ((nRefStartRow + mnLen - 1) <= nStartRow && + (nRefEndRow + mnLen - 1) >= nEndRow))) + return true; + + if (!bIsRef1RowRel && nRefStartRow <= nStartRow && + (nRefEndRow >= nEndRow || (nRefEndRow + mnLen - 1) >= nEndRow)) + return true; + + if (!bIsRef2RowRel && + nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) + return true; + + // If going from first row, the referenced range must be entirely above the formula, + // otherwise the formula would be included. + if (mFromFirstRow && nRefEndRow >= nStartRow) + return true; + + return false; + } + + // FIXME: another copy-paste + SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen) + { + SCROW nLastRow = nRow + nRowLen - 1; // current last row. + nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow); + if (nLastRow < (nRow + nRowLen - 1)) + { + // This can end up negative! Was that the original intent, or + // is it accidental? Was it not like that originally but the + // surrounding conditions changed? + nRowLen = nLastRow - nRow + 1; + // Anyway, let's assume it doesn't make sense to return a + // negative or zero value here. + if (nRowLen <= 0) + nRowLen = 1; + } + else if (nLastRow == 0) + // Column is empty. + nRowLen = 1; + + return nRowLen; + } + + bool DoIt() + { + // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx + + ScRangeList aRangeList; + + // Self references should be checked by considering the entire formula-group not just the provided span. + bool bHasSelfReferences = false; + bool bInDocShellRecalc = mrDoc.IsInDocShellRecalc(); + + FormulaToken** pRPNArray = mrCode.GetCode(); + sal_uInt16 nCodeLen = mrCode.GetCodeLen(); + for (sal_Int32 nTokenIdx = nCodeLen-1; nTokenIdx >= 0; --nTokenIdx) + { + auto p = pRPNArray[nTokenIdx]; + if (!bInDocShellRecalc) + { + // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective + // of the result of the condition expression. + // This is a perf problem if we *don't* intent on recalc'ing all dirty cells + // in the document. So lets disable threading and stop dependency evaluation if + // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc() + // for formulae with IF/IFS/SWITCH + OpCode nOpCode = p->GetOpCode(); + if (nOpCode == ocIf || nOpCode == ocIfs_MS || nOpCode == ocSwitch_MS) + return false; + } + + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData aRef = *p->GetSingleRef(); // =Sheet1!A1 + if( aRef.IsDeleted()) + return false; + ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos); + + if (!mrDoc.TableExists(aRefPos.Tab())) + return false; // or true? + + if (aRef.IsRowRel()) + { + if (isSelfReferenceRelative(aRefPos, aRef.Row())) + { + bHasSelfReferences = true; + continue; + } + + // Trim data array length to actual data range. + SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen); + + aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(), + aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab())); + } + else + { + if (isSelfReferenceAbsolute(aRefPos)) + { + bHasSelfReferences = true; + continue; + } + + aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab())); + } + } + break; + case svDoubleRef: + { + ScComplexRefData aRef = *p->GetDoubleRef(); + if( aRef.IsDeleted()) + return false; + ScRange aAbs = aRef.toAbs(mrDoc, mrPos); + + // Multiple sheet + if (aRef.Ref1.Tab() != aRef.Ref2.Tab()) + return false; + + bool bIsRef1RowRel = aRef.Ref1.IsRowRel(); + // Check for self reference. + if (bIsRef1RowRel) + { + if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row())) + { + bHasSelfReferences = true; + continue; + } + } + else if (isSelfReferenceAbsolute(aAbs.aStart)) + { + bHasSelfReferences = true; + continue; + } + + bool bIsRef2RowRel = aRef.Ref2.IsRowRel(); + if (bIsRef2RowRel) + { + if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row())) + { + bHasSelfReferences = true; + continue; + } + } + else if (isSelfReferenceAbsolute(aAbs.aEnd)) + { + bHasSelfReferences = true; + continue; + } + + if (isDoubleRefSpanGroupRange(aAbs, bIsRef1RowRel, bIsRef2RowRel)) + { + bHasSelfReferences = true; + continue; + } + + // The first row that will be referenced through the doubleref. + SCROW nFirstRefRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row(); + // The last row that will be referenced through the doubleref. + SCROW nLastRefRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row(); + // Number of rows to be evaluated from nFirstRefRow. + SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1; + assert(nArrayLength > 0); + + // Trim trailing empty rows. + nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength); + + aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(), + aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab())); + } + break; + default: + break; + } + } + + // Compute dependencies irrespective of the presence of any self references. + // These dependencies would get computed via InterpretTail anyway when we disable group calc, so lets do it now. + // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications. + for (size_t i = 0; i < aRangeList.size(); ++i) + { + const ScRange & rRange = aRangeList[i]; + assert(rRange.aStart.Tab() == rRange.aEnd.Tab()); + for (auto nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); nCol++) + { + SCROW nStartRow = rRange.aStart.Row(); + SCROW nLength = rRange.aEnd.Row() - rRange.aStart.Row() + 1; + if( mFromFirstRow ) + { // include also all previous rows + nLength += nStartRow; + nStartRow = 0; + } + if (!mrDoc.HandleRefArrayForParallelism(ScAddress(nCol, nStartRow, rRange.aStart.Tab()), + nLength, mxGroup)) + return false; + } + } + + if (bHasSelfReferences) + mxGroup->mbPartOfCycle = true; + + return !bHasSelfReferences; + } +}; + +} // anonymous namespace + +bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset) +{ + if (!mxGroup || !pCode) + return false; + + auto aScope = sc::FormulaLogger::get().enterGroup(rDocument, *this); + ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper(); + + if (mxGroup->mbPartOfCycle) + { + aScope.addMessage("This formula-group is part of a cycle"); + return false; + } + + if (mxGroup->meCalcState == sc::GroupCalcDisabled) + { + static constexpr OUStringLiteral MESSAGE = u"group calc disabled"; + aScope.addMessage(MESSAGE); + return false; + } + + // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests + static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType(); + if (forceType == ForceCalculationCore + || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize + && forceType != ForceCalculationOpenCL + && forceType != ForceCalculationThreads)) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + aScope.addGroupSizeThresholdMessage(*this); + return false; + } + + if (cMatrixFlag != ScMatrixMode::NONE) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + aScope.addMessage("matrix skipped"); + return false; + } + + if( forceType != ForceCalculationNone ) + { + // ScConditionEntry::Interpret() creates a temporary cell and interprets it + // without it actually being in the document at the specified position. + // That would confuse opencl/threading code, as they refer to the cell group + // also using the position. This is normally not triggered (single cells + // are normally not in a cell group), but if forced, check for this explicitly. + if( rDocument.GetFormulaCell( aPos ) != this ) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + aScope.addMessage("cell not in document"); + return false; + } + } + + // Get rid of -1's in offsets (defaults) or any invalid offsets. + SCROW nMaxOffset = mxGroup->mnLength - 1; + nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset); + nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset); + + if (nEndOffset < nStartOffset) + { + nStartOffset = 0; + nEndOffset = nMaxOffset; + } + + if (nEndOffset == nStartOffset) + return false; // Do not use threads for a single row. + + // Guard against endless recursion of Interpret() calls, for this to work + // ScFormulaCell::InterpretFormulaGroup() must never be called through + // anything else than ScFormulaCell::Interpret(), same as + // ScFormulaCell::InterpretTail() + RecursionCounter aRecursionCounter( rRecursionHelper, this); + + bool bDependencyComputed = false; + bool bDependencyCheckFailed = false; + + // Preference order: First try OpenCL, then threading. + // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default. + if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed)) + return true; + + if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset)) + return true; + + return false; +} + +bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow, + SCROW nStartOffset, SCROW nEndOffset, + bool bCalcDependencyOnly) +{ + ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper(); + // iterate over code in the formula ... + // ensure all input is pre-calculated - + // to avoid writing during the calculation + if (bCalcDependencyOnly) + { + // Lets not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding + // "ScFormulaGroupCycleCheckGuard" for this formula-group. + // (We can only reach here from a multi-group dependency evaluation attempt). + // (These two have to be in pairs always for any given formula-group) + ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset); + return aCalculator.DoIt(); + } + + bool bOKToParallelize = false; + { + ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this); + if (mxGroup->mbPartOfCycle) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + rScope.addMessage("found circular formula-group dependencies"); + return false; + } + + ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper); + ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset); + bOKToParallelize = aCalculator.DoIt(); + + } + + if (rRecursionHelper.IsInRecursionReturn()) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + rScope.addMessage("Recursion limit reached, cannot thread this formula group now"); + return false; + } + + if (mxGroup->mbPartOfCycle) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + rScope.addMessage("found circular formula-group dependencies"); + return false; + } + + if (!rRecursionHelper.AreGroupsIndependent()) + { + // This call resulted from a dependency calculation for a multigroup-threading attempt, + // but found dependency among the groups. + rScope.addMessage("multi-group-dependency failed"); + return false; + } + + if (!bOKToParallelize) + { + mxGroup->meCalcState = sc::GroupCalcDisabled; + rScope.addMessage("could not do new dependencies calculation thing"); + return false; + } + + return true; +} + +static SCCOL lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef& xGroup, const ScDocument& rDoc, + o3tl::sorted_vector<ScFormulaCellGroup*>& rFGSet, + std::map<SCCOL, ScFormulaCell*>& rFGMap, bool bLeft) +{ + const SCROW nLen = xGroup->mnLength; + const sal_Int32 nWt = xGroup->mnWeight; + ScAddress aAddr(xGroup->mpTopCell->aPos); + + SCCOL nColRet = aAddr.Col(); + + const SCCOL nMaxCol = rDoc.GetAllocatedColumnsCount(aAddr.Tab()) - 1; + if (bLeft) + --nColRet; + else + ++nColRet; + + while (nColRet >= 0 && nColRet <= nMaxCol) + { + aAddr.SetCol(nColRet); + const ScFormulaCell* pCell = rDoc.GetFormulaCell(aAddr); + if (!pCell) + break; + + if (!pCell->NeedsInterpret()) + break; + + const ScFormulaCellGroupRef& xNGroup = pCell->GetCellGroup(); + if (!xNGroup) + break; + + if (!pCell->GetCode()->IsEnabledForThreading()) + break; + + if (xNGroup->mpTopCell->aPos.Row() != aAddr.Row()) + break; + + const SCROW nNLen = xNGroup->mnLength; + const sal_Int32 nNWt = pCell->GetWeight(); + if (nNLen != nLen || nNWt != nWt) + break; + + rFGSet.insert(xNGroup.get()); + rFGMap[nColRet] = xNGroup->mpTopCell; + + if (bLeft) + --nColRet; + else + ++nColRet; + } + + if (bLeft) + ++nColRet; + else + --nColRet; + + return nColRet; +} + +// To be called only from InterpretFormulaGroup(). +bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope, + bool& bDependencyComputed, + bool& bDependencyCheckFailed, + SCROW nStartOffset, + SCROW nEndOffset) +{ + static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION"); + if (!bDependencyCheckFailed && !bThreadingProhibited && + pCode->IsEnabledForThreading() && + ScCalcConfig::isThreadingEnabled()) + { + if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset)) + { + bDependencyComputed = true; + bDependencyCheckFailed = true; + return false; + } + + bDependencyComputed = true; + + // Then do the threaded calculation + + class Executor : public comphelper::ThreadTask + { + private: + const unsigned mnThisThread; + const unsigned mnThreadsTotal; + ScDocument* mpDocument; + ScInterpreterContext* mpContext; + const ScAddress& mrTopPos; + SCCOL mnStartCol; + SCCOL mnEndCol; + SCROW mnStartOffset; + SCROW mnEndOffset; + + public: + Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag, + unsigned nThisThread, + unsigned nThreadsTotal, + ScDocument* pDocument2, + ScInterpreterContext* pContext, + const ScAddress& rTopPos, + SCCOL nStartCol, + SCCOL nEndCol, + SCROW nStartOff, + SCROW nEndOff) : + comphelper::ThreadTask(rTag), + mnThisThread(nThisThread), + mnThreadsTotal(nThreadsTotal), + mpDocument(pDocument2), + mpContext(pContext), + mrTopPos(rTopPos), + mnStartCol(nStartCol), + mnEndCol(nEndCol), + mnStartOffset(nStartOff), + mnEndOffset(nEndOff) + { + } + + virtual void doWork() override + { + ScRange aCalcRange(mnStartCol, mrTopPos.Row() + mnStartOffset, mrTopPos.Tab(), + mnEndCol, mrTopPos.Row() + mnEndOffset, mrTopPos.Tab()); + mpDocument->CalculateInColumnInThread(*mpContext, aCalcRange, mnThisThread, mnThreadsTotal); + } + + }; + + SvNumberFormatter* pNonThreadedFormatter = rDocument.GetNonThreadedContext().GetFormatTable(); + + comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool()); + sal_Int32 nThreadCount = rThreadPool.getWorkerCount(); + + SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads"); + + o3tl::sorted_vector<ScFormulaCellGroup*> aFGSet; + std::map<SCCOL, ScFormulaCell*> aFGMap; + aFGSet.insert(mxGroup.get()); + + ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper(); + SCCOL nColStart = aPos.Col(); + SCCOL nColEnd = nColStart; + if (!rRecursionHelper.HasFormulaGroupSet() && rDocument.IsInDocShellRecalc()) + { + nColStart = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, true); + nColEnd = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, false); + } + + if (nColStart != nColEnd) + { + ScCheckIndependentFGGuard aGuard(rRecursionHelper, &aFGSet); + for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol) + { + if (nCurrCol == aPos.Col()) + continue; + + bool bFGOK = aFGMap[nCurrCol]->CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset, true); + if (!bFGOK || !aGuard.AreGroupsIndependent()) + { + nColEnd = nColStart = aPos.Col(); + break; + } + } + } + + std::vector<std::unique_ptr<ScInterpreter>> aInterpreters(nThreadCount); + { + assert(!rDocument.IsThreadedGroupCalcInProgress()); + rDocument.SetThreadedGroupCalcInProgress(true); + + ScMutationDisable aGuard(rDocument, ScMutationGuardFlags::CORE); + + // Start nThreadCount new threads + std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag(); + ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, rDocument, pNonThreadedFormatter); + ScInterpreterContext* context = nullptr; + + for (int i = 0; i < nThreadCount; ++i) + { + context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i); + assert(!context->pInterpreter); + aInterpreters[i].reset(new ScInterpreter(this, rDocument, *context, mxGroup->mpTopCell->aPos, *pCode, true)); + context->pInterpreter = aInterpreters[i].get(); + rDocument.SetupContextFromNonThreadedContext(*context, i); + rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, &rDocument, context, mxGroup->mpTopCell->aPos, + nColStart, nColEnd, nStartOffset, nEndOffset)); + } + + SAL_INFO("sc.threaded", "Waiting for threads to finish work"); + // Do not join the threads here. They will get joined in ScDocument destructor + // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone). + rThreadPool.waitUntilDone(aTag, false); + + rDocument.SetThreadedGroupCalcInProgress(false); + + for (int i = 0; i < nThreadCount; ++i) + { + context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i); + // This is intentionally done in this main thread in order to avoid locking. + rDocument.MergeContextBackIntoNonThreadedContext(*context, i); + context->pInterpreter = nullptr; + } + + SAL_INFO("sc.threaded", "Done"); + } + + ScAddress aStartPos(mxGroup->mpTopCell->aPos); + SCROW nSpanLen = nEndOffset - nStartOffset + 1; + aStartPos.SetRow(aStartPos.Row() + nStartOffset); + // Reuse one of the previously allocated interpreter objects here. + rDocument.HandleStuffAfterParallelCalculation(nColStart, nColEnd, aStartPos.Row(), nSpanLen, + aStartPos.Tab(), aInterpreters[0].get()); + + return true; + } + + return false; +} + +// To be called only from InterpretFormulaGroup(). +bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope, + bool& bDependencyComputed, + bool& bDependencyCheckFailed) +{ + bool bCanVectorize = pCode->IsEnabledForOpenCL(); + switch (pCode->GetVectorState()) + { + case FormulaVectorEnabled: + case FormulaVectorCheckReference: + break; + + // Not good. + case FormulaVectorDisabledByOpCode: + aScope.addMessage("group calc disabled due to vector state (non-vector-supporting opcode)"); + break; + case FormulaVectorDisabledByStackVariable: + aScope.addMessage("group calc disabled due to vector state (non-vector-supporting stack variable)"); + break; + case FormulaVectorDisabledNotInSubSet: + aScope.addMessage("group calc disabled due to vector state (opcode not in subset)"); + break; + case FormulaVectorDisabled: + case FormulaVectorUnknown: + default: + aScope.addMessage("group calc disabled due to vector state (unknown)"); + return false; + } + + if (!bCanVectorize) + return false; + + if (!ScCalcConfig::isOpenCLEnabled()) + { + aScope.addMessage("opencl not enabled"); + return false; + } + + // TableOp does tricks with using a cell with different values, just bail out. + if(rDocument.IsInInterpreterTableOp()) + return false; + + if (bDependencyCheckFailed) + return false; + + if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1)) + { + bDependencyComputed = true; + bDependencyCheckFailed = true; + return false; + } + + bDependencyComputed = true; + + // TODO : Disable invariant formula group interpretation for now in order + // to get implicit intersection to work. + if (mxGroup->mbInvariant && false) + return InterpretInvariantFormulaGroup(); + + int nMaxGroupLength = INT_MAX; + +#ifdef _WIN32 + // Heuristic: Certain old low-end OpenCL implementations don't + // work for us with too large group lengths. 1000 was determined + // empirically to be a good compromise. + if (openclwrapper::gpuEnv.mbNeedsTDRAvoidance) + nMaxGroupLength = 1000; +#endif + + if (std::getenv("SC_MAX_GROUP_LENGTH")) + nMaxGroupLength = std::atoi(std::getenv("SC_MAX_GROUP_LENGTH")); + + int nNumOnePlus; + const int nNumParts = splitup(GetSharedLength(), nMaxGroupLength, nNumOnePlus); + + int nOffset = 0; + int nCurChunkSize; + ScAddress aOrigPos = mxGroup->mpTopCell->aPos; + for (int i = 0; i < nNumParts; i++, nOffset += nCurChunkSize) + { + nCurChunkSize = GetSharedLength()/nNumParts + (i < nNumOnePlus ? 1 : 0); + + ScFormulaCellGroupRef xGroup; + + if (nNumParts == 1) + xGroup = mxGroup; + else + { + // Ugly hack + xGroup = new ScFormulaCellGroup(); + xGroup->mpTopCell = mxGroup->mpTopCell; + xGroup->mpTopCell->aPos = aOrigPos; + xGroup->mpTopCell->aPos.IncRow(nOffset); + xGroup->mbInvariant = mxGroup->mbInvariant; + xGroup->mnLength = nCurChunkSize; + xGroup->mpCode = std::move(mxGroup->mpCode); // temporarily transfer + } + + ScTokenArray aCode(rDocument); + ScGroupTokenConverter aConverter(aCode, rDocument, *this, xGroup->mpTopCell->aPos); + // TODO avoid this extra compilation + ScCompiler aComp( rDocument, xGroup->mpTopCell->aPos, *pCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, true, cMatrixFlag != ScMatrixMode::NONE ); + aComp.CompileTokenArray(); + if (aComp.HasUnhandledPossibleImplicitIntersections() || !aConverter.convert(*pCode, aScope)) + { + if(aComp.HasUnhandledPossibleImplicitIntersections()) + { + SAL_INFO("sc.opencl", "group " << xGroup->mpTopCell->aPos << " has unhandled implicit intersections, disabling"); +#ifdef DBG_UTIL + for( const OpCode opcode : aComp.UnhandledPossibleImplicitIntersectionsOpCodes()) + { + SAL_INFO("sc.opencl", "unhandled implicit intersection opcode " + << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opcode) + << "(" << int(opcode) << ")"); + } +#endif + } + else + SAL_INFO("sc.opencl", "conversion of group " << xGroup->mpTopCell->aPos << " failed, disabling"); + + mxGroup->meCalcState = sc::GroupCalcDisabled; + + // Undo the hack above + if (nNumParts > 1) + { + mxGroup->mpTopCell->aPos = aOrigPos; + xGroup->mpTopCell = nullptr; + mxGroup->mpCode = std::move(xGroup->mpCode); + } + + aScope.addMessage("group token conversion failed"); + return false; + } + + // The converted code does not have RPN tokens yet. The interpreter will + // generate them. + xGroup->meCalcState = mxGroup->meCalcState = sc::GroupCalcRunning; + sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic(); + + if (pInterpreter == nullptr || + !pInterpreter->interpret(rDocument, xGroup->mpTopCell->aPos, xGroup, aCode)) + { + SAL_INFO("sc.opencl", "interpreting group " << mxGroup->mpTopCell->aPos + << " (state " << static_cast<int>(mxGroup->meCalcState) << ") failed, disabling"); + mxGroup->meCalcState = sc::GroupCalcDisabled; + + // Undo the hack above + if (nNumParts > 1) + { + mxGroup->mpTopCell->aPos = aOrigPos; + xGroup->mpTopCell = nullptr; + mxGroup->mpCode = std::move(xGroup->mpCode); + } + + aScope.addMessage("group interpretation unsuccessful"); + return false; + } + + aScope.setCalcComplete(); + + if (nNumParts > 1) + { + xGroup->mpTopCell = nullptr; + mxGroup->mpCode = std::move(xGroup->mpCode); + } + } + + if (nNumParts > 1) + mxGroup->mpTopCell->aPos = aOrigPos; + mxGroup->meCalcState = sc::GroupCalcEnabled; + return true; +} + +bool ScFormulaCell::InterpretInvariantFormulaGroup() +{ + if (pCode->GetVectorState() == FormulaVectorCheckReference) + { + // An invariant group should only have absolute row references, and no + // external references are allowed. + + ScTokenArray aCode(rDocument); + FormulaTokenArrayPlainIterator aIter(*pCode); + for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + switch (p->GetType()) + { + case svSingleRef: + { + ScSingleRefData aRef = *p->GetSingleRef(); + ScAddress aRefPos = aRef.toAbs(rDocument, aPos); + formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefPos); + if (!pNewToken) + return false; + + aCode.AddToken(*pNewToken); + } + break; + case svDoubleRef: + { + ScComplexRefData aRef = *p->GetDoubleRef(); + ScRange aRefRange = aRef.toAbs(rDocument, aPos); + formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefRange); + if (!pNewToken) + return false; + + aCode.AddToken(*pNewToken); + } + break; + default: + aCode.AddToken(*p); + } + } + + ScCompiler aComp(rDocument, aPos, aCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE); + aComp.CompileTokenArray(); // Create RPN token array. + ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, aCode); + aInterpreter.Interpret(); + aResult.SetToken(aInterpreter.GetResultToken().get()); + } + else + { + // Formula contains no references. + ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode); + aInterpreter.Interpret(); + aResult.SetToken(aInterpreter.GetResultToken().get()); + } + + for ( sal_Int32 i = 0; i < mxGroup->mnLength; i++ ) + { + ScAddress aTmpPos = aPos; + aTmpPos.SetRow(mxGroup->mpTopCell->aPos.Row() + i); + ScFormulaCell* pCell = rDocument.GetFormulaCell(aTmpPos); + if (!pCell) + { + SAL_WARN("sc.core.formulacell", "GetFormulaCell not found"); + continue; + } + + // FIXME: this set of horrors is unclear to me ... certainly + // the above GetCell is profoundly nasty & slow ... + // Ensure the cell truly has a result: + pCell->aResult = aResult; + pCell->ResetDirty(); + pCell->SetChanged(true); + } + + return true; +} + +namespace { + +void startListeningArea( + ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken) +{ + const ScSingleRefData& rRef1 = *rToken.GetSingleRef(); + const ScSingleRefData& rRef2 = *rToken.GetSingleRef2(); + ScAddress aCell1 = rRef1.toAbs(rDoc, rPos); + ScAddress aCell2 = rRef2.toAbs(rDoc, rPos); + if (!(aCell1.IsValid() && aCell2.IsValid())) + return; + + if (rToken.GetOpCode() == ocColRowNameAuto) + { // automagically + if ( rRef1.IsColRel() ) + { // ColName + aCell2.SetRow(rDoc.MaxRow()); + } + else + { // RowName + aCell2.SetCol(rDoc.MaxCol()); + } + } + rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell); +} + +} + +void ScFormulaCell::StartListeningTo( ScDocument& rDoc ) +{ + if (mxGroup) + mxGroup->endAllGroupListening(rDoc); + + if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack()) + return; + + rDoc.SetDetectiveDirty(true); // It has changed something + + ScTokenArray* pArr = GetCode(); + if( pArr->IsRecalcModeAlways() ) + { + rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this); + SetNeedsListening( false); + return; + } + + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + { + switch (t->GetType()) + { + case svSingleRef: + { + ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos); + if (aCell.IsValid()) + rDoc.StartListeningCell(aCell, this); + } + break; + case svDoubleRef: + startListeningArea(this, rDoc, aPos, *t); + break; + default: + ; // nothing + } + } + SetNeedsListening( false); +} + +void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt ) +{ + ScDocument& rDoc = rCxt.getDoc(); + + if (mxGroup) + mxGroup->endAllGroupListening(rDoc); + + if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack()) + return; + + rDoc.SetDetectiveDirty(true); // It has changed something + + ScTokenArray* pArr = GetCode(); + if( pArr->IsRecalcModeAlways() ) + { + rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this); + SetNeedsListening( false); + return; + } + + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + { + switch (t->GetType()) + { + case svSingleRef: + { + ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos); + if (aCell.IsValid()) + rDoc.StartListeningCell(rCxt, aCell, *this); + } + break; + case svDoubleRef: + startListeningArea(this, rDoc, aPos, *t); + break; + default: + ; // nothing + } + } + SetNeedsListening( false); +} + +namespace { + +void endListeningArea( + ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken) +{ + const ScSingleRefData& rRef1 = *rToken.GetSingleRef(); + const ScSingleRefData& rRef2 = *rToken.GetSingleRef2(); + ScAddress aCell1 = rRef1.toAbs(rDoc, rPos); + ScAddress aCell2 = rRef2.toAbs(rDoc, rPos); + if (!(aCell1.IsValid() && aCell2.IsValid())) + return; + + if (rToken.GetOpCode() == ocColRowNameAuto) + { // automagically + if ( rRef1.IsColRel() ) + { // ColName + aCell2.SetRow(rDoc.MaxRow()); + } + else + { // RowName + aCell2.SetCol(rDoc.MaxCol()); + } + } + + rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell); +} + +} + +void ScFormulaCell::EndListeningTo( ScDocument& rDoc, ScTokenArray* pArr, + ScAddress aCellPos ) +{ + if (mxGroup) + mxGroup->endAllGroupListening(rDoc); + + if (rDoc.IsClipOrUndo() || IsInChangeTrack()) + return; + + if (!HasBroadcaster()) + return; + + rDoc.SetDetectiveDirty(true); // It has changed something + + if ( GetCode()->IsRecalcModeAlways() ) + { + rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this); + return; + } + + if (!pArr) + { + pArr = GetCode(); + aCellPos = aPos; + } + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + { + switch (t->GetType()) + { + case svSingleRef: + { + ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos); + if (aCell.IsValid()) + rDoc.EndListeningCell(aCell, this); + } + break; + case svDoubleRef: + endListeningArea(this, rDoc, aCellPos, *t); + break; + default: + ; // nothing + } + } +} + +void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt ) +{ + if (mxGroup) + mxGroup->endAllGroupListening(rCxt.getDoc()); + + if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack()) + return; + + if (!HasBroadcaster()) + return; + + ScDocument& rDoc = rCxt.getDoc(); + rDoc.SetDetectiveDirty(true); // It has changed something + + ScTokenArray* pArr = rCxt.getOldCode(); + ScAddress aCellPos = rCxt.getOldPosition(aPos); + if (!pArr) + pArr = pCode; + + if (pArr->IsRecalcModeAlways()) + { + rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this); + return; + } + + formula::FormulaTokenArrayPlainIterator aIter(*pArr); + formula::FormulaToken* t; + while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr ) + { + switch (t->GetType()) + { + case svSingleRef: + { + ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos); + if (aCell.IsValid()) + rDoc.EndListeningCell(rCxt, aCell, *this); + } + break; + case svDoubleRef: + endListeningArea(this, rDoc, aCellPos, *t); + break; + default: + ; // nothing + } + } +} + +bool ScFormulaCell::IsShared() const +{ + return bool(mxGroup); +} + +bool ScFormulaCell::IsSharedTop() const +{ + if (!mxGroup) + return false; + + return mxGroup->mpTopCell == this; +} + +SCROW ScFormulaCell::GetSharedTopRow() const +{ + return mxGroup ? mxGroup->mpTopCell->aPos.Row() : -1; +} + +SCROW ScFormulaCell::GetSharedLength() const +{ + return mxGroup ? mxGroup->mnLength : 0; +} + +sal_Int32 ScFormulaCell::GetWeight() const +{ + if (!mxGroup) + return 1; + + if (mxGroup->mnWeight > 0) + return mxGroup->mnWeight; + + double nSharedCodeWeight = GetSharedCode()->GetWeight(); + double nResult = nSharedCodeWeight * GetSharedLength(); + if (nResult < SAL_MAX_INT32) + mxGroup->mnWeight = nResult; + else + mxGroup->mnWeight = SAL_MAX_INT32; + + return mxGroup->mnWeight; +} + +ScTokenArray* ScFormulaCell::GetSharedCode() +{ + return mxGroup ? &*mxGroup->mpCode : nullptr; +} + +const ScTokenArray* ScFormulaCell::GetSharedCode() const +{ + return mxGroup ? &*mxGroup->mpCode : nullptr; +} + +void ScFormulaCell::SyncSharedCode() +{ + if (!mxGroup) + // Not a shared formula cell. + return; + + pCode = &*mxGroup->mpCode; +} + +#if DUMP_COLUMN_STORAGE + +void ScFormulaCell::Dump() const +{ + cout << "-- formula cell (" << aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDocument) << ")" << endl; + cout << " * shared: " << (mxGroup ? "true" : "false") << endl; + if (mxGroup) + { + cout << " * shared length: " << mxGroup->mnLength << endl; + cout << " * shared calc state: " << mxGroup->meCalcState << endl; + } + + sc::TokenStringContext aCxt(rDocument, rDocument.GetGrammar()); + cout << " * code: " << pCode->CreateString(aCxt, aPos) << endl; + + FormulaError nErrCode = pCode->GetCodeError(); + cout << " * code error: "; + if (nErrCode == FormulaError::NONE) + cout << "(none)"; + else + { + OUString aStr = ScGlobal::GetErrorString(nErrCode); + cout << " * code error: " << aStr << " (" << int(nErrCode) << ")"; + } + cout << endl; + + cout << " * result: "; + sc::FormulaResultValue aRV = aResult.GetResult(); + switch (aRV.meType) + { + case sc::FormulaResultValue::Value: + cout << aRV.mfValue << " (value)"; + break; + case sc::FormulaResultValue::String: + cout << aRV.maString.getString() << " (string)"; + break; + case sc::FormulaResultValue::Error: + cout << ScGlobal::GetErrorString(aRV.mnError) << " (error: " << int(aRV.mnError) << ")"; + break; + case sc::FormulaResultValue::Invalid: + cout << "(invalid)"; + break; + default: + ; + } + cout << endl; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |