/* -*- 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
{
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.47 Sekunden
(vorverarbeitet)
¤
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.