/* -*- 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 .
*/
#define DEBUG_CALCULATION 0 #if DEBUG_CALCULATION staticbool bDebugCalculationActive = false; // Set to true for global active init, static ScAddress aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like
/** 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... ;)
*/ staticstruct DebugCalculation
{
std::vector< DebugCalculationEntry > mvPos;
std::vector< DebugCalculationEntry > mvResults;
ScAddress maTrigger;
sal_uInt32 mnGroup; bool mbActive; bool mbSwitchOff; bool mbPrint; bool mbPrintResults;
~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;
/** * 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) {}
// 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();
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;
}
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;
}
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; if (aFormula.getLength() == aShouldBeBuf.getLength() + nLeadingEqual &&
aFormula.match( aShouldBeBuf, 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 );
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.
} elseif (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)
}
// 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; constbool mEnabled;
};
// The result would possibly depend on a cell without a valid value, bail out // the entire dependency computation. if (rRecursionHelper.IsAbortingDependencyComputation()) returnfalse;
if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent()) return bGroupInterpreted;
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 (!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;
#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;
}
// 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;
} elseif (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;
} elseif (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 constbool bOnlyThis = (rRecursionHelper.GetList().size() == 1);
rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;}); if (bOnlyThis)
{
assert(rRecursionHelper.GetList().empty()); if (rRecursionHelper.GetList().empty())
rRecursionHelper.EndIteration();
}
}
}
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();
}
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::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;
}
// 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;
}
}
} elseif (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, nOldFormatIndex, nFormatType))
bForceNumberFormat = false;
}
}
if (nFormatType == SvNumFormatType::TEXT)
{ // Don't set text format as hard format.
bSetFormat = false;
} elseif (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;
}
}
}
}
// 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. constbool 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()));
}
}
// #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_uInt32 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();
}
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::SetMatColsRows( SCCOL nCols, SCROW nRows )
{
ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst(); if (pMat)
pMat->SetMatColsRows( nCols, nRows ); elseif (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();
}
}
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 );
}
// 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.IsInsertingFromOtherDoc())
rDocument.TrackFormulas();
}
// 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::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 );
}
// 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( ScRange(aPos) );
ScInterpreterContext& rContext = rDocument.GetNonThreadedContext();
/* 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();
}
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); returntrue;
} else returnfalse;
}
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)) returntrue;
// 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);
}
} returnfalse;
}
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... returnfalse;
if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta) // No movement. returnfalse;
if (!rCxt.maRange.Contains(aPos)) returnfalse;
// 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");
}
returntrue;
}
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) returnfalse;
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()) returntrue;
} else
{ // on the fly if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row()) returntrue;
}
} 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()) returntrue;
} else
{ // on the fly if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col()) returntrue;
}
}
}
} 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) returntrue;
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)) returntrue;
}
}
} break; case URM_COPY: return bValChanged; default:
;
}
returnfalse;
}
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;
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... returnfalse;
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 = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr); if (!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 bCellStateChanged;
std::unique_ptr<ScTokenArray> pOldCode; if (pUndoDoc)
pOldCode = pCode->Clone();
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);
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;
}
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. returnfalse;
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);
// 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 ( !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();
}
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. returnfalse;
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();
}
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:
;
}
returnfalse;
}
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;
}
// 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 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; elseif (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; elseif (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();
}
staticvoid 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);
// 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
}
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;
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];
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.
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:
;
}
}
// 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;
constint ideal_num_parts = N / K; if (ideal_num_parts * K == N) return ideal_num_parts;
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.
if (nRelRow <= 0)
{
SCROW nTest = nEndRow;
nTest += nRelRow; if (nTest >= mrPos.Row()) returntrue;
} else
{
SCROW nTest = mrPos.Row(); // top row.
nTest += nRelRow; if (nTest <= nEndRow) returntrue; // If pointing below the formula, it's always included if going from first row. if (mFromFirstRow) returntrue;
}
returnfalse;
}
// FIXME: another copy-paste
// And this correspondingly checks whether an absolute row is inside the range of rows covered // by the formula group.
// If pointing below the formula, it's always included if going from first row. if (rRefPos.Row() > nEndRow && !mFromFirstRow) returnfalse;
returntrue;
}
// 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())
{ returnfalse;
}
// If going from first row, the referenced range must be entirely above the formula, // otherwise the formula would be included. if (mFromFirstRow && nRefEndRow >= nStartRow) returntrue;
returnfalse;
}
// 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? constbool bFail = o3tl::checked_sub(nLastRow + 1, nRow, nRowLen); // Anyway, let's assume it doesn't make sense to return a // negative or zero value here. if (bFail || nRowLen <= 0)
nRowLen = 1;
} elseif (nLastRow == 0) // Column is empty.
nRowLen = 1;
return nRowLen;
}
// Because Lookup will extend the Result Vector under certain circumstances listed at: // https://wiki.documentfoundation.org/Documentation/Calc_Functions/LOOKUP // then if the Lookup has a Result Vector only accept the Lookup for parallelization // of the Result Vector has the same dimensions as the Search Vector. bool LookupResultVectorMismatch(sal_Int32 nTokenIdx)
{ if (nTokenIdx >= 3)
{
FormulaToken** pRPNArray = mrCode.GetCode(); if (pRPNArray[nTokenIdx - 1]->GetOpCode() == ocPush && // <- result vector
pRPNArray[nTokenIdx - 2]->GetOpCode() == ocPush && // <- search vector
pRPNArray[nTokenIdx - 2]->GetType() == svDoubleRef &&
pRPNArray[nTokenIdx - 3]->GetOpCode() == ocPush) // <- search criterion
{ auto res = pRPNArray[nTokenIdx - 1]; // If Result vector is just a single cell reference // LOOKUP extends it as a column vector. if (res->GetType() == svSingleRef) returntrue;
// If Result vector is a cell range and the match position // falls outside its length, it gets automatically extended // to the length of Search vector, but in the direction of // Result vector. if (res->GetType() == svDoubleRef)
{
ScComplexRefData aRef1 = *res->GetDoubleRef();
ScComplexRefData aRef2 = *pRPNArray[nTokenIdx - 2]->GetDoubleRef();
ScRange resultRange = aRef1.toAbs(mrDoc, mrPos);
ScRange sourceRange = aRef2.toAbs(mrDoc, mrPos);
bool DoIt(ScRangeList* pSuccessfulDependencies, ScAddress* pDirtiedAddress)
{ // 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 let's 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) returnfalse;
}
if (p->GetOpCode() == ocLookup && LookupResultVectorMismatch(nTokenIdx))
{
SAL_INFO("sc.core.formulacell", "Lookup Result Vector size doesn't match Search Vector"); returnfalse;
}
if (p->GetOpCode() == ocRange)
{ // We are just looking at svSingleRef/svDoubleRef, so we will miss that ocRange constructs // a range from its arguments, and only examining the individual args doesn't capture the // true range of dependencies
SAL_WARN("sc.core.formulacell", "dynamic range, dropping as candidate for parallelizing"); returnfalse;
}
if (!mrDoc.HasTable(aRefPos.Tab())) returnfalse; // 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);
// The first row that will be referenced through the doubleref.
SCROW nFirstRefRow = std::min(nFirstRefStartRow, nLastRefStartRow); // The last row that will be referenced through the doubleref.
SCROW nLastRefRow = std::max(nLastRefEndRow, nFirstRefEndRow);
// Number of rows to be evaluated from nFirstRefRow.
SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
assert(nArrayLength > 0);
// Compute dependencies irrespective of the presence of any self references. // These dependencies would get computed via InterpretTail anyway when we disable group calc, so let's 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, pDirtiedAddress)) returnfalse;
}
}
if (bHasSelfReferences)
mxGroup->mbPartOfCycle = true;
if (pSuccessfulDependencies && !bHasSelfReferences)
*pSuccessfulDependencies = std::move(aRangeList);
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(u"cell not in document"_ustr); returnfalse;
}
}
// 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 && forceType == ForceCalculationNone) returnfalse; // 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);
// 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)) returntrue;
bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
SCROW nStartOffset, SCROW nEndOffset, bool bCalcDependencyOnly,
ScRangeList* pSuccessfulDependencies,
ScAddress* pDirtiedAddress)
{
ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper(); // iterate over code in the formula ... // ensure all input is pre-calculated - // to avoid writing during the calculation if (bCalcDependencyOnly)
{ // Let's 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(pSuccessfulDependencies, pDirtiedAddress);
}
if (rRecursionHelper.IsInRecursionReturn())
{
mxGroup->meCalcState = sc::GroupCalcDisabled;
rScope.addMessage(u"Recursion limit reached, cannot thread this formula group now"_ustr); returnfalse;
}
if (!rRecursionHelper.AreGroupsIndependent())
{ // This call resulted from a dependency calculation for a multigroup-threading attempt, // but found dependency among the groups.
rScope.addMessage(u"multi-group-dependency failed"_ustr); returnfalse;
}
if (!bOKToParallelize)
{
mxGroup->meCalcState = sc::GroupCalcDisabled;
rScope.addMessage(u"could not do new dependencies calculation thing"_ustr); returnfalse;
}
// tdf#156677 it is possible that if a check of a column in the new range fails that the check has // now left a cell that the original range depended on in a Dirty state. So if the dirtied cell // was part of the original dependencies re-run the initial CheckComputeDependencies to fix it. if (!bFGOK && aDirtiedAddress.IsValid() && aOrigDependencies.Find(aDirtiedAddress))
{
SAL_WARN("sc.core.formulacell", "rechecking dependencies due to a dirtied cell during speculative probe"); constbool bRedoEntryCheckSucceeded = CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset);
assert(bRedoEntryCheckSucceeded && "if it worked on the original range it should work again on that range");
(void)bRedoEntryCheckSucceeded;
}
// Here we turn off ref-counting for the contents of pCode on the basis // that pCode is not modified by interpreting and when interpreting is // complete all token refcounts will be back to their initial ref count
FormulaToken** pArray = pCode->GetArray(); for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
pArray[i]->SetRefCntPolicy(RefCntPolicy::None);
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);
// Drop any caches that reference Tokens before restoring ref counting policy for (int i = 0; i < nThreadCount; ++i)
aInterpreters[i]->DropTokenCaches();
for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
pArray[i]->SetRefCntPolicy(RefCntPolicy::ThreadSafe);
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());
returntrue;
}
returnfalse;
}
// 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(u"group calc disabled due to vector state (non-vector-supporting opcode)"_ustr); break; case FormulaVectorDisabledByStackVariable:
aScope.addMessage(u"group calc disabled due to vector state (non-vector-supporting stack variable)"_ustr); break; case FormulaVectorDisabledNotInSubSet:
aScope.addMessage(u"group calc disabled due to vector state (opcode not in subset)"_ustr); break; case FormulaVectorDisabled: case FormulaVectorUnknown: default:
aScope.addMessage(u"group calc disabled due to vector state (unknown)"_ustr); returnfalse;
}
if (!bCanVectorize) returnfalse;
if (!ScCalcConfig::isOpenCLEnabled())
{
aScope.addMessage(u"opencl not enabled"_ustr); returnfalse;
}
// TableOp does tricks with using a cell with different values, just bail out. if(rDocument.IsInInterpreterTableOp()) returnfalse;
// 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; constint 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);
// 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();
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) returnfalse;
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);
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.129Bemerkung:
(vorverarbeitet am 2026-05-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.