#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
~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;
// 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 *addedabovewithrRecursionHelper.Insert()shouldalways
* 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. #if1 // 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 *usedwhetheritissolelyfortransportofasimpleresulterrorandget
* 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 *andnotalsoabusedforsignalingothererrorconditionswecouldbail
* 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 asHasOneReference().Iftherearemoreofthem,theycandenote onerangeiftheyare(sole)argumentsofonefunction. Unionofthesereferencesmustformonerangeandtheir intersectionmustbeemptyset.
*/
// 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?ortoocumbersome?mightnarrowrecompilationto *onlyneededcases.
* */ 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");
}
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) return1;
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.249Bemerkung:
(vorverarbeitet am 2026-06-10)
¤
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.