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