1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <config_feature_opencl.h>
21
22 #include <sal/config.h>
23 #include <sal/log.hxx>
24 #include <osl/diagnose.h>
25
26 #include <cassert>
27 #include <cstdlib>
28
29 #include <formulacell.hxx>
30 #include <grouptokenconverter.hxx>
31
32 #include <compiler.hxx>
33 #include <document.hxx>
34 #include <cellvalue.hxx>
35 #include <interpre.hxx>
36 #include <macromgr.hxx>
37 #include <refupdat.hxx>
38 #include <recursionhelper.hxx>
39 #include <docoptio.hxx>
40 #include <rangenam.hxx>
41 #include <rangelst.hxx>
42 #include <dbdata.hxx>
43 #include <progress.hxx>
44 #include <scmatrix.hxx>
45 #include <rechead.hxx>
46 #include <scitems.hxx>
47 #include <validat.hxx>
48 #include <editutil.hxx>
49 #include <chgtrack.hxx>
50 #include <tokenarray.hxx>
51
52 #include <comphelper/threadpool.hxx>
53 #include <editeng/editobj.hxx>
54 #include <formula/errorcodes.hxx>
55 #include <svl/intitem.hxx>
56 #include <svl/numformat.hxx>
57 #include <formulagroup.hxx>
58 #include <listenercontext.hxx>
59 #include <types.hxx>
60 #include <scopetools.hxx>
61 #include <refupdatecontext.hxx>
62 #include <tokenstringcontext.hxx>
63 #include <refhint.hxx>
64 #include <listenerquery.hxx>
65 #include <listenerqueryids.hxx>
66 #include <grouparealistener.hxx>
67 #include <formulalogger.hxx>
68 #include <com/sun/star/sheet/FormulaLanguage.hpp>
69
70 #if HAVE_FEATURE_OPENCL
71 #include <opencl/openclwrapper.hxx>
72 #endif
73
74 #include <memory>
75 #include <map>
76
77 using namespace formula;
78
79 #define DEBUG_CALCULATION 0
80 #if DEBUG_CALCULATION
81 static bool bDebugCalculationActive = false; // Set to true for global active init,
82 static ScAddress aDebugCalculationTriggerAddress(1,2,0); // or on cell Sheet1.B3, whatever you like
83
84 struct DebugCalculationEntry
85 {
86 ScAddress maPos;
87 OUString maResult;
88 const ScDocument& mrDoc;
89 sal_uInt32 mnGroup;
90 sal_uInt16 mnRecursion;
91
DebugCalculationEntryDebugCalculationEntry92 DebugCalculationEntry( const ScAddress& rPos, ScDocument& rDoc, sal_uInt32 nGroup ) :
93 maPos(rPos),
94 mrDoc(rDoc),
95 mnGroup(nGroup),
96 mnRecursion(rDoc.GetRecursionHelper().GetRecursionCount())
97 {
98 }
99 };
100
101 /** Debug/dump formula cell calculation chain.
102 Either, somewhere set aDC.mbActive=true, or
103 aDC.maTrigger=ScAddress(col,row,tab) of interest from where to start.
104 This does not work for deep recursion > MAXRECURSION, the results are
105 somewhat... funny... ;)
106 */
107 static struct DebugCalculation
108 {
109 std::vector< DebugCalculationEntry > mvPos;
110 std::vector< DebugCalculationEntry > mvResults;
111 ScAddress maTrigger;
112 sal_uInt32 mnGroup;
113 bool mbActive;
114 bool mbSwitchOff;
115 bool mbPrint;
116 bool mbPrintResults;
117
DebugCalculationDebugCalculation118 DebugCalculation() : mnGroup(0), mbActive(bDebugCalculationActive), mbSwitchOff(false),
119 mbPrint(true), mbPrintResults(false) {}
120
121 /** Print chain in encountered dependency order. */
printDebugCalculation122 void print() const
123 {
124 for (auto const& it : mvPos)
125 {
126 OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc) +
127 " [" + OUString::number( it.mnRecursion) + "," + OUString::number( it.mnGroup) + "]");
128 fprintf( stderr, "%s -> ", aStr.toUtf8().getStr());
129 }
130 fprintf( stderr, "%s", "END\n");
131 }
132
133 /** Print chain results. */
printResultsDebugCalculation134 void printResults() const
135 {
136 for (auto const& it : mvResults)
137 {
138 OUString aStr( it.maPos.Format( ScRefFlags::VALID | ScRefFlags::TAB_3D, &it.mrDoc));
139 aStr += " (" + it.maResult + ")";
140 fprintf( stderr, "%s, ", aStr.toUtf8().getStr());
141 }
142 fprintf( stderr, "%s", "END\n");
143 }
144
storeResultDebugCalculation145 void storeResult( const svl::SharedString& rStr )
146 {
147 if (mbActive && !mvPos.empty())
148 mvPos.back().maResult = "\"" + rStr.getString() + "\"";
149 }
150
storeResultDebugCalculation151 void storeResult( const double& fVal )
152 {
153 if (mbActive && !mvPos.empty())
154 mvPos.back().maResult = rtl::math::doubleToUString( fVal, rtl_math_StringFormat_G, 2, '.', true);
155 }
156
storeResultErrorDebugCalculation157 void storeResultError( FormulaError nErr )
158 {
159 if (mbActive && !mvPos.empty())
160 mvPos.back().maResult = "Err:" + OUString::number( int( nErr ));
161 }
162
enterGroupDebugCalculation163 void enterGroup()
164 {
165 ++mnGroup;
166 }
167
leaveGroupDebugCalculation168 void leaveGroup()
169 {
170 --mnGroup;
171 }
172
173 } aDC;
174
175 struct DebugCalculationStacker
176 {
DebugCalculationStackerDebugCalculationStacker177 DebugCalculationStacker( const ScAddress& rPos, ScDocument& rDoc )
178 {
179 if (!aDC.mbActive && rPos == aDC.maTrigger)
180 aDC.mbActive = aDC.mbSwitchOff = true;
181 if (aDC.mbActive)
182 {
183 aDC.mvPos.push_back( DebugCalculationEntry( rPos, rDoc, aDC.mnGroup));
184 aDC.mbPrint = true;
185 }
186 }
187
~DebugCalculationStackerDebugCalculationStacker188 ~DebugCalculationStacker()
189 {
190 if (aDC.mbActive)
191 {
192 if (!aDC.mvPos.empty())
193 {
194 if (aDC.mbPrint)
195 {
196 aDC.print();
197 aDC.mbPrint = false;
198 }
199 if (aDC.mbPrintResults)
200 {
201 // Store results until final result is available, reversing order.
202 aDC.mvResults.push_back( aDC.mvPos.back());
203 }
204 aDC.mvPos.pop_back();
205 if (aDC.mbPrintResults && aDC.mvPos.empty())
206 {
207 aDC.printResults();
208 std::vector< DebugCalculationEntry >().swap( aDC.mvResults);
209 }
210 if (aDC.mbSwitchOff && aDC.mvPos.empty())
211 aDC.mbActive = false;
212 }
213 }
214 }
215 };
216 #endif
217
218 namespace {
219
220 // More or less arbitrary, of course all recursions must fit into available
221 // stack space (which is what on all systems we don't know yet?). Choosing a
222 // lower value may be better than trying a much higher value that also isn't
223 // sufficient but temporarily leads to high memory consumption. On the other
224 // hand, if the value fits all recursions, execution is quicker as no resumes
225 // are necessary. Could be made a configurable option.
226 // Allow for a year's calendar (366).
227 const sal_uInt16 MAXRECURSION = 400;
228
229 typedef SCCOLROW(*DimensionSelector)(const ScDocument&, const ScAddress&, const ScSingleRefData&);
230
lcl_GetCol(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)231 SCCOLROW lcl_GetCol(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
232 {
233 return rData.toAbs(rDoc, rPos).Col();
234 }
235
lcl_GetRow(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)236 SCCOLROW lcl_GetRow(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
237 {
238 return rData.toAbs(rDoc, rPos).Row();
239 }
240
lcl_GetTab(const ScDocument & rDoc,const ScAddress & rPos,const ScSingleRefData & rData)241 SCCOLROW lcl_GetTab(const ScDocument& rDoc, const ScAddress& rPos, const ScSingleRefData& rData)
242 {
243 return rData.toAbs(rDoc, rPos).Tab();
244 }
245
246 /** Check if both references span the same range in selected dimension.
247 */
248 bool
lcl_checkRangeDimension(const ScDocument & rDoc,const ScAddress & rPos,const SingleDoubleRefProvider & rRef1,const SingleDoubleRefProvider & rRef2,const DimensionSelector aWhich)249 lcl_checkRangeDimension(
250 const ScDocument& rDoc,
251 const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
252 const DimensionSelector aWhich)
253 {
254 return aWhich(rDoc, rPos, rRef1.Ref1) == aWhich(rDoc, rPos, rRef2.Ref1) &&
255 aWhich(rDoc, rPos, rRef1.Ref2) == aWhich(rDoc, rPos, rRef2.Ref2);
256 }
257
258 bool
lcl_checkRangeDimensions(const ScDocument & rDoc,const ScAddress & rPos,const SingleDoubleRefProvider & rRef1,const SingleDoubleRefProvider & rRef2,bool & bCol,bool & bRow,bool & bTab)259 lcl_checkRangeDimensions(
260 const ScDocument& rDoc,
261 const ScAddress& rPos, const SingleDoubleRefProvider& rRef1, const SingleDoubleRefProvider& rRef2,
262 bool& bCol, bool& bRow, bool& bTab)
263 {
264 const bool bSameCols(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetCol));
265 const bool bSameRows(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetRow));
266 const bool bSameTabs(lcl_checkRangeDimension(rDoc, rPos, rRef1, rRef2, lcl_GetTab));
267
268 // Test if exactly two dimensions are equal
269 if (int(bSameCols) + int(bSameRows) + int(bSameTabs) == 2)
270 {
271 bCol = !bSameCols;
272 bRow = !bSameRows;
273 bTab = !bSameTabs;
274 return true;
275 }
276 return false;
277 }
278
279 /** Check if references in given reference list can possibly
280 form a range. To do that, two of their dimensions must be the same.
281 */
282 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)283 lcl_checkRangeDimensions(
284 const ScDocument& rDoc, const ScAddress& rPos,
285 const std::vector<formula::FormulaToken*>::const_iterator& rBegin,
286 const std::vector<formula::FormulaToken*>::const_iterator& rEnd,
287 bool& bCol, bool& bRow, bool& bTab)
288 {
289 std::vector<formula::FormulaToken*>::const_iterator aCur(rBegin);
290 ++aCur;
291 const SingleDoubleRefProvider aRef(**rBegin);
292 bool bOk(false);
293 {
294 const SingleDoubleRefProvider aRefCur(**aCur);
295 bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bCol, bRow, bTab);
296 }
297 while (bOk && aCur != rEnd)
298 {
299 const SingleDoubleRefProvider aRefCur(**aCur);
300 bool bColTmp(false);
301 bool bRowTmp(false);
302 bool bTabTmp(false);
303 bOk = lcl_checkRangeDimensions(rDoc, rPos, aRef, aRefCur, bColTmp, bRowTmp, bTabTmp);
304 bOk = bOk && (bCol == bColTmp && bRow == bRowTmp && bTab == bTabTmp);
305 ++aCur;
306 }
307
308 return bOk && aCur == rEnd;
309 }
310
311 class LessByReference
312 {
313 const ScDocument& mrDoc;
314 ScAddress maPos;
315 DimensionSelector maFunc;
316 public:
LessByReference(const ScDocument & rDoc,const ScAddress & rPos,const DimensionSelector & rFunc)317 LessByReference(const ScDocument& rDoc, const ScAddress& rPos, const DimensionSelector& rFunc) :
318 mrDoc(rDoc), maPos(rPos), maFunc(rFunc) {}
319
operator ()(const formula::FormulaToken * pRef1,const formula::FormulaToken * pRef2)320 bool operator() (const formula::FormulaToken* pRef1, const formula::FormulaToken* pRef2)
321 {
322 const SingleDoubleRefProvider aRef1(*pRef1);
323 const SingleDoubleRefProvider aRef2(*pRef2);
324 return maFunc(mrDoc, maPos, aRef1.Ref1) < maFunc(mrDoc, maPos, aRef2.Ref1);
325 }
326 };
327
328 /**
329 * Returns true if range denoted by token p2 starts immediately after range
330 * denoted by token p1. Dimension, in which the comparison takes place, is
331 * given by maFunc.
332 */
333 class AdjacentByReference
334 {
335 const ScDocument& mrDoc;
336 ScAddress maPos;
337 DimensionSelector maFunc;
338 public:
AdjacentByReference(const ScDocument & rDoc,const ScAddress & rPos,DimensionSelector aFunc)339 AdjacentByReference(const ScDocument& rDoc, const ScAddress& rPos, DimensionSelector aFunc) :
340 mrDoc(rDoc), maPos(rPos), maFunc(aFunc) {}
341
operator ()(const formula::FormulaToken * p1,const formula::FormulaToken * p2)342 bool operator() (const formula::FormulaToken* p1, const formula::FormulaToken* p2)
343 {
344 const SingleDoubleRefProvider aRef1(*p1);
345 const SingleDoubleRefProvider aRef2(*p2);
346 return maFunc(mrDoc, maPos, aRef2.Ref1) - maFunc(mrDoc, maPos, aRef1.Ref2) == 1;
347 }
348 };
349
350 bool
lcl_checkIfAdjacent(const ScDocument & rDoc,const ScAddress & rPos,const std::vector<formula::FormulaToken * > & rReferences,const DimensionSelector aWhich)351 lcl_checkIfAdjacent(
352 const ScDocument& rDoc,
353 const ScAddress& rPos, const std::vector<formula::FormulaToken*>& rReferences, const DimensionSelector aWhich)
354 {
355 auto aBegin(rReferences.cbegin());
356 auto aEnd(rReferences.cend());
357 auto aBegin1(aBegin);
358 ++aBegin1;
359 --aEnd;
360 return std::equal(aBegin, aEnd, aBegin1, AdjacentByReference(rDoc, rPos, aWhich));
361 }
362
363 void
lcl_fillRangeFromRefList(const ScDocument & rDoc,const ScAddress & aPos,const std::vector<formula::FormulaToken * > & rReferences,ScRange & rRange)364 lcl_fillRangeFromRefList(
365 const ScDocument& rDoc,
366 const ScAddress& aPos, const std::vector<formula::FormulaToken*>& rReferences, ScRange& rRange)
367 {
368 const ScSingleRefData aStart(
369 SingleDoubleRefProvider(*rReferences.front()).Ref1);
370 rRange.aStart = aStart.toAbs(rDoc, aPos);
371 const ScSingleRefData aEnd(
372 SingleDoubleRefProvider(*rReferences.back()).Ref2);
373 rRange.aEnd = aEnd.toAbs(rDoc, aPos);
374 }
375
376 bool
lcl_refListFormsOneRange(const ScDocument & rDoc,const ScAddress & rPos,std::vector<formula::FormulaToken * > & rReferences,ScRange & rRange)377 lcl_refListFormsOneRange(
378 const ScDocument& rDoc,
379 const ScAddress& rPos, std::vector<formula::FormulaToken*>& rReferences,
380 ScRange& rRange)
381 {
382 if (rReferences.size() == 1)
383 {
384 lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
385 return true;
386 }
387
388 bool bCell(false);
389 bool bRow(false);
390 bool bTab(false);
391 if (lcl_checkRangeDimensions(rDoc, rPos, rReferences.begin(), rReferences.end(), bCell, bRow, bTab))
392 {
393 DimensionSelector aWhich;
394 if (bCell)
395 {
396 aWhich = lcl_GetCol;
397 }
398 else if (bRow)
399 {
400 aWhich = lcl_GetRow;
401 }
402 else if (bTab)
403 {
404 aWhich = lcl_GetTab;
405 }
406 else
407 {
408 OSL_FAIL( "lcl_checkRangeDimensions shouldn't allow that!");
409 aWhich = lcl_GetRow; // initialize to avoid warning
410 }
411
412 // Sort the references by start of range
413 std::sort(rReferences.begin(), rReferences.end(), LessByReference(rDoc, rPos, aWhich));
414 if (lcl_checkIfAdjacent(rDoc, rPos, rReferences, aWhich))
415 {
416 lcl_fillRangeFromRefList(rDoc, rPos, rReferences, rRange);
417 return true;
418 }
419 }
420 return false;
421 }
422
lcl_isReference(const FormulaToken & rToken)423 bool lcl_isReference(const FormulaToken& rToken)
424 {
425 return
426 rToken.GetType() == svSingleRef ||
427 rToken.GetType() == svDoubleRef;
428 }
429
adjustRangeName(formula::FormulaToken * pToken,ScDocument & rNewDoc,const ScDocument & rOldDoc,const ScAddress & rNewPos,const ScAddress & rOldPos,bool bGlobalNamesToLocal)430 void adjustRangeName(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc,
431 const ScAddress& rNewPos, const ScAddress& rOldPos, bool bGlobalNamesToLocal)
432 {
433 ScRangeData* pRangeData = nullptr;
434 SCTAB nSheet = pToken->GetSheet();
435 sal_uInt16 nIndex = pToken->GetIndex();
436 if (!rOldDoc.CopyAdjustRangeName( nSheet, nIndex, pRangeData, rNewDoc, rNewPos, rOldPos, bGlobalNamesToLocal, true))
437 return; // nothing to do
438
439 if (!pRangeData)
440 {
441 // If this happened we have a real problem.
442 pToken->SetIndex(0);
443 assert(!"inserting the range name should not fail");
444 return;
445 }
446
447 pToken->SetIndex(nIndex);
448 pToken->SetSheet(nSheet);
449 }
450
adjustDBRange(formula::FormulaToken * pToken,ScDocument & rNewDoc,const ScDocument & rOldDoc)451 void adjustDBRange(formula::FormulaToken* pToken, ScDocument& rNewDoc, const ScDocument& rOldDoc)
452 {
453 ScDBCollection* pOldDBCollection = rOldDoc.GetDBCollection();
454 if (!pOldDBCollection)
455 return;//strange error case, don't do anything
456 ScDBCollection::NamedDBs& aOldNamedDBs = pOldDBCollection->getNamedDBs();
457 ScDBData* pDBData = aOldNamedDBs.findByIndex(pToken->GetIndex());
458 if (!pDBData)
459 return; //invalid index
460 OUString aDBName = pDBData->GetUpperName();
461
462 //search in new document
463 ScDBCollection* pNewDBCollection = rNewDoc.GetDBCollection();
464 if (!pNewDBCollection)
465 {
466 rNewDoc.SetDBCollection(std::unique_ptr<ScDBCollection>(new ScDBCollection(rNewDoc)));
467 pNewDBCollection = rNewDoc.GetDBCollection();
468 }
469 ScDBCollection::NamedDBs& aNewNamedDBs = pNewDBCollection->getNamedDBs();
470 ScDBData* pNewDBData = aNewNamedDBs.findByUpperName(aDBName);
471 if (!pNewDBData)
472 {
473 pNewDBData = new ScDBData(*pDBData);
474 bool ins = aNewNamedDBs.insert(std::unique_ptr<ScDBData>(pNewDBData));
475 assert(ins); (void)ins;
476 }
477 pToken->SetIndex(pNewDBData->GetIndex());
478 }
479
480 }
481
operator <(const AreaListenerKey & r) const482 bool AreaListenerKey::operator < ( const AreaListenerKey& r ) const
483 {
484 if (maRange.aStart.Tab() != r.maRange.aStart.Tab())
485 return maRange.aStart.Tab() < r.maRange.aStart.Tab();
486 if (maRange.aStart.Col() != r.maRange.aStart.Col())
487 return maRange.aStart.Col() < r.maRange.aStart.Col();
488 if (maRange.aStart.Row() != r.maRange.aStart.Row())
489 return maRange.aStart.Row() < r.maRange.aStart.Row();
490 if (maRange.aEnd.Tab() != r.maRange.aEnd.Tab())
491 return maRange.aEnd.Tab() < r.maRange.aEnd.Tab();
492 if (maRange.aEnd.Col() != r.maRange.aEnd.Col())
493 return maRange.aEnd.Col() < r.maRange.aEnd.Col();
494 if (maRange.aEnd.Row() != r.maRange.aEnd.Row())
495 return maRange.aEnd.Row() < r.maRange.aEnd.Row();
496 if (mbStartFixed != r.mbStartFixed)
497 return r.mbStartFixed;
498 if (mbEndFixed != r.mbEndFixed)
499 return r.mbEndFixed;
500
501 return false;
502 }
503
ScFormulaCellGroup()504 ScFormulaCellGroup::ScFormulaCellGroup() :
505 mnRefCount(0),
506 mpTopCell(nullptr),
507 mnLength(0),
508 mnWeight(0),
509 mnFormatType(SvNumFormatType::NUMBER),
510 mbInvariant(false),
511 mbSubTotal(false),
512 mbPartOfCycle(false),
513 meCalcState(sc::GroupCalcEnabled)
514 {
515 }
516
~ScFormulaCellGroup()517 ScFormulaCellGroup::~ScFormulaCellGroup()
518 {
519 }
520
setCode(const ScTokenArray & rCode)521 void ScFormulaCellGroup::setCode( const ScTokenArray& rCode )
522 {
523 mpCode = rCode.CloneValue();
524 mbInvariant = mpCode->IsInvariant();
525 mpCode->GenHash();
526 }
527
compileCode(ScDocument & rDoc,const ScAddress & rPos,FormulaGrammar::Grammar eGram)528 void ScFormulaCellGroup::compileCode(
529 ScDocument& rDoc, const ScAddress& rPos, FormulaGrammar::Grammar eGram )
530 {
531 if (!mpCode)
532 return;
533
534 if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen())
535 {
536 bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE;
537 ScCompiler aComp(rDoc, rPos, *mpCode, eGram, true, bMatrixFormula);
538 mbSubTotal = aComp.CompileTokenArray();
539 mnFormatType = aComp.GetNumFormatType();
540 }
541 else
542 {
543 mbSubTotal = mpCode->HasOpCodeRPN( ocSubTotal ) || mpCode->HasOpCodeRPN( ocAggregate );
544 }
545 }
546
getAreaListener(ScFormulaCell ** ppTopCell,const ScRange & rRange,bool bStartFixed,bool bEndFixed)547 sc::FormulaGroupAreaListener* ScFormulaCellGroup::getAreaListener(
548 ScFormulaCell** ppTopCell, const ScRange& rRange, bool bStartFixed, bool bEndFixed )
549 {
550 AreaListenerKey aKey(rRange, bStartFixed, bEndFixed);
551
552 AreaListenersType::iterator it = m_AreaListeners.lower_bound(aKey);
553 if (it == m_AreaListeners.end() || m_AreaListeners.key_comp()(aKey, it->first))
554 {
555 // Insert a new one.
556 it = m_AreaListeners.emplace_hint(
557 it, std::piecewise_construct,
558 std::forward_as_tuple(aKey),
559 std::forward_as_tuple(
560 rRange, (*ppTopCell)->GetDocument(), (*ppTopCell)->aPos, mnLength, bStartFixed, bEndFixed));
561 }
562
563 return &it->second;
564 }
565
endAllGroupListening(ScDocument & rDoc)566 void ScFormulaCellGroup::endAllGroupListening( ScDocument& rDoc )
567 {
568 for (auto& rEntry : m_AreaListeners)
569 {
570 sc::FormulaGroupAreaListener& rListener = rEntry.second;
571 ScRange aListenRange = rListener.getListeningRange();
572 // This "always listen" special range is never grouped.
573 bool bGroupListening = (aListenRange != BCA_LISTEN_ALWAYS);
574 rDoc.EndListeningArea(aListenRange, bGroupListening, &rListener);
575 }
576
577 m_AreaListeners.clear();
578 }
579
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos)580 ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos ) :
581 bDirty(false),
582 bTableOpDirty(false),
583 bChanged(false),
584 bRunning(false),
585 bCompile(false),
586 bSubTotal(false),
587 bIsIterCell(false),
588 bInChangeTrack(false),
589 bNeedListening(false),
590 mbNeedsNumberFormat(false),
591 mbAllowNumberFormatChange(false),
592 mbPostponedDirty(false),
593 mbIsExtRef(false),
594 mbSeenInPath(false),
595 mbFreeFlying(false),
596 cMatrixFlag(ScMatrixMode::NONE),
597 nSeenInIteration(0),
598 nFormatType(SvNumFormatType::NUMBER),
599 eTempGrammar(formula::FormulaGrammar::GRAM_DEFAULT),
600 pCode(new ScTokenArray(rDoc)),
601 rDocument(rDoc),
602 pPrevious(nullptr),
603 pNext(nullptr),
604 pPreviousTrack(nullptr),
605 pNextTrack(nullptr),
606 aPos(rPos)
607 {
608 }
609
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const OUString & rFormula,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)610 ScFormulaCell::ScFormulaCell( ScDocument& rDoc, const ScAddress& rPos,
611 const OUString& rFormula,
612 const FormulaGrammar::Grammar eGrammar,
613 ScMatrixMode cMatInd ) :
614 bDirty( true ), // -> Because of the use of the Auto Pilot Function was: cMatInd != 0
615 bTableOpDirty( false ),
616 bChanged( false ),
617 bRunning( false ),
618 bCompile( false ),
619 bSubTotal( false ),
620 bIsIterCell( false ),
621 bInChangeTrack( false ),
622 bNeedListening( false ),
623 mbNeedsNumberFormat( false ),
624 mbAllowNumberFormatChange(false),
625 mbPostponedDirty(false),
626 mbIsExtRef(false),
627 mbSeenInPath(false),
628 mbFreeFlying(false),
629 cMatrixFlag ( cMatInd ),
630 nSeenInIteration(0),
631 nFormatType ( SvNumFormatType::NUMBER ),
632 eTempGrammar( eGrammar),
633 pCode( nullptr ),
634 rDocument( rDoc ),
635 pPrevious(nullptr),
636 pNext(nullptr),
637 pPreviousTrack(nullptr),
638 pNextTrack(nullptr),
639 aPos(rPos)
640 {
641 Compile( rFormula, true, eGrammar ); // bNoListening, Insert does that
642 if (!pCode)
643 // We need to have a non-NULL token array instance at all times.
644 pCode = new ScTokenArray(rDoc);
645 }
646
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,std::unique_ptr<ScTokenArray> pArray,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)647 ScFormulaCell::ScFormulaCell(
648 ScDocument& rDoc, const ScAddress& rPos, std::unique_ptr<ScTokenArray> pArray,
649 const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
650 bDirty( true ),
651 bTableOpDirty( false ),
652 bChanged( false ),
653 bRunning( false ),
654 bCompile( false ),
655 bSubTotal( false ),
656 bIsIterCell( false ),
657 bInChangeTrack( false ),
658 bNeedListening( false ),
659 mbNeedsNumberFormat( false ),
660 mbAllowNumberFormatChange(false),
661 mbPostponedDirty(false),
662 mbIsExtRef(false),
663 mbSeenInPath(false),
664 mbFreeFlying(false),
665 cMatrixFlag ( cMatInd ),
666 nSeenInIteration(0),
667 nFormatType ( SvNumFormatType::NUMBER ),
668 eTempGrammar( eGrammar),
669 pCode(pArray.release()),
670 rDocument( rDoc ),
671 pPrevious(nullptr),
672 pNext(nullptr),
673 pPreviousTrack(nullptr),
674 pNextTrack(nullptr),
675 aPos(rPos)
676 {
677 assert(pCode); // Never pass a NULL pointer here.
678
679 pCode->Finalize(); // Reduce memory usage if needed.
680
681 // Generate RPN token array.
682 if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen())
683 {
684 ScCompiler aComp(rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE);
685 bSubTotal = aComp.CompileTokenArray();
686 nFormatType = aComp.GetNumFormatType();
687 }
688 else
689 {
690 if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
691 bSubTotal = true;
692 }
693
694 if (bSubTotal)
695 rDocument.AddSubTotalCell(this);
696
697 pCode->GenHash();
698 }
699
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const ScTokenArray & rArray,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cMatInd)700 ScFormulaCell::ScFormulaCell(
701 ScDocument& rDoc, const ScAddress& rPos, const ScTokenArray& rArray,
702 const FormulaGrammar::Grammar eGrammar, ScMatrixMode cMatInd ) :
703 bDirty( true ),
704 bTableOpDirty( false ),
705 bChanged( false ),
706 bRunning( false ),
707 bCompile( false ),
708 bSubTotal( false ),
709 bIsIterCell( false ),
710 bInChangeTrack( false ),
711 bNeedListening( false ),
712 mbNeedsNumberFormat( false ),
713 mbAllowNumberFormatChange(false),
714 mbPostponedDirty(false),
715 mbIsExtRef(false),
716 mbSeenInPath(false),
717 mbFreeFlying(false),
718 cMatrixFlag ( cMatInd ),
719 nSeenInIteration(0),
720 nFormatType ( SvNumFormatType::NUMBER ),
721 eTempGrammar( eGrammar),
722 pCode(new ScTokenArray(rArray)), // also implicitly does Finalize() on the array
723 rDocument( rDoc ),
724 pPrevious(nullptr),
725 pNext(nullptr),
726 pPreviousTrack(nullptr),
727 pNextTrack(nullptr),
728 aPos(rPos)
729 {
730 // RPN array generation
731 if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() )
732 {
733 ScCompiler aComp( rDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE );
734 bSubTotal = aComp.CompileTokenArray();
735 nFormatType = aComp.GetNumFormatType();
736 }
737 else
738 {
739 if ( pCode->HasOpCodeRPN( ocSubTotal ) || pCode->HasOpCodeRPN( ocAggregate ) )
740 bSubTotal = true;
741 }
742
743 if (bSubTotal)
744 rDocument.AddSubTotalCell(this);
745
746 pCode->GenHash();
747 }
748
ScFormulaCell(ScDocument & rDoc,const ScAddress & rPos,const ScFormulaCellGroupRef & xGroup,const FormulaGrammar::Grammar eGrammar,ScMatrixMode cInd)749 ScFormulaCell::ScFormulaCell(
750 ScDocument& rDoc, const ScAddress& rPos, const ScFormulaCellGroupRef& xGroup,
751 const FormulaGrammar::Grammar eGrammar, ScMatrixMode cInd ) :
752 mxGroup(xGroup),
753 bDirty(true),
754 bTableOpDirty( false ),
755 bChanged( false ),
756 bRunning( false ),
757 bCompile( false ),
758 bSubTotal(xGroup->mbSubTotal),
759 bIsIterCell( false ),
760 bInChangeTrack( false ),
761 bNeedListening( false ),
762 mbNeedsNumberFormat( false ),
763 mbAllowNumberFormatChange(false),
764 mbPostponedDirty(false),
765 mbIsExtRef(false),
766 mbSeenInPath(false),
767 mbFreeFlying(false),
768 cMatrixFlag ( cInd ),
769 nSeenInIteration(0),
770 nFormatType(xGroup->mnFormatType),
771 eTempGrammar( eGrammar),
772 pCode(xGroup->mpCode ? &*xGroup->mpCode : new ScTokenArray(rDoc)),
773 rDocument( rDoc ),
774 pPrevious(nullptr),
775 pNext(nullptr),
776 pPreviousTrack(nullptr),
777 pNextTrack(nullptr),
778 aPos(rPos)
779 {
780 if (bSubTotal)
781 rDocument.AddSubTotalCell(this);
782 }
783
ScFormulaCell(const ScFormulaCell & rCell,ScDocument & rDoc,const ScAddress & rPos,ScCloneFlags nCloneFlags)784 ScFormulaCell::ScFormulaCell(const ScFormulaCell& rCell, ScDocument& rDoc, const ScAddress& rPos, ScCloneFlags nCloneFlags) :
785 bDirty( rCell.bDirty ),
786 bTableOpDirty( false ),
787 bChanged( rCell.bChanged ),
788 bRunning( false ),
789 bCompile( rCell.bCompile ),
790 bSubTotal( rCell.bSubTotal ),
791 bIsIterCell( false ),
792 bInChangeTrack( false ),
793 bNeedListening( false ),
794 mbNeedsNumberFormat( rCell.mbNeedsNumberFormat ),
795 mbAllowNumberFormatChange(false),
796 mbPostponedDirty(false),
797 mbIsExtRef(false),
798 mbSeenInPath(false),
799 mbFreeFlying(false),
800 cMatrixFlag ( rCell.cMatrixFlag ),
801 nSeenInIteration(0),
802 nFormatType( rCell.nFormatType ),
803 aResult( rCell.aResult ),
804 eTempGrammar( rCell.eTempGrammar),
805 rDocument( rDoc ),
806 pPrevious(nullptr),
807 pNext(nullptr),
808 pPreviousTrack(nullptr),
809 pNextTrack(nullptr),
810 aPos(rPos)
811 {
812 pCode = rCell.pCode->Clone().release();
813
814 // set back any errors and recompile
815 // not in the Clipboard - it must keep the received error flag
816 // Special Length=0: as bad cells are generated, then they are also retained
817 if ( pCode->GetCodeError() != FormulaError::NONE && !rDocument.IsClipboard() && pCode->GetLen() )
818 {
819 pCode->SetCodeError( FormulaError::NONE );
820 bCompile = true;
821 }
822 // Compile ColRowNames on URM_MOVE/URM_COPY _after_ UpdateReference !
823 bool bCompileLater = false;
824 bool bClipMode = rCell.rDocument.IsClipboard();
825
826 //update ScNameTokens
827 if (!rDocument.IsClipOrUndo() || rDoc.IsUndo())
828 {
829 if (!rDocument.IsClipboardSource() || aPos.Tab() != rCell.aPos.Tab())
830 {
831 bool bGlobalNamesToLocal = ((nCloneFlags & ScCloneFlags::NamesToLocal) != ScCloneFlags::Default);
832 formula::FormulaToken* pToken = nullptr;
833 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
834 while((pToken = aIter.GetNextName())!= nullptr)
835 {
836 OpCode eOpCode = pToken->GetOpCode();
837 if (eOpCode == ocName)
838 adjustRangeName(pToken, rDoc, rCell.rDocument, aPos, rCell.aPos, bGlobalNamesToLocal);
839 else if (eOpCode == ocDBArea || eOpCode == ocTableRef)
840 adjustDBRange(pToken, rDoc, rCell.rDocument);
841 }
842 }
843
844 bool bCopyBetweenDocs = rDocument.GetPool() != rCell.rDocument.GetPool();
845 if (bCopyBetweenDocs && !(nCloneFlags & ScCloneFlags::NoMakeAbsExternal))
846 {
847 pCode->ReadjustAbsolute3DReferences(rCell.rDocument, rDoc, rCell.aPos);
848 }
849
850 pCode->AdjustAbsoluteRefs( rCell.rDocument, rCell.aPos, aPos, bCopyBetweenDocs );
851 }
852
853 if (!rDocument.IsClipOrUndo())
854 {
855 if (&rDocument.GetSharedStringPool() != &rCell.rDocument.GetSharedStringPool())
856 pCode->ReinternStrings( rDocument.GetSharedStringPool());
857 pCode->AdjustReferenceOnCopy( aPos);
858 }
859
860 if( !bCompile )
861 { // Name references with references and ColRowNames
862 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
863 for (;;)
864 {
865 formula::FormulaToken* t = aIter.GetNextReferenceOrName();
866 if (!t || bCompile)
867 break;
868 if ( t->IsExternalRef() )
869 {
870 // External name, cell, and area references.
871 bCompile = true;
872 }
873 else if ( t->GetType() == svIndex )
874 {
875 const ScRangeData* pRangeData = rDoc.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
876 if( pRangeData )
877 {
878 if( pRangeData->HasReferences() )
879 bCompile = true;
880 }
881 else
882 bCompile = true; // invalid reference!
883 }
884 else if ( t->GetOpCode() == ocColRowName )
885 {
886 bCompile = true; // new lookup needed
887 bCompileLater = bClipMode;
888 }
889 }
890 }
891 if( bCompile )
892 {
893 if ( !bCompileLater && bClipMode )
894 {
895 // Merging ranges needs the actual positions after UpdateReference.
896 // ColRowNames and TableRefs need new lookup after positions are
897 // adjusted.
898 bCompileLater = pCode->HasOpCode( ocRange) || pCode->HasOpCode( ocColRowName) ||
899 pCode->HasOpCode( ocTableRef);
900 }
901 if ( !bCompileLater )
902 {
903 // bNoListening, not at all if in Clipboard/Undo,
904 // and not from Clipboard either, instead after Insert(Clone) and UpdateReference.
905 CompileTokenArray( true );
906 }
907 }
908
909 if( nCloneFlags & ScCloneFlags::StartListening )
910 StartListeningTo( rDoc );
911
912 if (bSubTotal)
913 rDocument.AddSubTotalCell(this);
914 }
915
~ScFormulaCell()916 ScFormulaCell::~ScFormulaCell()
917 {
918 rDocument.RemoveFromFormulaTrack( this );
919 rDocument.RemoveFromFormulaTree( this );
920 rDocument.RemoveSubTotalCell(this);
921 if (pCode->HasOpCode(ocMacro))
922 rDocument.GetMacroManager()->RemoveDependentCell(this);
923
924 if (rDocument.HasExternalRefManager())
925 rDocument.GetExternalRefManager()->removeRefCell(this);
926
927 if (!mxGroup || !mxGroup->mpCode)
928 // Formula token is not shared.
929 delete pCode;
930
931 if (mxGroup && mxGroup->mpTopCell == this)
932 mxGroup->mpTopCell = nullptr;
933 }
934
Clone() const935 ScFormulaCell* ScFormulaCell::Clone() const
936 {
937 return new ScFormulaCell(*this, rDocument, aPos);
938 }
939
Clone(const ScAddress & rPos) const940 ScFormulaCell* ScFormulaCell::Clone( const ScAddress& rPos ) const
941 {
942 return new ScFormulaCell(*this, rDocument, rPos, ScCloneFlags::Default);
943 }
944
GetHash() const945 size_t ScFormulaCell::GetHash() const
946 {
947 return pCode->GetHash();
948 }
949
GetFormula(const FormulaGrammar::Grammar eGrammar,ScInterpreterContext * pContext) const950 OUString ScFormulaCell::GetFormula( const FormulaGrammar::Grammar eGrammar, ScInterpreterContext* pContext ) const
951 {
952 if( pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen() )
953 {
954 return ScGlobal::GetErrorString(pCode->GetCodeError());
955 }
956 OUStringBuffer buffer;
957 if( cMatrixFlag == ScMatrixMode::Reference )
958 {
959 // Reference to another cell that contains a matrix formula.
960 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
961 formula::FormulaToken* p = aIter.GetNextReferenceRPN();
962 if( p )
963 {
964 /* FIXME: original GetFormula() code obtained
965 * pCell only if (!IsInChangeTrack()),
966 * GetEnglishFormula() omitted that test.
967 * Can we live without in all cases? */
968 ScFormulaCell* pCell = nullptr;
969 ScSingleRefData& rRef = *p->GetSingleRef();
970 ScAddress aAbs = rRef.toAbs(rDocument, aPos);
971 if (rDocument.ValidAddress(aAbs))
972 pCell = rDocument.GetFormulaCell(aAbs);
973
974 if (pCell)
975 {
976 return pCell->GetFormula( eGrammar, pContext );
977 }
978 else
979 {
980 ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
981 aComp.CreateStringFromTokenArray( buffer );
982 }
983 }
984 else
985 {
986 OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
987 }
988 }
989 else
990 {
991 ScCompiler aComp( rDocument, aPos, *pCode, eGrammar, false, false, pContext );
992 aComp.CreateStringFromTokenArray( buffer );
993 }
994
995 buffer.insert( 0, '=');
996 if( cMatrixFlag != ScMatrixMode::NONE )
997 {
998 buffer.insert( 0, '{');
999 buffer.append( '}');
1000 }
1001 return buffer.makeStringAndClear();
1002 }
1003
GetFormula(sc::CompileFormulaContext & rCxt,ScInterpreterContext * pContext) const1004 OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, ScInterpreterContext* pContext ) const
1005 {
1006 OUStringBuffer aBuf;
1007 if (pCode->GetCodeError() != FormulaError::NONE && !pCode->GetLen())
1008 {
1009 ScTokenArray aCode(rCxt.getDoc());
1010 aCode.AddToken( FormulaErrorToken( pCode->GetCodeError()));
1011 ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext);
1012 aComp.CreateStringFromTokenArray(aBuf);
1013 return aBuf.makeStringAndClear();
1014 }
1015 else if( cMatrixFlag == ScMatrixMode::Reference )
1016 {
1017 // Reference to another cell that contains a matrix formula.
1018 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
1019 formula::FormulaToken* p = aIter.GetNextReferenceRPN();
1020 if( p )
1021 {
1022 /* FIXME: original GetFormula() code obtained
1023 * pCell only if (!IsInChangeTrack()),
1024 * GetEnglishFormula() omitted that test.
1025 * Can we live without in all cases? */
1026 ScFormulaCell* pCell = nullptr;
1027 ScSingleRefData& rRef = *p->GetSingleRef();
1028 ScAddress aAbs = rRef.toAbs(rDocument, aPos);
1029 if (rDocument.ValidAddress(aAbs))
1030 pCell = rDocument.GetFormulaCell(aAbs);
1031
1032 if (pCell)
1033 {
1034 return pCell->GetFormula(rCxt);
1035 }
1036 else
1037 {
1038 ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
1039 aComp.CreateStringFromTokenArray(aBuf);
1040 }
1041 }
1042 else
1043 {
1044 OSL_FAIL("ScFormulaCell::GetFormula: not a matrix");
1045 }
1046 }
1047 else
1048 {
1049 ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext);
1050 aComp.CreateStringFromTokenArray(aBuf);
1051 }
1052
1053 aBuf.insert( 0, '=');
1054 if( cMatrixFlag != ScMatrixMode::NONE )
1055 {
1056 aBuf.insert( 0, '{');
1057 aBuf.append( '}');
1058 }
1059
1060 return aBuf.makeStringAndClear();
1061 }
1062
GetResultDimensions(SCSIZE & rCols,SCSIZE & rRows)1063 void ScFormulaCell::GetResultDimensions( SCSIZE& rCols, SCSIZE& rRows )
1064 {
1065 MaybeInterpret();
1066
1067 if (pCode->GetCodeError() == FormulaError::NONE && aResult.GetType() == svMatrixCell)
1068 {
1069 const ScMatrix* pMat = aResult.GetToken()->GetMatrix();
1070 if (pMat)
1071 {
1072 pMat->GetDimensions( rCols, rRows );
1073 if (pCode->IsHyperLink())
1074 {
1075 // Row 2 element is the URL that is not to be displayed and the
1076 // result dimension not to be extended.
1077 assert(rRows == 2);
1078 rRows = 1;
1079 }
1080 return;
1081 }
1082 }
1083 rCols = 0;
1084 rRows = 0;
1085 }
1086
ResetDirty()1087 void ScFormulaCell::ResetDirty() { bDirty = bTableOpDirty = mbPostponedDirty = false; }
SetNeedsListening(bool bVar)1088 void ScFormulaCell::SetNeedsListening( bool bVar ) { bNeedListening = bVar; }
1089
SetNeedsDirty(bool bVar)1090 void ScFormulaCell::SetNeedsDirty( bool bVar )
1091 {
1092 mbPostponedDirty = bVar;
1093 }
1094
SetNeedNumberFormat(bool bVal)1095 void ScFormulaCell::SetNeedNumberFormat( bool bVal )
1096 {
1097 mbNeedsNumberFormat = mbAllowNumberFormatChange = bVal;
1098 }
1099
Compile(const OUString & rFormula,bool bNoListening,const FormulaGrammar::Grammar eGrammar)1100 void ScFormulaCell::Compile( const OUString& rFormula, bool bNoListening,
1101 const FormulaGrammar::Grammar eGrammar )
1102 {
1103 if ( rDocument.IsClipOrUndo() )
1104 return;
1105 bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1106 if ( bWasInFormulaTree )
1107 rDocument.RemoveFromFormulaTree( this );
1108 // pCode may not deleted for queries, but must be empty
1109 if ( pCode )
1110 pCode->Clear();
1111 ScTokenArray* pCodeOld = pCode;
1112 ScCompiler aComp( rDocument, aPos, eGrammar);
1113 pCode = aComp.CompileString( rFormula ).release();
1114 assert(!mxGroup);
1115 delete pCodeOld;
1116 if( pCode->GetCodeError() == FormulaError::NONE )
1117 {
1118 if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
1119 { // not recursive CompileTokenArray/Compile/CompileTokenArray
1120 if ( rFormula[0] == '=' )
1121 pCode->AddBad( rFormula.copy(1) );
1122 else
1123 pCode->AddBad( rFormula );
1124 }
1125 bCompile = true;
1126 CompileTokenArray( bNoListening );
1127 }
1128 else
1129 bChanged = true;
1130
1131 if ( bWasInFormulaTree )
1132 rDocument.PutInFormulaTree( this );
1133 }
1134
Compile(sc::CompileFormulaContext & rCxt,const OUString & rFormula,bool bNoListening)1135 void ScFormulaCell::Compile(
1136 sc::CompileFormulaContext& rCxt, const OUString& rFormula, bool bNoListening )
1137 {
1138 if ( rDocument.IsClipOrUndo() )
1139 return;
1140 bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1141 if ( bWasInFormulaTree )
1142 rDocument.RemoveFromFormulaTree( this );
1143 // pCode may not deleted for queries, but must be empty
1144 if ( pCode )
1145 pCode->Clear();
1146 ScTokenArray* pCodeOld = pCode;
1147 ScCompiler aComp(rCxt, aPos);
1148 pCode = aComp.CompileString( rFormula ).release();
1149 assert(!mxGroup);
1150 delete pCodeOld;
1151 if( pCode->GetCodeError() == FormulaError::NONE )
1152 {
1153 if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() && rFormula == aResult.GetHybridFormula() )
1154 { // not recursive CompileTokenArray/Compile/CompileTokenArray
1155 if ( rFormula[0] == '=' )
1156 pCode->AddBad( rFormula.copy(1) );
1157 else
1158 pCode->AddBad( rFormula );
1159 }
1160 bCompile = true;
1161 CompileTokenArray(rCxt, bNoListening);
1162 }
1163 else
1164 bChanged = true;
1165
1166 if ( bWasInFormulaTree )
1167 rDocument.PutInFormulaTree( this );
1168 }
1169
CompileTokenArray(bool bNoListening)1170 void ScFormulaCell::CompileTokenArray( bool bNoListening )
1171 {
1172 // Not already compiled?
1173 if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1174 {
1175 Compile( aResult.GetHybridFormula(), bNoListening, eTempGrammar);
1176 }
1177 else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE )
1178 {
1179 // RPN length may get changed
1180 bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1181 if ( bWasInFormulaTree )
1182 rDocument.RemoveFromFormulaTree( this );
1183
1184 // Loading from within filter? No listening yet!
1185 if( rDocument.IsInsertingFromOtherDoc() )
1186 bNoListening = true;
1187
1188 if( !bNoListening && pCode->GetCodeLen() )
1189 EndListeningTo( rDocument );
1190 ScCompiler aComp(rDocument, aPos, *pCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
1191 bSubTotal = aComp.CompileTokenArray();
1192 if( pCode->GetCodeError() == FormulaError::NONE )
1193 {
1194 nFormatType = aComp.GetNumFormatType();
1195 bChanged = true;
1196 aResult.SetToken( nullptr);
1197 bCompile = false;
1198 if ( !bNoListening )
1199 StartListeningTo( rDocument );
1200 }
1201 if ( bWasInFormulaTree )
1202 rDocument.PutInFormulaTree( this );
1203
1204 if (bSubTotal)
1205 rDocument.AddSubTotalCell(this);
1206 }
1207 }
1208
CompileTokenArray(sc::CompileFormulaContext & rCxt,bool bNoListening)1209 void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNoListening )
1210 {
1211 // Not already compiled?
1212 if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1213 {
1214 rCxt.setGrammar(eTempGrammar);
1215 Compile(rCxt, aResult.GetHybridFormula(), bNoListening);
1216 }
1217 else if( bCompile && !rDocument.IsClipOrUndo() && pCode->GetCodeError() == FormulaError::NONE)
1218 {
1219 // RPN length may get changed
1220 bool bWasInFormulaTree = rDocument.IsInFormulaTree( this );
1221 if ( bWasInFormulaTree )
1222 rDocument.RemoveFromFormulaTree( this );
1223
1224 // Loading from within filter? No listening yet!
1225 if( rDocument.IsInsertingFromOtherDoc() )
1226 bNoListening = true;
1227
1228 if( !bNoListening && pCode->GetCodeLen() )
1229 EndListeningTo( rDocument );
1230 ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1231 bSubTotal = aComp.CompileTokenArray();
1232 if( pCode->GetCodeError() == FormulaError::NONE )
1233 {
1234 nFormatType = aComp.GetNumFormatType();
1235 bChanged = true;
1236 aResult.SetToken( nullptr);
1237 bCompile = false;
1238 if ( !bNoListening )
1239 StartListeningTo( rDocument );
1240 }
1241 if ( bWasInFormulaTree )
1242 rDocument.PutInFormulaTree( this );
1243
1244 if (bSubTotal)
1245 rDocument.AddSubTotalCell(this);
1246 }
1247 }
1248
CompileXML(sc::CompileFormulaContext & rCxt,ScProgress & rProgress)1249 void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rProgress )
1250 {
1251 if ( cMatrixFlag == ScMatrixMode::Reference )
1252 { // is already token code via ScDocFunc::EnterMatrix, ScDocument::InsertMatrixFormula
1253 // just establish listeners
1254 StartListeningTo( rDocument );
1255 return ;
1256 }
1257
1258 // Error constant formula cell stays as is.
1259 if (!pCode->GetLen() && pCode->GetCodeError() != FormulaError::NONE)
1260 return;
1261
1262 // Compilation changes RPN count, remove and reinsert to FormulaTree if it
1263 // was in to update its count.
1264 bool bWasInFormulaTree = rDocument.IsInFormulaTree( this);
1265 if (bWasInFormulaTree)
1266 rDocument.RemoveFromFormulaTree( this);
1267 rCxt.setGrammar(eTempGrammar);
1268 ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1269 OUString aFormula, aFormulaNmsp;
1270 aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp );
1271 rDocument.DecXMLImportedFormulaCount( aFormula.getLength() );
1272 rProgress.SetStateCountDownOnPercent( rDocument.GetXMLImportedFormulaCount() );
1273 // pCode may not deleted for queries, but must be empty
1274 pCode->Clear();
1275
1276 bool bDoCompile = true;
1277
1278 if ( !mxGroup && aFormulaNmsp.isEmpty() ) // optimization
1279 {
1280 ScAddress aPreviousCell( aPos );
1281 aPreviousCell.IncRow( -1 );
1282 ScFormulaCell *pPreviousCell = rDocument.GetFormulaCell( aPreviousCell );
1283 if (pPreviousCell && pPreviousCell->GetCode()->IsShareable())
1284 {
1285 // Build formula string using the tokens from the previous cell,
1286 // but use the current cell position.
1287 ScCompiler aBackComp( rCxt, aPos, *(pPreviousCell->pCode) );
1288 OUStringBuffer aShouldBeBuf;
1289 aBackComp.CreateStringFromTokenArray( aShouldBeBuf );
1290
1291 // The initial '=' is optional in ODFF.
1292 const sal_Int32 nLeadingEqual = (aFormula.getLength() > 0 && aFormula[0] == '=') ? 1 : 0;
1293 if (aFormula.getLength() == aShouldBeBuf.getLength() + nLeadingEqual &&
1294 aFormula.match( aShouldBeBuf, nLeadingEqual))
1295 {
1296 // Put them in the same formula group.
1297 ScFormulaCellGroupRef xGroup = pPreviousCell->GetCellGroup();
1298 if (!xGroup) // Last cell is not grouped yet. Start a new group.
1299 xGroup = pPreviousCell->CreateCellGroup(1, false);
1300 ++xGroup->mnLength;
1301 SetCellGroup( xGroup );
1302
1303 // Do setup here based on previous cell.
1304
1305 nFormatType = pPreviousCell->nFormatType;
1306 bSubTotal = pPreviousCell->bSubTotal;
1307 bChanged = true;
1308 bCompile = false;
1309
1310 if (bSubTotal)
1311 rDocument.AddSubTotalCell(this);
1312
1313 bDoCompile = false;
1314 pCode = pPreviousCell->pCode;
1315 if (pPreviousCell->mbIsExtRef)
1316 rDocument.GetExternalRefManager()->insertRefCellFromTemplate( pPreviousCell, this );
1317 }
1318 }
1319 }
1320
1321 if (bDoCompile)
1322 {
1323 ScTokenArray* pCodeOld = pCode;
1324 pCode = aComp.CompileString( aFormula, aFormulaNmsp ).release();
1325 assert(!mxGroup);
1326 delete pCodeOld;
1327
1328 if( pCode->GetCodeError() == FormulaError::NONE )
1329 {
1330 if ( !pCode->GetLen() )
1331 {
1332 if ( !aFormula.isEmpty() && aFormula[0] == '=' )
1333 pCode->AddBad( aFormula.copy( 1 ) );
1334 else
1335 pCode->AddBad( aFormula );
1336 }
1337 bSubTotal = aComp.CompileTokenArray();
1338 if( pCode->GetCodeError() == FormulaError::NONE )
1339 {
1340 nFormatType = aComp.GetNumFormatType();
1341 bChanged = true;
1342 bCompile = false;
1343 }
1344
1345 if (bSubTotal)
1346 rDocument.AddSubTotalCell(this);
1347 }
1348 else
1349 bChanged = true;
1350 }
1351
1352 // After loading, it must be known if ocDde/ocWebservice is in any formula
1353 // (for external links warning, CompileXML is called at the end of loading XML file)
1354 rDocument.CheckLinkFormulaNeedingCheck(*pCode);
1355
1356 //volatile cells must be added here for import
1357 if( !pCode->IsRecalcModeNormal() || pCode->IsRecalcModeForced())
1358 {
1359 // During load, only those cells that are marked explicitly dirty get
1360 // recalculated. So we need to set it dirty here.
1361 SetDirtyVar();
1362 rDocument.AppendToFormulaTrack(this);
1363 // Do not call TrackFormulas() here, not all listeners may have been
1364 // established, postponed until ScDocument::CompileXML() finishes.
1365 }
1366 else if (bWasInFormulaTree)
1367 rDocument.PutInFormulaTree(this);
1368 }
1369
CalcAfterLoad(sc::CompileFormulaContext & rCxt,bool bStartListening)1370 void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartListening )
1371 {
1372 bool bNewCompiled = false;
1373 // If a Calc 1.0-doc is read, we have a result, but no token array
1374 if( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1375 {
1376 rCxt.setGrammar(eTempGrammar);
1377 Compile(rCxt, aResult.GetHybridFormula(), true);
1378 aResult.SetToken( nullptr);
1379 bDirty = true;
1380 bNewCompiled = true;
1381 }
1382 // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now.
1383 if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
1384 {
1385 ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE);
1386 bSubTotal = aComp.CompileTokenArray();
1387 nFormatType = aComp.GetNumFormatType();
1388 bDirty = true;
1389 bCompile = false;
1390 bNewCompiled = true;
1391
1392 if (bSubTotal)
1393 rDocument.AddSubTotalCell(this);
1394 }
1395
1396 // On OS/2 with broken FPU exception, we can somehow store /0 without Err503. Later on in
1397 // the BLC Lib NumberFormatter crashes when doing a fabs (NAN) (# 32739 #).
1398 // We iron this out here for all systems, such that we also have an Err503 here.
1399 if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
1400 {
1401 OSL_FAIL("Formula cell INFINITY!!! Where does this document come from?");
1402 aResult.SetResultError( FormulaError::IllegalFPOperation );
1403 bDirty = true;
1404 }
1405
1406 // DoubleRefs for binary operators were always a Matrix before version v5.0.
1407 // Now this is only the case when in an array formula, otherwise it's an implicit intersection
1408 if ( ScDocument::GetSrcVersion() < SC_MATRIX_DOUBLEREF &&
1409 GetMatrixFlag() == ScMatrixMode::NONE && pCode->HasMatrixDoubleRefOps() )
1410 {
1411 cMatrixFlag = ScMatrixMode::Formula;
1412 SetMatColsRows( 1, 1);
1413 }
1414
1415 // Do the cells need to be calculated? After Load cells can contain an error code, and then start
1416 // the listener and Recalculate (if needed) if not ScRecalcMode::NORMAL
1417 if( !bNewCompiled || pCode->GetCodeError() == FormulaError::NONE )
1418 {
1419 if (bStartListening)
1420 StartListeningTo(rDocument);
1421
1422 if( !pCode->IsRecalcModeNormal() )
1423 bDirty = true;
1424 }
1425 if ( pCode->IsRecalcModeAlways() )
1426 { // random(), today(), now() always stay in the FormulaTree, so that they are calculated
1427 // for each F9
1428 bDirty = true;
1429 }
1430 // No SetDirty yet, as no all Listeners are known yet (only in SetDirtyAfterLoad)
1431 }
1432
MarkUsedExternalReferences()1433 bool ScFormulaCell::MarkUsedExternalReferences()
1434 {
1435 return pCode && rDocument.MarkUsedExternalReferences(*pCode, aPos);
1436 }
1437
1438 namespace {
1439 class RecursionCounter
1440 {
1441 ScRecursionHelper& rRec;
1442 bool bStackedInIteration;
1443 #if defined DBG_UTIL && !defined NDEBUG
1444 const ScFormulaCell* cell;
1445 #endif
1446 public:
RecursionCounter(ScRecursionHelper & r,ScFormulaCell * p)1447 RecursionCounter( ScRecursionHelper& r, ScFormulaCell* p )
1448 : rRec(r)
1449 #if defined DBG_UTIL && !defined NDEBUG
1450 , cell(p)
1451 #endif
1452 {
1453 bStackedInIteration = rRec.IsDoingIteration();
1454 if (bStackedInIteration)
1455 rRec.GetRecursionInIterationStack().push( p);
1456 rRec.IncRecursionCount();
1457 }
~RecursionCounter()1458 ~RecursionCounter()
1459 {
1460 rRec.DecRecursionCount();
1461 if (bStackedInIteration)
1462 {
1463 #if defined DBG_UTIL && !defined NDEBUG
1464 assert(rRec.GetRecursionInIterationStack().top() == cell);
1465 #endif
1466 rRec.GetRecursionInIterationStack().pop();
1467 }
1468 }
1469 };
1470
1471 // Forced calculation: OpenCL and threads require formula groups, so force even single cells to be a "group".
1472 // Remove the group again at the end, since there are some places throughout the code
1473 // that do not handle well groups with just 1 cell. Remove the groups only when the recursion level
1474 // reaches 0 again (groups contain some info such as disabling threading because of cycles, so removing
1475 // a group immediately would remove the info), for this reason affected cells are stored in the recursion
1476 // helper.
1477 struct TemporaryCellGroupMaker
1478 {
TemporaryCellGroupMaker__anonb316ae920211::TemporaryCellGroupMaker1479 TemporaryCellGroupMaker( ScFormulaCell* cell, bool enable )
1480 : mCell( cell )
1481 , mEnabled( enable )
1482 {
1483 if( mEnabled && mCell->GetCellGroup() == nullptr )
1484 {
1485 mCell->CreateCellGroup( 1, false );
1486 mCell->GetDocument().GetRecursionHelper().AddTemporaryGroupCell( mCell );
1487 }
1488 }
~TemporaryCellGroupMaker__anonb316ae920211::TemporaryCellGroupMaker1489 ~TemporaryCellGroupMaker() COVERITY_NOEXCEPT_FALSE
1490 {
1491 if( mEnabled )
1492 mCell->GetDocument().GetRecursionHelper().CleanTemporaryGroupCells();
1493 }
1494 ScFormulaCell* mCell;
1495 const bool mEnabled;
1496 };
1497
1498 } // namespace
1499
Interpret(SCROW nStartOffset,SCROW nEndOffset)1500 bool ScFormulaCell::Interpret(SCROW nStartOffset, SCROW nEndOffset)
1501 {
1502 ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
1503 bool bGroupInterpreted = false;
1504
1505 // The result would possibly depend on a cell without a valid value, bail out
1506 // the entire dependency computation.
1507 if (rRecursionHelper.IsAbortingDependencyComputation())
1508 return false;
1509
1510 if ((mxGroup && !rRecursionHelper.CheckFGIndependence(mxGroup.get())) || !rRecursionHelper.AreGroupsIndependent())
1511 return bGroupInterpreted;
1512
1513 static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
1514 TemporaryCellGroupMaker cellGroupMaker( this, forceType != ForceCalculationNone && forceType != ForceCalculationCore );
1515
1516 ScFormulaCell* pTopCell = mxGroup ? mxGroup->mpTopCell : this;
1517
1518 if (pTopCell->mbSeenInPath && rRecursionHelper.GetDepComputeLevel() &&
1519 rRecursionHelper.AnyCycleMemberInDependencyEvalMode(pTopCell))
1520 {
1521 // This call arose from a dependency calculation and we just found a cycle.
1522 // This will mark all elements in the cycle as parts-of-cycle.
1523 ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, pTopCell);
1524 // Reaching here does not necessarily mean a circular reference, so don't set Err:522 here yet.
1525 // If there is a genuine circular reference, it will be marked so when all groups
1526 // in the cycle get out of dependency evaluation mode.
1527 // But returning without calculation a new value means other cells depending
1528 // on this one would use a possibly invalid value, so ensure the dependency
1529 // computation is aborted without resetting the dirty flag of any cell.
1530 rRecursionHelper.AbortDependencyComputation();
1531 return bGroupInterpreted;
1532 }
1533
1534 #if DEBUG_CALCULATION
1535 static bool bDebugCalculationInit = true;
1536 if (bDebugCalculationInit)
1537 {
1538 aDC.maTrigger = aDebugCalculationTriggerAddress;
1539 aDC.mbPrintResults = true;
1540 bDebugCalculationInit = false;
1541 }
1542 DebugCalculationStacker aDebugEntry(aPos, rDocument);
1543 #endif
1544
1545 if (!IsDirtyOrInTableOpDirty() || rRecursionHelper.IsInReturn())
1546 return bGroupInterpreted; // no double/triple processing
1547
1548 //FIXME:
1549 // If the call originates from a Reschedule in DdeLink update, leave dirty
1550 // Better: Do a Dde Link Update without Reschedule or do it completely asynchronously!
1551 if ( rDocument.IsInDdeLinkUpdate() )
1552 return bGroupInterpreted;
1553
1554 if (bRunning)
1555 {
1556 if (!rDocument.GetDocOptions().IsIter())
1557 {
1558 aResult.SetResultError( FormulaError::CircularReference );
1559 return bGroupInterpreted;
1560 }
1561
1562 if (aResult.GetResultError() == FormulaError::CircularReference)
1563 aResult.SetResultError( FormulaError::NONE );
1564
1565 // Start or add to iteration list.
1566 if (!rRecursionHelper.IsDoingIteration() ||
1567 !rRecursionHelper.GetRecursionInIterationStack().top()->bIsIterCell)
1568 rRecursionHelper.SetInIterationReturn( true);
1569
1570 return bGroupInterpreted;
1571 }
1572 // no multiple interprets for GetErrCode, IsValue, GetValue and
1573 // different entry point recursions. Would also lead to premature
1574 // convergence in iterations.
1575 if (rRecursionHelper.GetIteration() && nSeenInIteration ==
1576 rRecursionHelper.GetIteration())
1577 return bGroupInterpreted;
1578
1579 bool bOldRunning = bRunning;
1580 if (rRecursionHelper.GetRecursionCount() > MAXRECURSION)
1581 {
1582 bRunning = true;
1583 rRecursionHelper.SetInRecursionReturn( true);
1584 }
1585 else
1586 {
1587 rDocument.IncInterpretLevel();
1588
1589 #if DEBUG_CALCULATION
1590 aDC.enterGroup();
1591 #endif
1592 bool bPartOfCycleBefore = mxGroup && mxGroup->mbPartOfCycle;
1593 bGroupInterpreted = InterpretFormulaGroup(nStartOffset, nEndOffset);
1594 bool bPartOfCycleAfter = mxGroup && mxGroup->mbPartOfCycle;
1595
1596 #if DEBUG_CALCULATION
1597 aDC.leaveGroup();
1598 #endif
1599 if (!bGroupInterpreted)
1600 {
1601 // This call resulted from a dependency calculation for a multigroup-threading attempt,
1602 // but found dependency among the groups.
1603 if (!rRecursionHelper.AreGroupsIndependent())
1604 {
1605 rDocument.DecInterpretLevel();
1606 return bGroupInterpreted;
1607 }
1608 // Dependency calc inside InterpretFormulaGroup() failed due to
1609 // detection of a cycle and there are parent FG's in the cycle.
1610 // Skip InterpretTail() in such cases, only run InterpretTail for the "cycle-starting" FG
1611 if (!bPartOfCycleBefore && bPartOfCycleAfter && rRecursionHelper.AnyParentFGInCycle())
1612 {
1613 rDocument.DecInterpretLevel();
1614 return bGroupInterpreted;
1615 }
1616
1617 ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
1618 ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1619 InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
1620 }
1621
1622 rDocument.DecInterpretLevel();
1623 }
1624
1625 // While leaving a recursion or iteration stack, insert its cells to the
1626 // recursion list in reverse order.
1627 if (rRecursionHelper.IsInReturn())
1628 {
1629 bool bFreeFlyingInserted = false;
1630 if (rRecursionHelper.GetRecursionCount() > 0 || !rRecursionHelper.IsDoingRecursion())
1631 {
1632 rRecursionHelper.Insert( this, bOldRunning, aResult);
1633 bFreeFlyingInserted = mbFreeFlying;
1634 }
1635 bool bIterationFromRecursion = false;
1636 bool bResumeIteration = false;
1637 do
1638 {
1639 if ((rRecursionHelper.IsInIterationReturn() &&
1640 rRecursionHelper.GetRecursionCount() == 0 &&
1641 !rRecursionHelper.IsDoingIteration()) ||
1642 bIterationFromRecursion || bResumeIteration)
1643 {
1644 bool & rDone = rRecursionHelper.GetConvergingReference();
1645 rDone = false;
1646 if (!bIterationFromRecursion && bResumeIteration)
1647 {
1648 bResumeIteration = false;
1649 // Resuming iteration expands the range.
1650 ScFormulaRecursionList::const_iterator aOldStart(
1651 rRecursionHelper.GetLastIterationStart());
1652 rRecursionHelper.ResumeIteration();
1653 // Mark new cells being in iteration.
1654 for (ScFormulaRecursionList::const_iterator aIter(
1655 rRecursionHelper.GetIterationStart()); aIter !=
1656 aOldStart; ++aIter)
1657 {
1658 ScFormulaCell* pIterCell = (*aIter).pCell;
1659 pIterCell->bIsIterCell = true;
1660 }
1661 // Mark older cells dirty again, in case they converted
1662 // without accounting for all remaining cells in the circle
1663 // that weren't touched so far, e.g. conditional. Restore
1664 // backupped result.
1665 sal_uInt16 nIteration = rRecursionHelper.GetIteration();
1666 for (ScFormulaRecursionList::const_iterator aIter(
1667 aOldStart); aIter !=
1668 rRecursionHelper.GetIterationEnd(); ++aIter)
1669 {
1670 ScFormulaCell* pIterCell = (*aIter).pCell;
1671 if (pIterCell->nSeenInIteration == nIteration)
1672 {
1673 if (!pIterCell->bDirty || aIter == aOldStart)
1674 {
1675 pIterCell->aResult = (*aIter).aPreviousResult;
1676 }
1677 --pIterCell->nSeenInIteration;
1678 }
1679 pIterCell->bDirty = true;
1680 }
1681 }
1682 else
1683 {
1684 bResumeIteration = false;
1685 // Close circle once. If 'this' is self-referencing only
1686 // (e.g. counter or self-adder) then it is already
1687 // implicitly closed.
1688 /* TODO: does this even make sense anymore? The last cell
1689 * added above with rRecursionHelper.Insert() should always
1690 * be 'this', shouldn't it? */
1691 if (rRecursionHelper.GetList().size() > 1)
1692 {
1693 ScFormulaCell* pLastCell = rRecursionHelper.GetList().back().pCell;
1694 if (pLastCell != this)
1695 {
1696 rDocument.IncInterpretLevel();
1697 ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1698 pLastCell->InterpretTail(
1699 *aContextGetterGuard.GetInterpreterContext(), SCITP_CLOSE_ITERATION_CIRCLE);
1700 rDocument.DecInterpretLevel();
1701 }
1702 }
1703 // Start at 1, init things.
1704 rRecursionHelper.StartIteration();
1705 // Mark all cells being in iteration. Reset results to
1706 // original values, formula cells have been interpreted
1707 // already, discard that step.
1708 for (ScFormulaRecursionList::const_iterator aIter(
1709 rRecursionHelper.GetIterationStart()); aIter !=
1710 rRecursionHelper.GetIterationEnd(); ++aIter)
1711 {
1712 ScFormulaCell* pIterCell = (*aIter).pCell;
1713 pIterCell->aResult = (*aIter).aPreviousResult;
1714 pIterCell->bIsIterCell = true;
1715 }
1716 }
1717 bIterationFromRecursion = false;
1718 sal_uInt16 nIterMax = rDocument.GetDocOptions().GetIterCount();
1719 for ( ; rRecursionHelper.GetIteration() <= nIterMax && !rDone;
1720 rRecursionHelper.IncIteration())
1721 {
1722 rDone = false;
1723 bool bFirst = true;
1724 for ( ScFormulaRecursionList::iterator aIter(
1725 rRecursionHelper.GetIterationStart()); aIter !=
1726 rRecursionHelper.GetIterationEnd() &&
1727 !rRecursionHelper.IsInReturn(); ++aIter)
1728 {
1729 ScFormulaCell* pIterCell = (*aIter).pCell;
1730 if (pIterCell->IsDirtyOrInTableOpDirty() &&
1731 rRecursionHelper.GetIteration() !=
1732 pIterCell->GetSeenInIteration())
1733 {
1734 (*aIter).aPreviousResult = pIterCell->aResult;
1735 rDocument.IncInterpretLevel();
1736 ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1737 pIterCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_FROM_ITERATION);
1738 rDocument.DecInterpretLevel();
1739 }
1740 if (bFirst)
1741 {
1742 rDone = !pIterCell->IsDirtyOrInTableOpDirty();
1743 bFirst = false;
1744 }
1745 else if (rDone)
1746 {
1747 rDone = !pIterCell->IsDirtyOrInTableOpDirty();
1748 }
1749 }
1750 if (rRecursionHelper.IsInReturn())
1751 {
1752 bResumeIteration = true;
1753 break; // for
1754 // Don't increment iteration.
1755 }
1756 }
1757 if (!bResumeIteration)
1758 {
1759 if (rDone)
1760 {
1761 for (ScFormulaRecursionList::const_iterator aIter(
1762 rRecursionHelper.GetIterationStart());
1763 aIter != rRecursionHelper.GetIterationEnd();
1764 ++aIter)
1765 {
1766 ScFormulaCell* pIterCell = (*aIter).pCell;
1767 pIterCell->bIsIterCell = false;
1768 pIterCell->nSeenInIteration = 0;
1769 pIterCell->bRunning = (*aIter).bOldRunning;
1770 }
1771 }
1772 else
1773 {
1774 for (ScFormulaRecursionList::const_iterator aIter(
1775 rRecursionHelper.GetIterationStart());
1776 aIter != rRecursionHelper.GetIterationEnd();
1777 ++aIter)
1778 {
1779 ScFormulaCell* pIterCell = (*aIter).pCell;
1780 pIterCell->bIsIterCell = false;
1781 pIterCell->nSeenInIteration = 0;
1782 pIterCell->bRunning = (*aIter).bOldRunning;
1783 pIterCell->ResetDirty();
1784 // The difference to Excel is that Excel does not
1785 // produce an error for non-convergence thus a
1786 // delta of 0.001 still works to execute the
1787 // maximum number of iterations and display the
1788 // results no matter if the result anywhere reached
1789 // near delta, but also never indicates whether the
1790 // result actually makes sense in case of
1791 // non-counter context. Calc does check the delta
1792 // in every case. If we wanted to support what
1793 // Excel does then add another option "indicate
1794 // non-convergence error" (default on) and execute
1795 // the following block only if set.
1796 #if 1
1797 // If one cell didn't converge, all cells of this
1798 // circular dependency don't, no matter whether
1799 // single cells did.
1800 pIterCell->aResult.SetResultError( FormulaError::NoConvergence);
1801 pIterCell->bChanged = true;
1802 #endif
1803 }
1804 }
1805 // End this iteration and remove entries.
1806 rRecursionHelper.EndIteration();
1807 bResumeIteration = rRecursionHelper.IsDoingIteration();
1808 }
1809 }
1810 if (rRecursionHelper.IsInRecursionReturn() &&
1811 rRecursionHelper.GetRecursionCount() == 0 &&
1812 !rRecursionHelper.IsDoingRecursion())
1813 {
1814 bIterationFromRecursion = false;
1815 // Iterate over cells known so far, start with the last cell
1816 // encountered, inserting new cells if another recursion limit
1817 // is reached. Repeat until solved.
1818 rRecursionHelper.SetDoingRecursion( true);
1819 do
1820 {
1821 rRecursionHelper.SetInRecursionReturn( false);
1822 for (ScFormulaRecursionList::const_iterator aIter(
1823 rRecursionHelper.GetIterationStart());
1824 !rRecursionHelper.IsInReturn() && aIter !=
1825 rRecursionHelper.GetIterationEnd(); ++aIter)
1826 {
1827 ScFormulaCell* pCell = (*aIter).pCell;
1828 if (pCell->IsDirtyOrInTableOpDirty())
1829 {
1830 rDocument.IncInterpretLevel();
1831 ScInterpreterContextGetterGuard aContextGetterGuard(rDocument, rDocument.GetFormatTable());
1832 pCell->InterpretTail( *aContextGetterGuard.GetInterpreterContext(), SCITP_NORMAL);
1833 rDocument.DecInterpretLevel();
1834 if (!pCell->IsDirtyOrInTableOpDirty() && !pCell->IsIterCell())
1835 pCell->bRunning = (*aIter).bOldRunning;
1836 }
1837 }
1838 } while (rRecursionHelper.IsInRecursionReturn());
1839 rRecursionHelper.SetDoingRecursion( false);
1840 if (rRecursionHelper.IsInIterationReturn())
1841 {
1842 if (!bResumeIteration)
1843 bIterationFromRecursion = true;
1844 }
1845 else if (bResumeIteration ||
1846 rRecursionHelper.IsDoingIteration())
1847 rRecursionHelper.GetList().erase(
1848 rRecursionHelper.GetIterationStart(),
1849 rRecursionHelper.GetLastIterationStart());
1850 else
1851 rRecursionHelper.Clear();
1852 }
1853 } while (bIterationFromRecursion || bResumeIteration);
1854
1855 if (bFreeFlyingInserted)
1856 {
1857 // Remove this from recursion list, it may get deleted.
1858 // It additionally also should mean that the recursion/iteration
1859 // ends here as it must had been triggered by this free-flying
1860 // out-of-sheets cell
1861 const bool bOnlyThis = (rRecursionHelper.GetList().size() == 1);
1862 rRecursionHelper.GetList().remove_if([this](const ScFormulaRecursionEntry& r){return r.pCell == this;});
1863 if (bOnlyThis)
1864 {
1865 assert(rRecursionHelper.GetList().empty());
1866 if (rRecursionHelper.GetList().empty())
1867 rRecursionHelper.EndIteration();
1868 }
1869 }
1870 }
1871
1872 #if DEBUG_CALCULATION
1873 FormulaError nErr = aResult.GetResultError();
1874 if (nErr != FormulaError::NONE)
1875 aDC.storeResultError( nErr);
1876 else if (aResult.IsValue())
1877 aDC.storeResult( aResult.GetDouble());
1878 else
1879 aDC.storeResult( aResult.GetString());
1880 #endif
1881
1882 return bGroupInterpreted;
1883 }
1884
InterpretTail(ScInterpreterContext & rContext,ScInterpretTailParameter eTailParam)1885 void ScFormulaCell::InterpretTail( ScInterpreterContext& rContext, ScInterpretTailParameter eTailParam )
1886 {
1887 RecursionCounter aRecursionCounter( rDocument.GetRecursionHelper(), this);
1888 // TODO If this cell is not an iteration cell, add it to the list of iteration cells?
1889 if(bIsIterCell)
1890 nSeenInIteration = rDocument.GetRecursionHelper().GetIteration();
1891 if( !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE )
1892 {
1893 // #i11719# no RPN and no error and no token code but result string present
1894 // => interpretation of this cell during name-compilation and unknown names
1895 // => can't exchange underlying code array in CompileTokenArray() /
1896 // Compile() because interpreter's token iterator would crash or pCode
1897 // would be deleted twice if this cell was interpreted during
1898 // compilation.
1899 // This should only be a temporary condition and, since we set an
1900 // error, if ran into it again we'd bump into the dirty-clearing
1901 // condition further down.
1902 if ( !pCode->GetLen() && !aResult.GetHybridFormula().isEmpty() )
1903 {
1904 pCode->SetCodeError( FormulaError::NoCode );
1905 // This is worth an assertion; if encountered in daily work
1906 // documents we might need another solution. Or just confirm correctness.
1907 return;
1908 }
1909 CompileTokenArray();
1910 }
1911
1912 if( pCode->GetCodeLen() )
1913 {
1914 std::unique_ptr<ScInterpreter> pScopedInterpreter;
1915 ScInterpreter* pInterpreter;
1916 if (rContext.pInterpreter)
1917 {
1918 pInterpreter = rContext.pInterpreter;
1919 pInterpreter->Init(this, aPos, *pCode);
1920 }
1921 else
1922 {
1923 pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rContext, aPos, *pCode ));
1924 pInterpreter = pScopedInterpreter.get();
1925 }
1926
1927 FormulaError nOldErrCode = aResult.GetResultError();
1928 if ( nSeenInIteration == 0 )
1929 { // Only the first time
1930 // With bChanged=false, if a newly compiled cell has a result of
1931 // 0.0, no change is detected and the cell will not be repainted.
1932 // bChanged = false;
1933 aResult.SetResultError( FormulaError::NONE );
1934 }
1935
1936 switch ( aResult.GetResultError() )
1937 {
1938 case FormulaError::CircularReference : // will be determined again if so
1939 aResult.SetResultError( FormulaError::NONE );
1940 break;
1941 default: break;
1942 }
1943
1944 bool bOldRunning = bRunning;
1945 bRunning = true;
1946 pInterpreter->Interpret();
1947 if (rDocument.GetRecursionHelper().IsInReturn() && eTailParam != SCITP_CLOSE_ITERATION_CIRCLE)
1948 {
1949 if (nSeenInIteration > 0)
1950 --nSeenInIteration; // retry when iteration is resumed
1951
1952 if ( aResult.GetType() == formula::svUnknown )
1953 aResult.SetToken( pInterpreter->GetResultToken().get() );
1954
1955 return;
1956 }
1957 bRunning = bOldRunning;
1958
1959 // The result may be invalid or depend on another invalid result, just abort
1960 // without updating the cell value. Since the dirty flag will not be reset,
1961 // the proper value will be computed later.
1962 if(rDocument.GetRecursionHelper().IsAbortingDependencyComputation())
1963 return;
1964
1965 // #i102616# For single-sheet saving consider only content changes, not format type,
1966 // because format type isn't set on loading (might be changed later)
1967 bool bContentChanged = false;
1968
1969 // Do not create a HyperLink() cell if the formula results in an error.
1970 if( pInterpreter->GetError() != FormulaError::NONE && pCode->IsHyperLink())
1971 pCode->SetHyperLink(false);
1972
1973 if( pInterpreter->GetError() != FormulaError::NONE && pInterpreter->GetError() != FormulaError::CircularReference)
1974 {
1975 bChanged = true;
1976
1977 if (pInterpreter->GetError() == FormulaError::RetryCircular)
1978 {
1979 // Array formula matrix calculation corner case. Keep dirty
1980 // state, do not remove from formula tree or anything else, but
1981 // store FormulaError::CircularReference in case this cell does not get
1982 // recalculated.
1983 aResult.SetResultError( FormulaError::CircularReference);
1984 return;
1985 }
1986
1987 ResetDirty();
1988 }
1989
1990 if (eTailParam == SCITP_FROM_ITERATION && IsDirtyOrInTableOpDirty())
1991 {
1992 bool bIsValue = aResult.IsValue(); // the previous type
1993 // Did it converge?
1994 if ((bIsValue && pInterpreter->GetResultType() == svDouble && fabs(
1995 pInterpreter->GetNumResult() - aResult.GetDouble()) <=
1996 rDocument.GetDocOptions().GetIterEps()) ||
1997 (!bIsValue && pInterpreter->GetResultType() == svString &&
1998 pInterpreter->GetStringResult() == aResult.GetString()))
1999 {
2000 // A convergence in the first iteration doesn't necessarily
2001 // mean that it's done, it may be as not all related cells
2002 // of a circle changed their values yet. If the set really
2003 // converges it will do so also during the next iteration. This
2004 // fixes situations like of #i44115#. If this wasn't wanted an
2005 // initial "uncalculated" value would be needed for all cells
2006 // of a circular dependency => graph needed before calculation.
2007 if (nSeenInIteration > 1 ||
2008 rDocument.GetDocOptions().GetIterCount() == 1)
2009 {
2010 ResetDirty();
2011 }
2012 }
2013 }
2014
2015 // New error code?
2016 if( pInterpreter->GetError() != nOldErrCode )
2017 {
2018 bChanged = true;
2019 // bContentChanged only has to be set if the file content would be changed
2020 if ( aResult.GetCellResultType() != svUnknown )
2021 bContentChanged = true;
2022 }
2023
2024 ScFormulaResult aNewResult( pInterpreter->GetResultToken().get());
2025
2026 // For IF() and other jumps or changed formatted source data the result
2027 // format may change for different runs, e.g. =IF(B1,B1) with first
2028 // B1:0 boolean FALSE next B1:23 numeric 23, we don't want the 23
2029 // displayed as TRUE. Do not force a general format though if
2030 // mbNeedsNumberFormat is set (because there was a general format..).
2031 // Note that nFormatType may be out of sync here if a format was
2032 // applied or cleared after the last run, but obtaining the current
2033 // format always just to check would be expensive. There may be
2034 // cases where the format should be changed but is not. If that turns
2035 // out to be a real problem then obtain the current format type after
2036 // the initial check when needed.
2037 bool bForceNumberFormat = (mbAllowNumberFormatChange && !mbNeedsNumberFormat &&
2038 !SvNumberFormatter::IsCompatible( nFormatType, pInterpreter->GetRetFormatType()));
2039
2040 // We have some requirements additionally to IsCompatible().
2041 // * Do not apply a NumberFormat::LOGICAL if the result value is not
2042 // 1.0 or 0.0
2043 // * Do not override an already set numeric number format if the result
2044 // is of type NumberFormat::LOGICAL, it could be user applied.
2045 // On the other hand, for an empty jump path instead of FALSE an
2046 // unexpected for example 0% could be displayed. YMMV.
2047 // * Never override a non-standard number format that indicates user
2048 // applied.
2049 // * NumberFormat::TEXT does not force a change.
2050 if (bForceNumberFormat)
2051 {
2052 sal_uInt32 nOldFormatIndex = NUMBERFORMAT_ENTRY_NOT_FOUND;
2053 const SvNumFormatType nRetType = pInterpreter->GetRetFormatType();
2054 if (nRetType == SvNumFormatType::LOGICAL)
2055 {
2056 double fVal = aNewResult.GetDouble();
2057 if (fVal != 1.0 && fVal != 0.0)
2058 bForceNumberFormat = false;
2059 else
2060 {
2061 nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
2062 nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
2063 switch (nFormatType)
2064 {
2065 case SvNumFormatType::PERCENT:
2066 case SvNumFormatType::CURRENCY:
2067 case SvNumFormatType::SCIENTIFIC:
2068 case SvNumFormatType::FRACTION:
2069 bForceNumberFormat = false;
2070 break;
2071 case SvNumFormatType::NUMBER:
2072 if ((nOldFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0)
2073 bForceNumberFormat = false;
2074 break;
2075 default: break;
2076 }
2077 }
2078 }
2079 else if (nRetType == SvNumFormatType::TEXT)
2080 {
2081 bForceNumberFormat = false;
2082 }
2083 if (bForceNumberFormat)
2084 {
2085 if (nOldFormatIndex == NUMBERFORMAT_ENTRY_NOT_FOUND)
2086 {
2087 nOldFormatIndex = rDocument.GetNumberFormat( rContext, aPos);
2088 nFormatType = rContext.GetFormatTable()->GetType( nOldFormatIndex);
2089 }
2090 if (nOldFormatIndex !=
2091 ScGlobal::GetStandardFormat(rContext, nOldFormatIndex, nFormatType))
2092 bForceNumberFormat = false;
2093 }
2094 }
2095
2096 if( mbNeedsNumberFormat || bForceNumberFormat )
2097 {
2098 bool bSetFormat = true;
2099 const SvNumFormatType nOldFormatType = nFormatType;
2100 nFormatType = pInterpreter->GetRetFormatType();
2101 sal_uInt32 nFormatIndex = pInterpreter->GetRetFormatIndex();
2102
2103 if (nFormatType == SvNumFormatType::TEXT)
2104 {
2105 // Don't set text format as hard format.
2106 bSetFormat = false;
2107 }
2108 else if (nFormatType == SvNumFormatType::LOGICAL && cMatrixFlag != ScMatrixMode::NONE)
2109 {
2110 // In a matrix range do not set an (inherited) logical format
2111 // as hard format if the value does not represent a strict TRUE
2112 // or FALSE value. But do set for a top left error value so
2113 // following matrix cells can inherit for non-error values.
2114 // This solves a problem with IF() expressions in array context
2115 // where incidentally the top left element results in logical
2116 // type but some others don't. It still doesn't solve the
2117 // reverse case though, where top left is not logical type but
2118 // some other elements should be. We'd need to transport type
2119 // or format information on arrays.
2120 StackVar eNewCellResultType = aNewResult.GetCellResultType();
2121 if (eNewCellResultType != svError || cMatrixFlag == ScMatrixMode::Reference)
2122 {
2123 if (eNewCellResultType != svDouble)
2124 {
2125 bSetFormat = false;
2126 nFormatType = nOldFormatType; // that? or number?
2127 }
2128 else
2129 {
2130 double fVal = aNewResult.GetDouble();
2131 if (fVal != 1.0 && fVal != 0.0)
2132 {
2133 bSetFormat = false;
2134 nFormatType = SvNumFormatType::NUMBER;
2135 }
2136 }
2137 }
2138 }
2139
2140 if (bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) == 0)))
2141 nFormatIndex = ScGlobal::GetStandardFormat(rContext, nFormatIndex, nFormatType);
2142
2143 // Do not replace a General format (which was the reason why
2144 // mbNeedsNumberFormat was set) with a General format.
2145 // 1. setting a format has quite some overhead in the
2146 // ScPatternAttr/ScAttrArray handling, even if identical.
2147 // 2. the General formats may be of different locales.
2148 // XXX if mbNeedsNumberFormat was set even if the current format
2149 // was not General then we'd have to obtain the current format here
2150 // and check at least the types.
2151 const bool bSetNumberFormat = bSetFormat && (bForceNumberFormat || ((nFormatIndex % SV_COUNTRY_LANGUAGE_OFFSET) != 0));
2152 if (bSetNumberFormat && !rDocument.IsInLayoutStrings())
2153 {
2154 // set number format explicitly
2155 if (!rDocument.IsThreadedGroupCalcInProgress())
2156 rDocument.SetNumberFormat( aPos, nFormatIndex );
2157 else
2158 {
2159 // SetNumberFormat() is not thread-safe (modifies ScAttrArray), delay the work
2160 // to the main thread. Since thread calculations operate on formula groups,
2161 // it's enough to store just the row.
2162 DelayedSetNumberFormat data = { aPos.Col(), aPos.Row(), nFormatIndex };
2163 rContext.maDelayedSetNumberFormat.push_back( data );
2164 }
2165 bChanged = true;
2166 }
2167
2168 // Currently (2019-05-10) nothing else can cope with a duration
2169 // format type, change to time as it was before.
2170 if (nFormatType == SvNumFormatType::DURATION)
2171 nFormatType = SvNumFormatType::TIME;
2172
2173 mbNeedsNumberFormat = false;
2174 }
2175
2176 // In case of changes just obtain the result, no temporary and
2177 // comparison needed anymore.
2178 if (bChanged)
2179 {
2180 // #i102616# Compare anyway if the sheet is still marked unchanged for single-sheet saving
2181 // Also handle special cases of initial results after loading.
2182 if ( !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
2183 {
2184 StackVar eOld = aResult.GetCellResultType();
2185 StackVar eNew = aNewResult.GetCellResultType();
2186 if ( eOld == svUnknown && ( eNew == svError || ( eNew == svDouble && aNewResult.GetDouble() == 0.0 ) ) )
2187 {
2188 // ScXMLTableRowCellContext::EndElement doesn't call SetFormulaResultDouble for 0
2189 // -> no change
2190 }
2191 else
2192 {
2193 if ( eOld == svHybridCell ) // string result from SetFormulaResultString?
2194 eOld = svString; // ScHybridCellToken has a valid GetString method
2195
2196 // #i106045# use approxEqual to compare with stored value
2197 bContentChanged = (eOld != eNew ||
2198 (eNew == svDouble && !rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble() )) ||
2199 (eNew == svString && aResult.GetString() != aNewResult.GetString()));
2200 }
2201 }
2202
2203 aResult.SetToken( pInterpreter->GetResultToken().get() );
2204 }
2205 else
2206 {
2207 StackVar eOld = aResult.GetCellResultType();
2208 StackVar eNew = aNewResult.GetCellResultType();
2209 bChanged = (eOld != eNew ||
2210 (eNew == svDouble && aResult.GetDouble() != aNewResult.GetDouble()) ||
2211 (eNew == svString && aResult.GetString() != aNewResult.GetString()));
2212
2213 // #i102616# handle special cases of initial results after loading
2214 // (only if the sheet is still marked unchanged)
2215 if ( bChanged && !bContentChanged && rDocument.IsStreamValid(aPos.Tab()) )
2216 {
2217 if ((eOld == svUnknown && (eNew == svError || (eNew == svDouble && aNewResult.GetDouble() == 0.0))) ||
2218 ((eOld == svHybridCell) &&
2219 eNew == svString && aResult.GetString() == aNewResult.GetString()) ||
2220 (eOld == svDouble && eNew == svDouble &&
2221 rtl::math::approxEqual( aResult.GetDouble(), aNewResult.GetDouble())))
2222 {
2223 // no change, see above
2224 }
2225 else
2226 bContentChanged = true;
2227 }
2228
2229 aResult.Assign( aNewResult);
2230 }
2231
2232 // Precision as shown?
2233 if ( aResult.IsValue() && pInterpreter->GetError() == FormulaError::NONE
2234 && rDocument.GetDocOptions().IsCalcAsShown()
2235 && nFormatType != SvNumFormatType::DATE
2236 && nFormatType != SvNumFormatType::TIME
2237 && nFormatType != SvNumFormatType::DATETIME )
2238 {
2239 sal_uInt32 nFormat = rDocument.GetNumberFormat( rContext, aPos );
2240 aResult.SetDouble( rDocument.RoundValueAsShown(
2241 aResult.GetDouble(), nFormat, &rContext));
2242 }
2243 if (eTailParam == SCITP_NORMAL)
2244 {
2245 ResetDirty();
2246 }
2247 if( aResult.GetMatrix() )
2248 {
2249 // If the formula wasn't entered as a matrix formula, live on with
2250 // the upper left corner and let reference counting delete the matrix.
2251 if( cMatrixFlag != ScMatrixMode::Formula && !pCode->IsHyperLink() )
2252 aResult.SetToken( aResult.GetCellResultToken().get());
2253 }
2254 if ( aResult.IsValue() && !std::isfinite( aResult.GetDouble() ) )
2255 {
2256 // Coded double error may occur via filter import.
2257 FormulaError nErr = GetDoubleErrorValue( aResult.GetDouble());
2258 aResult.SetResultError( nErr);
2259 bChanged = bContentChanged = true;
2260 }
2261
2262 if (bContentChanged && rDocument.IsStreamValid(aPos.Tab()))
2263 {
2264 // pass bIgnoreLock=true, because even if called from pending row height update,
2265 // a changed result must still reset the stream flag
2266 rDocument.SetStreamValid(aPos.Tab(), false, true);
2267 }
2268 if ( !rDocument.IsThreadedGroupCalcInProgress() && !pCode->IsRecalcModeAlways() )
2269 rDocument.RemoveFromFormulaTree( this );
2270
2271 // FORCED cells also immediately tested for validity (start macro possibly)
2272
2273 if ( pCode->IsRecalcModeForced() )
2274 {
2275 sal_uInt32 nValidation = rDocument.GetAttr(
2276 aPos.Col(), aPos.Row(), aPos.Tab(), ATTR_VALIDDATA )->GetValue();
2277 if ( nValidation )
2278 {
2279 const ScValidationData* pData = rDocument.GetValidationEntry( nValidation );
2280 ScRefCellValue aTmpCell(this);
2281 if ( pData && !pData->IsDataValid(aTmpCell, aPos))
2282 pData->DoCalcError( this );
2283 }
2284 }
2285
2286 // Reschedule slows the whole thing down considerably, thus only execute on percent change
2287 if (!rDocument.IsThreadedGroupCalcInProgress())
2288 {
2289 ScProgress *pProgress = ScProgress::GetInterpretProgress();
2290 if (pProgress && pProgress->Enabled())
2291 {
2292 pProgress->SetStateCountDownOnPercent(
2293 rDocument.GetFormulaCodeInTree()/MIN_NO_CODES_PER_PROGRESS_UPDATE );
2294 }
2295
2296 switch (pInterpreter->GetVolatileType())
2297 {
2298 case ScInterpreter::VOLATILE:
2299 // Volatile via built-in volatile functions. No actions needed.
2300 break;
2301 case ScInterpreter::VOLATILE_MACRO:
2302 // The formula contains a volatile macro.
2303 pCode->SetExclusiveRecalcModeAlways();
2304 rDocument.PutInFormulaTree(this);
2305 StartListeningTo(rDocument);
2306 break;
2307 case ScInterpreter::NOT_VOLATILE:
2308 if (pCode->IsRecalcModeAlways())
2309 {
2310 // The formula was previously volatile, but no more.
2311 EndListeningTo(rDocument);
2312 pCode->SetExclusiveRecalcModeNormal();
2313 }
2314 else
2315 {
2316 // non-volatile formula. End listening to the area in case
2317 // it's listening due to macro module change.
2318 rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
2319 }
2320 rDocument.RemoveFromFormulaTree(this);
2321 break;
2322 default:
2323 ;
2324 }
2325 }
2326 }
2327 else
2328 {
2329 // Cells with compiler errors should not be marked dirty forever
2330 OSL_ENSURE( pCode->GetCodeError() != FormulaError::NONE, "no RPN code and no errors ?!?!" );
2331 ResetDirty();
2332 }
2333
2334 pCode->ClearRecalcModeMustAfterImport();
2335 }
2336
HandleStuffAfterParallelCalculation(ScInterpreter * pInterpreter)2337 void ScFormulaCell::HandleStuffAfterParallelCalculation(ScInterpreter* pInterpreter)
2338 {
2339 aResult.HandleStuffAfterParallelCalculation();
2340
2341 if( !pCode->GetCodeLen() )
2342 return;
2343
2344 if ( !pCode->IsRecalcModeAlways() )
2345 rDocument.RemoveFromFormulaTree( this );
2346
2347 std::unique_ptr<ScInterpreter> pScopedInterpreter;
2348 if (pInterpreter)
2349 pInterpreter->Init(this, aPos, *pCode);
2350 else
2351 {
2352 pScopedInterpreter.reset(new ScInterpreter( this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode ));
2353 pInterpreter = pScopedInterpreter.get();
2354 }
2355
2356 switch (pInterpreter->GetVolatileType())
2357 {
2358 case ScInterpreter::VOLATILE_MACRO:
2359 // The formula contains a volatile macro.
2360 pCode->SetExclusiveRecalcModeAlways();
2361 rDocument.PutInFormulaTree(this);
2362 StartListeningTo(rDocument);
2363 break;
2364 case ScInterpreter::NOT_VOLATILE:
2365 if (pCode->IsRecalcModeAlways())
2366 {
2367 // The formula was previously volatile, but no more.
2368 EndListeningTo(rDocument);
2369 pCode->SetExclusiveRecalcModeNormal();
2370 }
2371 else
2372 {
2373 // non-volatile formula. End listening to the area in case
2374 // it's listening due to macro module change.
2375 rDocument.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
2376 }
2377 rDocument.RemoveFromFormulaTree(this);
2378 break;
2379 default:
2380 ;
2381 }
2382 }
2383
SetCompile(bool bVal)2384 void ScFormulaCell::SetCompile( bool bVal )
2385 {
2386 bCompile = bVal;
2387 }
2388
SetMatColsRows(SCCOL nCols,SCROW nRows)2389 void ScFormulaCell::SetMatColsRows( SCCOL nCols, SCROW nRows )
2390 {
2391 ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellTokenNonConst();
2392 if (pMat)
2393 pMat->SetMatColsRows( nCols, nRows );
2394 else if (nCols || nRows)
2395 {
2396 aResult.SetToken( new ScMatrixFormulaCellToken( nCols, nRows));
2397 // Setting the new token actually forces an empty result at this top
2398 // left cell, so have that recalculated.
2399 SetDirty();
2400 }
2401 }
2402
GetMatColsRows(SCCOL & nCols,SCROW & nRows) const2403 void ScFormulaCell::GetMatColsRows( SCCOL & nCols, SCROW & nRows ) const
2404 {
2405 const ScMatrixFormulaCellToken* pMat = aResult.GetMatrixFormulaCellToken();
2406 if (pMat)
2407 pMat->GetMatColsRows( nCols, nRows);
2408 else
2409 {
2410 nCols = 0;
2411 nRows = 0;
2412 }
2413 }
2414
SetInChangeTrack(bool bVal)2415 void ScFormulaCell::SetInChangeTrack( bool bVal )
2416 {
2417 bInChangeTrack = bVal;
2418 }
2419
Notify(const SfxHint & rHint)2420 void ScFormulaCell::Notify( const SfxHint& rHint )
2421 {
2422 if (rDocument.IsInDtorClear())
2423 return;
2424
2425 const SfxHintId nHint = rHint.GetId();
2426 if (nHint == SfxHintId::ScReference)
2427 {
2428 const sc::RefHint& rRefHint = static_cast<const sc::RefHint&>(rHint);
2429
2430 switch (rRefHint.getType())
2431 {
2432 case sc::RefHint::ColumnReordered:
2433 {
2434 const sc::RefColReorderHint& rRefColReorder =
2435 static_cast<const sc::RefColReorderHint&>(rRefHint);
2436 if (!IsShared() || IsSharedTop())
2437 pCode->MoveReferenceColReorder(
2438 aPos, rRefColReorder.getTab(),
2439 rRefColReorder.getStartRow(),
2440 rRefColReorder.getEndRow(),
2441 rRefColReorder.getColMap());
2442 }
2443 break;
2444 case sc::RefHint::RowReordered:
2445 {
2446 const sc::RefRowReorderHint& rRefRowReorder =
2447 static_cast<const sc::RefRowReorderHint&>(rRefHint);
2448 if (!IsShared() || IsSharedTop())
2449 pCode->MoveReferenceRowReorder(
2450 aPos, rRefRowReorder.getTab(),
2451 rRefRowReorder.getStartColumn(),
2452 rRefRowReorder.getEndColumn(),
2453 rRefRowReorder.getRowMap());
2454 }
2455 break;
2456 case sc::RefHint::StartListening:
2457 {
2458 StartListeningTo(rDocument);
2459 }
2460 break;
2461 case sc::RefHint::StopListening:
2462 {
2463 EndListeningTo(rDocument);
2464 }
2465 break;
2466 default:
2467 ;
2468 }
2469
2470 return;
2471 }
2472
2473 if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2474 return;
2475
2476 if (!(nHint == SfxHintId::ScDataChanged || nHint == SfxHintId::ScTableOpDirty || (bSubTotal && nHint == SfxHintId::ScHiddenRowsChanged)))
2477 return;
2478
2479 bool bForceTrack = false;
2480 if ( nHint == SfxHintId::ScTableOpDirty )
2481 {
2482 bForceTrack = !bTableOpDirty;
2483 if ( !bTableOpDirty )
2484 {
2485 rDocument.AddTableOpFormulaCell( this );
2486 bTableOpDirty = true;
2487 }
2488 }
2489 else
2490 {
2491 bForceTrack = !bDirty;
2492 SetDirtyVar();
2493 }
2494 // Don't remove from FormulaTree to put in FormulaTrack to
2495 // put in FormulaTree again and again, only if necessary.
2496 // Any other means except ScRecalcMode::ALWAYS by which a cell could
2497 // be in FormulaTree if it would notify other cells through
2498 // FormulaTrack which weren't in FormulaTrack/FormulaTree before?!?
2499 // Yes. The new TableOpDirty made it necessary to have a
2500 // forced mode where formulas may still be in FormulaTree from
2501 // TableOpDirty but have to notify dependents for normal dirty.
2502 if ( (bForceTrack || !rDocument.IsInFormulaTree( this )
2503 || pCode->IsRecalcModeAlways())
2504 && !rDocument.IsInFormulaTrack( this ) )
2505 rDocument.AppendToFormulaTrack( this );
2506 }
2507
Query(SvtListener::QueryBase & rQuery) const2508 void ScFormulaCell::Query( SvtListener::QueryBase& rQuery ) const
2509 {
2510 switch (rQuery.getId())
2511 {
2512 case SC_LISTENER_QUERY_FORMULA_GROUP_POS:
2513 {
2514 sc::RefQueryFormulaGroup& rRefQuery =
2515 static_cast<sc::RefQueryFormulaGroup&>(rQuery);
2516 if (IsShared())
2517 rRefQuery.add(aPos);
2518 }
2519 break;
2520 default:
2521 ;
2522 }
2523 }
2524
SetDirty(bool bDirtyFlag)2525 void ScFormulaCell::SetDirty( bool bDirtyFlag )
2526 {
2527 if (IsInChangeTrack())
2528 return;
2529
2530 if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2531 {
2532 SetDirtyVar();
2533 rDocument.SetStreamValid(aPos.Tab(), false);
2534 return;
2535 }
2536
2537 // Avoid multiple formula tracking in Load() and in CompileAll()
2538 // after CopyScenario() and CopyBlockFromClip().
2539 // If unconditional formula tracking is needed, set bDirty=false
2540 // before calling SetDirty(), for example in CompileTokenArray().
2541 if ( !bDirty || mbPostponedDirty || !rDocument.IsInFormulaTree( this ) )
2542 {
2543 if( bDirtyFlag )
2544 SetDirtyVar();
2545 rDocument.AppendToFormulaTrack( this );
2546
2547 // While loading a document listeners have not been established yet.
2548 // Tracking would remove this cell from the FormulaTrack and add it to
2549 // the FormulaTree, once in there it would be assumed that its
2550 // dependents already had been tracked and it would be skipped on a
2551 // subsequent notify. Postpone tracking until all listeners are set.
2552 if (!rDocument.IsImportingXML() && !rDocument.IsInsertingFromOtherDoc())
2553 rDocument.TrackFormulas();
2554 }
2555
2556 rDocument.SetStreamValid(aPos.Tab(), false);
2557 }
2558
SetDirtyVar()2559 void ScFormulaCell::SetDirtyVar()
2560 {
2561 bDirty = true;
2562 mbPostponedDirty = false;
2563 if (mxGroup && mxGroup->meCalcState == sc::GroupCalcRunning)
2564 {
2565 mxGroup->meCalcState = sc::GroupCalcEnabled;
2566 mxGroup->mbPartOfCycle = false;
2567 }
2568
2569 // mark the sheet of this cell to be calculated
2570 //#FIXME do we need to revert this remnant of old fake vba events? rDocument.AddCalculateTable( aPos.Tab() );
2571 }
2572
SetDirtyAfterLoad()2573 void ScFormulaCell::SetDirtyAfterLoad()
2574 {
2575 bDirty = true;
2576 if ( rDocument.GetHardRecalcState() == ScDocument::HardRecalcState::OFF )
2577 rDocument.PutInFormulaTree( this );
2578 }
2579
ResetTableOpDirtyVar()2580 void ScFormulaCell::ResetTableOpDirtyVar()
2581 {
2582 bTableOpDirty = false;
2583 }
2584
SetTableOpDirty()2585 void ScFormulaCell::SetTableOpDirty()
2586 {
2587 if ( IsInChangeTrack() )
2588 return;
2589
2590 if ( rDocument.GetHardRecalcState() != ScDocument::HardRecalcState::OFF )
2591 bTableOpDirty = true;
2592 else
2593 {
2594 if ( !bTableOpDirty || !rDocument.IsInFormulaTree( this ) )
2595 {
2596 if ( !bTableOpDirty )
2597 {
2598 rDocument.AddTableOpFormulaCell( this );
2599 bTableOpDirty = true;
2600 }
2601 rDocument.AppendToFormulaTrack( this );
2602 rDocument.TrackFormulas( SfxHintId::ScTableOpDirty );
2603 }
2604 }
2605 }
2606
SetResultDouble(double n)2607 void ScFormulaCell::SetResultDouble( double n )
2608 {
2609 aResult.SetDouble(n);
2610 }
2611
SetResultToken(const formula::FormulaToken * pToken)2612 void ScFormulaCell::SetResultToken( const formula::FormulaToken* pToken )
2613 {
2614 aResult.SetToken(pToken);
2615 }
2616
GetResultString() const2617 const svl::SharedString & ScFormulaCell::GetResultString() const
2618 {
2619 return aResult.GetString();
2620 }
2621
HasHybridStringResult() const2622 bool ScFormulaCell::HasHybridStringResult() const
2623 {
2624 return aResult.GetType() == formula::svHybridCell && !aResult.GetString().isEmpty();
2625 }
2626
SetResultMatrix(SCCOL nCols,SCROW nRows,const ScConstMatrixRef & pMat,const formula::FormulaToken * pUL)2627 void ScFormulaCell::SetResultMatrix( SCCOL nCols, SCROW nRows, const ScConstMatrixRef& pMat, const formula::FormulaToken* pUL )
2628 {
2629 aResult.SetMatrix(nCols, nRows, pMat, pUL);
2630 }
2631
SetErrCode(FormulaError n)2632 void ScFormulaCell::SetErrCode( FormulaError n )
2633 {
2634 /* FIXME: check the numerous places where ScTokenArray::GetCodeError() is
2635 * used whether it is solely for transport of a simple result error and get
2636 * rid of that abuse. */
2637 pCode->SetCodeError( n );
2638 // Hard set errors are transported as result type value per convention,
2639 // e.g. via clipboard. ScFormulaResult::IsValue() and
2640 // ScFormulaResult::GetDouble() handle that.
2641 aResult.SetResultError( n );
2642 }
2643
SetResultError(FormulaError n)2644 void ScFormulaCell::SetResultError( FormulaError n )
2645 {
2646 aResult.SetResultError( n );
2647 }
2648
AddRecalcMode(ScRecalcMode nBits)2649 void ScFormulaCell::AddRecalcMode( ScRecalcMode nBits )
2650 {
2651 if ( (nBits & ScRecalcMode::EMask) != ScRecalcMode::NORMAL )
2652 SetDirtyVar();
2653 pCode->AddRecalcMode( nBits );
2654 }
2655
SetHybridDouble(double n)2656 void ScFormulaCell::SetHybridDouble( double n )
2657 {
2658 aResult.SetHybridDouble( n);
2659 }
2660
SetHybridString(const svl::SharedString & r)2661 void ScFormulaCell::SetHybridString( const svl::SharedString& r )
2662 {
2663 aResult.SetHybridString( r);
2664 }
2665
SetHybridEmptyDisplayedAsString()2666 void ScFormulaCell::SetHybridEmptyDisplayedAsString()
2667 {
2668 aResult.SetHybridEmptyDisplayedAsString();
2669 }
2670
SetHybridFormula(const OUString & r,const formula::FormulaGrammar::Grammar eGrammar)2671 void ScFormulaCell::SetHybridFormula( const OUString& r,
2672 const formula::FormulaGrammar::Grammar eGrammar )
2673 {
2674 aResult.SetHybridFormula( r); eTempGrammar = eGrammar;
2675 }
2676
GetHybridFormula() const2677 OUString ScFormulaCell::GetHybridFormula() const
2678 {
2679 return aResult.GetHybridFormula();
2680 }
2681
2682 // Dynamically create the URLField on a mouse-over action on a hyperlink() cell.
GetURLResult(OUString & rURL,OUString & rCellText)2683 void ScFormulaCell::GetURLResult( OUString& rURL, OUString& rCellText )
2684 {
2685 OUString aCellString;
2686
2687 const Color* pColor;
2688
2689 // Cell Text uses the Cell format while the URL uses
2690 // the default format for the type.
2691 const sal_uInt32 nCellFormat = rDocument.GetNumberFormat( aPos );
2692 ScInterpreterContext& rContext = rDocument.GetNonThreadedContext();
2693
2694 const sal_uInt32 nURLFormat = ScGlobal::GetStandardFormat(rContext, nCellFormat, SvNumFormatType::NUMBER);
2695
2696 if ( IsValue() )
2697 {
2698 double fValue = GetValue();
2699 rContext.NFGetOutputString( fValue, nCellFormat, rCellText, &pColor );
2700 }
2701 else
2702 {
2703 aCellString = GetString().getString();
2704 rContext.NFGetOutputString( aCellString, nCellFormat, rCellText, &pColor );
2705 }
2706 ScConstMatrixRef xMat( aResult.GetMatrix());
2707 if (xMat)
2708 {
2709 // determine if the matrix result is a string or value.
2710 if (!xMat->IsValue(0, 1))
2711 rURL = xMat->GetString(0, 1).getString();
2712 else
2713 rContext.NFGetOutputString(
2714 xMat->GetDouble(0, 1), nURLFormat, rURL, &pColor);
2715 }
2716
2717 if(rURL.isEmpty())
2718 {
2719 if(IsValue())
2720 rContext.NFGetOutputString( GetValue(), nURLFormat, rURL, &pColor );
2721 else
2722 rContext.NFGetOutputString( aCellString, nURLFormat, rURL, &pColor );
2723 }
2724 }
2725
IsMultilineResult()2726 bool ScFormulaCell::IsMultilineResult()
2727 {
2728 if (!IsValue())
2729 return aResult.IsMultiline();
2730 return false;
2731 }
2732
IsHyperLinkCell() const2733 bool ScFormulaCell::IsHyperLinkCell() const
2734 {
2735 return pCode && pCode->IsHyperLink();
2736 }
2737
CreateURLObject()2738 std::unique_ptr<EditTextObject> ScFormulaCell::CreateURLObject()
2739 {
2740 OUString aCellText;
2741 OUString aURL;
2742 GetURLResult( aURL, aCellText );
2743
2744 return ScEditUtil::CreateURLObjectFromURL( rDocument, aURL, aCellText );
2745 }
2746
IsEmpty()2747 bool ScFormulaCell::IsEmpty()
2748 {
2749 MaybeInterpret();
2750 return aResult.GetCellResultType() == formula::svEmptyCell;
2751 }
2752
IsEmptyDisplayedAsString()2753 bool ScFormulaCell::IsEmptyDisplayedAsString()
2754 {
2755 MaybeInterpret();
2756 return aResult.IsEmptyDisplayedAsString();
2757 }
2758
IsValue()2759 bool ScFormulaCell::IsValue()
2760 {
2761 MaybeInterpret();
2762 return aResult.IsValue();
2763 }
2764
IsValueNoError()2765 bool ScFormulaCell::IsValueNoError()
2766 {
2767 MaybeInterpret();
2768 if (pCode->GetCodeError() != FormulaError::NONE)
2769 return false;
2770
2771 return aResult.IsValueNoError();
2772 }
2773
IsValueNoError() const2774 bool ScFormulaCell::IsValueNoError() const
2775 {
2776 if (NeedsInterpret())
2777 // false if the cell is dirty & needs to be interpreted.
2778 return false;
2779
2780 if (pCode->GetCodeError() != FormulaError::NONE)
2781 return false;
2782
2783 return aResult.IsValueNoError();
2784 }
2785
GetValue()2786 double ScFormulaCell::GetValue()
2787 {
2788 MaybeInterpret();
2789 return GetRawValue();
2790 }
2791
GetString()2792 const svl::SharedString & ScFormulaCell::GetString()
2793 {
2794 MaybeInterpret();
2795 return GetRawString();
2796 }
2797
GetRawValue() const2798 double ScFormulaCell::GetRawValue() const
2799 {
2800 if ((pCode->GetCodeError() == FormulaError::NONE) &&
2801 aResult.GetResultError() == FormulaError::NONE)
2802 return aResult.GetDouble();
2803 return 0.0;
2804 }
2805
GetRawString() const2806 const svl::SharedString & ScFormulaCell::GetRawString() const
2807 {
2808 if ((pCode->GetCodeError() == FormulaError::NONE) &&
2809 aResult.GetResultError() == FormulaError::NONE)
2810 return aResult.GetString();
2811
2812 return svl::SharedString::getEmptyString();
2813 }
2814
GetMatrix()2815 const ScMatrix* ScFormulaCell::GetMatrix()
2816 {
2817 if ( rDocument.GetAutoCalc() )
2818 {
2819 if( IsDirtyOrInTableOpDirty()
2820 // Was stored !bDirty but an accompanying matrix cell was bDirty?
2821 || (!bDirty && cMatrixFlag == ScMatrixMode::Formula && !aResult.GetMatrix()))
2822 Interpret();
2823 }
2824 return aResult.GetMatrix().get();
2825 }
2826
GetMatrixOrigin(const ScDocument & rDoc,ScAddress & rPos) const2827 bool ScFormulaCell::GetMatrixOrigin( const ScDocument& rDoc, ScAddress& rPos ) const
2828 {
2829 switch ( cMatrixFlag )
2830 {
2831 case ScMatrixMode::Formula :
2832 rPos = aPos;
2833 return true;
2834 case ScMatrixMode::Reference :
2835 {
2836 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
2837 formula::FormulaToken* t = aIter.GetNextReferenceRPN();
2838 if( t )
2839 {
2840 ScSingleRefData& rRef = *t->GetSingleRef();
2841 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
2842 if (rDoc.ValidAddress(aAbs))
2843 {
2844 rPos = aAbs;
2845 return true;
2846 }
2847 }
2848 }
2849 break;
2850 default: break;
2851 }
2852 return false;
2853 }
2854
GetMatrixEdge(const ScDocument & rDoc,ScAddress & rOrgPos) const2855 sc::MatrixEdge ScFormulaCell::GetMatrixEdge( const ScDocument& rDoc, ScAddress& rOrgPos ) const
2856 {
2857 switch ( cMatrixFlag )
2858 {
2859 case ScMatrixMode::Formula :
2860 case ScMatrixMode::Reference :
2861 {
2862 static thread_local SCCOL nC;
2863 static thread_local SCROW nR;
2864 ScAddress aOrg;
2865 if ( !GetMatrixOrigin( rDoc, aOrg ) )
2866 return sc::MatrixEdge::Nothing;
2867 if ( aOrg != rOrgPos )
2868 { // First time or a different matrix than last time.
2869 rOrgPos = aOrg;
2870 const ScFormulaCell* pFCell;
2871 if ( cMatrixFlag == ScMatrixMode::Reference )
2872 pFCell = rDocument.GetFormulaCell(aOrg);
2873 else
2874 pFCell = this; // this ScMatrixMode::Formula
2875 // There's only one this, don't compare pFCell==this.
2876 if (pFCell && pFCell->cMatrixFlag == ScMatrixMode::Formula)
2877 {
2878 pFCell->GetMatColsRows( nC, nR );
2879 if ( nC == 0 || nR == 0 )
2880 {
2881 // No ScMatrixFormulaCellToken available yet, calculate new.
2882 nC = 1;
2883 nR = 1;
2884 ScAddress aTmpOrg;
2885 ScFormulaCell* pCell;
2886 ScAddress aAdr( aOrg );
2887 aAdr.IncCol();
2888 bool bCont = true;
2889 do
2890 {
2891 pCell = rDocument.GetFormulaCell(aAdr);
2892 if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
2893 pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
2894 {
2895 nC++;
2896 aAdr.IncCol();
2897 }
2898 else
2899 bCont = false;
2900 } while ( bCont );
2901 aAdr = aOrg;
2902 aAdr.IncRow();
2903 bCont = true;
2904 do
2905 {
2906 pCell = rDocument.GetFormulaCell(aAdr);
2907 if (pCell && pCell->cMatrixFlag == ScMatrixMode::Reference &&
2908 pCell->GetMatrixOrigin(rDocument, aTmpOrg) && aTmpOrg == aOrg)
2909 {
2910 nR++;
2911 aAdr.IncRow();
2912 }
2913 else
2914 bCont = false;
2915 } while ( bCont );
2916
2917 const_cast<ScFormulaCell*>(pFCell)->SetMatColsRows(nC, nR);
2918 }
2919 }
2920 else
2921 {
2922 #if OSL_DEBUG_LEVEL > 0
2923 SAL_WARN( "sc", "broken Matrix, no MatFormula at origin, Pos: "
2924 << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2925 << ", MatOrg: "
2926 << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument) );
2927 #endif
2928 return sc::MatrixEdge::Nothing;
2929 }
2930 }
2931 // here we are, healthy and clean, somewhere in between
2932 SCCOL dC = aPos.Col() - aOrg.Col();
2933 SCROW dR = aPos.Row() - aOrg.Row();
2934 sc::MatrixEdge nEdges = sc::MatrixEdge::Nothing;
2935 if ( dC >= 0 && dR >= 0 && dC < nC && dR < nR )
2936 {
2937 if ( dC == 0 )
2938 nEdges |= sc::MatrixEdge::Left;
2939 if ( dC+1 == nC )
2940 nEdges |= sc::MatrixEdge::Right;
2941 if ( dR == 0 )
2942 nEdges |= sc::MatrixEdge::Top;
2943 if ( dR+1 == nR )
2944 nEdges |= sc::MatrixEdge::Bottom;
2945 if ( nEdges == sc::MatrixEdge::Nothing )
2946 nEdges = sc::MatrixEdge::Inside;
2947 }
2948 else
2949 {
2950 SAL_WARN( "sc", "broken Matrix, Pos: "
2951 << aPos.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2952 << ", MatOrg: "
2953 << aOrg.Format(ScRefFlags::COL_VALID | ScRefFlags::ROW_VALID, &rDocument)
2954 << ", MatCols: " << static_cast<sal_Int32>( nC )
2955 << ", MatRows: " << static_cast<sal_Int32>( nR )
2956 << ", DiffCols: " << static_cast<sal_Int32>( dC )
2957 << ", DiffRows: " << static_cast<sal_Int32>( dR ));
2958 }
2959 return nEdges;
2960 }
2961 default:
2962 return sc::MatrixEdge::Nothing;
2963 }
2964 }
2965
GetErrCode()2966 FormulaError ScFormulaCell::GetErrCode()
2967 {
2968 MaybeInterpret();
2969
2970 /* FIXME: If ScTokenArray::SetCodeError() was really only for code errors
2971 * and not also abused for signaling other error conditions we could bail
2972 * out even before attempting to interpret broken code. */
2973 FormulaError nErr = pCode->GetCodeError();
2974 if (nErr != FormulaError::NONE)
2975 return nErr;
2976 return aResult.GetResultError();
2977 }
2978
GetRawError() const2979 FormulaError ScFormulaCell::GetRawError() const
2980 {
2981 FormulaError nErr = pCode->GetCodeError();
2982 if (nErr != FormulaError::NONE)
2983 return nErr;
2984 return aResult.GetResultError();
2985 }
2986
GetErrorOrValue(FormulaError & rErr,double & rVal)2987 bool ScFormulaCell::GetErrorOrValue( FormulaError& rErr, double& rVal )
2988 {
2989 MaybeInterpret();
2990
2991 rErr = pCode->GetCodeError();
2992 if (rErr != FormulaError::NONE)
2993 return true;
2994
2995 return aResult.GetErrorOrDouble(rErr, rVal);
2996 }
2997
GetResult()2998 sc::FormulaResultValue ScFormulaCell::GetResult()
2999 {
3000 MaybeInterpret();
3001
3002 FormulaError nErr = pCode->GetCodeError();
3003 if (nErr != FormulaError::NONE)
3004 return sc::FormulaResultValue(nErr);
3005
3006 return aResult.GetResult();
3007 }
3008
GetResult() const3009 sc::FormulaResultValue ScFormulaCell::GetResult() const
3010 {
3011 FormulaError nErr = pCode->GetCodeError();
3012 if (nErr != FormulaError::NONE)
3013 return sc::FormulaResultValue(nErr);
3014
3015 return aResult.GetResult();
3016 }
3017
HasOneReference(ScRange & r) const3018 bool ScFormulaCell::HasOneReference( ScRange& r ) const
3019 {
3020 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3021 formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3022 if( p && !aIter.GetNextReferenceRPN() ) // only one!
3023 {
3024 SingleDoubleRefProvider aProv( *p );
3025 r.aStart = aProv.Ref1.toAbs(rDocument, aPos);
3026 r.aEnd = aProv.Ref2.toAbs(rDocument, aPos);
3027 return true;
3028 }
3029 else
3030 return false;
3031 }
3032
3033 bool
HasRefListExpressibleAsOneReference(ScRange & rRange) const3034 ScFormulaCell::HasRefListExpressibleAsOneReference(ScRange& rRange) const
3035 {
3036 /* If there appears just one reference in the formula, it's the same
3037 as HasOneReference(). If there are more of them, they can denote
3038 one range if they are (sole) arguments of one function.
3039 Union of these references must form one range and their
3040 intersection must be empty set.
3041 */
3042
3043 // Detect the simple case of exactly one reference in advance without all
3044 // overhead.
3045 // #i107741# Doing so actually makes outlines using SUBTOTAL(x;reference)
3046 // work again, where the function does not have only references.
3047 if (HasOneReference( rRange))
3048 return true;
3049
3050 // Get first reference, if any
3051 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3052 formula::FormulaToken* const pFirstReference(aIter.GetNextReferenceRPN());
3053 if (pFirstReference)
3054 {
3055 // Collect all consecutive references, starting by the one
3056 // already found
3057 std::vector<formula::FormulaToken*> aReferences { pFirstReference };
3058 FormulaToken* pToken(aIter.NextRPN());
3059 FormulaToken* pFunction(nullptr);
3060 while (pToken)
3061 {
3062 if (lcl_isReference(*pToken))
3063 {
3064 aReferences.push_back(pToken);
3065 pToken = aIter.NextRPN();
3066 }
3067 else
3068 {
3069 if (pToken->IsFunction())
3070 {
3071 pFunction = pToken;
3072 }
3073 break;
3074 }
3075 }
3076 if (pFunction && !aIter.GetNextReferenceRPN()
3077 && (pFunction->GetParamCount() == aReferences.size()))
3078 {
3079 return lcl_refListFormsOneRange(rDocument, aPos, aReferences, rRange);
3080 }
3081 }
3082 return false;
3083 }
3084
HasRelNameReference() const3085 ScFormulaCell::RelNameRef ScFormulaCell::HasRelNameReference() const
3086 {
3087 RelNameRef eRelNameRef = RelNameRef::NONE;
3088 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3089 formula::FormulaToken* t;
3090 while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
3091 {
3092 switch (t->GetType())
3093 {
3094 case formula::svSingleRef:
3095 if (t->GetSingleRef()->IsRelName() && eRelNameRef == RelNameRef::NONE)
3096 eRelNameRef = RelNameRef::SINGLE;
3097 break;
3098 case formula::svDoubleRef:
3099 if (t->GetDoubleRef()->Ref1.IsRelName() || t->GetDoubleRef()->Ref2.IsRelName())
3100 // May originate from individual cell names, in which case
3101 // it needs recompilation.
3102 return RelNameRef::DOUBLE;
3103 /* TODO: have an extra flag at ScComplexRefData if range was
3104 * extended? or too cumbersome? might narrow recompilation to
3105 * only needed cases.
3106 * */
3107 break;
3108 default:
3109 ; // nothing
3110 }
3111 }
3112 return eRelNameRef;
3113 }
3114
UpdatePosOnShift(const sc::RefUpdateContext & rCxt)3115 bool ScFormulaCell::UpdatePosOnShift( const sc::RefUpdateContext& rCxt )
3116 {
3117 if (rCxt.meMode != URM_INSDEL)
3118 // Just in case...
3119 return false;
3120
3121 if (!rCxt.mnColDelta && !rCxt.mnRowDelta && !rCxt.mnTabDelta)
3122 // No movement.
3123 return false;
3124
3125 if (!rCxt.maRange.Contains(aPos))
3126 return false;
3127
3128 // This formula cell itself is being shifted during cell range
3129 // insertion or deletion. Update its position.
3130 ScAddress aErrorPos( ScAddress::UNINITIALIZED );
3131 if (!aPos.Move(rCxt.mnColDelta, rCxt.mnRowDelta, rCxt.mnTabDelta, aErrorPos, rCxt.mrDoc))
3132 {
3133 assert(!"can't move ScFormulaCell");
3134 }
3135
3136 return true;
3137 }
3138
3139 namespace {
3140
3141 /**
3142 * Check if we need to re-compile column or row names.
3143 */
checkCompileColRowName(const sc::RefUpdateContext & rCxt,ScDocument & rDoc,const ScTokenArray & rCode,const ScAddress & aOldPos,const ScAddress & aPos,bool bValChanged)3144 bool checkCompileColRowName(
3145 const sc::RefUpdateContext& rCxt, ScDocument& rDoc, const ScTokenArray& rCode,
3146 const ScAddress& aOldPos, const ScAddress& aPos, bool bValChanged)
3147 {
3148 switch (rCxt.meMode)
3149 {
3150 case URM_INSDEL:
3151 {
3152 if (rCxt.mnColDelta <= 0 && rCxt.mnRowDelta <= 0)
3153 return false;
3154
3155 formula::FormulaTokenArrayPlainIterator aIter(rCode);
3156 formula::FormulaToken* t;
3157 ScRangePairList* pColList = rDoc.GetColNameRanges();
3158 ScRangePairList* pRowList = rDoc.GetRowNameRanges();
3159 while ((t = aIter.GetNextColRowName()) != nullptr)
3160 {
3161 ScSingleRefData& rRef = *t->GetSingleRef();
3162 if (rCxt.mnRowDelta > 0 && rRef.IsColRel())
3163 { // ColName
3164 ScAddress aAdr = rRef.toAbs(rDoc, aPos);
3165 ScRangePair* pR = pColList->Find( aAdr );
3166 if ( pR )
3167 { // defined
3168 if (pR->GetRange(1).aStart.Row() == rCxt.maRange.aStart.Row())
3169 return true;
3170 }
3171 else
3172 { // on the fly
3173 if (aAdr.Row() + 1 == rCxt.maRange.aStart.Row())
3174 return true;
3175 }
3176 }
3177 if (rCxt.mnColDelta > 0 && rRef.IsRowRel())
3178 { // RowName
3179 ScAddress aAdr = rRef.toAbs(rDoc, aPos);
3180 ScRangePair* pR = pRowList->Find( aAdr );
3181 if ( pR )
3182 { // defined
3183 if ( pR->GetRange(1).aStart.Col() == rCxt.maRange.aStart.Col())
3184 return true;
3185 }
3186 else
3187 { // on the fly
3188 if (aAdr.Col() + 1 == rCxt.maRange.aStart.Col())
3189 return true;
3190 }
3191 }
3192 }
3193 }
3194 break;
3195 case URM_MOVE:
3196 { // Recompile for Move/D&D when ColRowName was moved or this Cell
3197 // points to one and was moved.
3198 bool bMoved = (aPos != aOldPos);
3199 if (bMoved)
3200 return true;
3201
3202 formula::FormulaTokenArrayPlainIterator aIter(rCode);
3203 const formula::FormulaToken* t = aIter.GetNextColRowName();
3204 for (; t; t = aIter.GetNextColRowName())
3205 {
3206 const ScSingleRefData& rRef = *t->GetSingleRef();
3207 ScAddress aAbs = rRef.toAbs(rDoc, aPos);
3208 if (rDoc.ValidAddress(aAbs))
3209 {
3210 if (rCxt.maRange.Contains(aAbs))
3211 return true;
3212 }
3213 }
3214 }
3215 break;
3216 case URM_COPY:
3217 return bValChanged;
3218 default:
3219 ;
3220 }
3221
3222 return false;
3223 }
3224
setOldCodeToUndo(ScDocument & rUndoDoc,const ScAddress & aUndoPos,const ScTokenArray * pOldCode,FormulaGrammar::Grammar eTempGrammar,ScMatrixMode cMatrixFlag)3225 void setOldCodeToUndo(
3226 ScDocument& rUndoDoc, const ScAddress& aUndoPos, const ScTokenArray* pOldCode, FormulaGrammar::Grammar eTempGrammar, ScMatrixMode cMatrixFlag)
3227 {
3228 // Copy the cell to aUndoPos, which is its current position in the document,
3229 // so this works when UpdateReference is called before moving the cells
3230 // (InsertCells/DeleteCells - aPos is changed above) as well as when UpdateReference
3231 // is called after moving the cells (MoveBlock/PasteFromClip - aOldPos is changed).
3232
3233 // If there is already a formula cell in the undo document, don't overwrite it,
3234 // the first (oldest) is the important cell.
3235 if (rUndoDoc.GetCellType(aUndoPos) == CELLTYPE_FORMULA)
3236 return;
3237
3238 ScFormulaCell* pFCell =
3239 new ScFormulaCell(
3240 rUndoDoc, aUndoPos, pOldCode ? *pOldCode : ScTokenArray(rUndoDoc), eTempGrammar, cMatrixFlag);
3241
3242 pFCell->SetResultToken(nullptr); // to recognize it as changed later (Cut/Paste!)
3243 rUndoDoc.SetFormulaCell(aUndoPos, pFCell);
3244 }
3245
3246 }
3247
UpdateReferenceOnShift(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3248 bool ScFormulaCell::UpdateReferenceOnShift(
3249 const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3250 {
3251 if (rCxt.meMode != URM_INSDEL)
3252 // Just in case...
3253 return false;
3254
3255 bool bCellStateChanged = false;
3256 ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
3257 if ( pUndoCellPos )
3258 aUndoPos = *pUndoCellPos;
3259 ScAddress aOldPos( aPos );
3260 bCellStateChanged = UpdatePosOnShift(rCxt);
3261
3262 // Check presence of any references or column row names.
3263 bool bHasRefs = pCode->HasReferences();
3264 bool bHasColRowNames = false;
3265 if (!bHasRefs)
3266 {
3267 bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3268 bHasRefs = bHasColRowNames;
3269 }
3270 bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3271
3272 if (!bHasRefs && !bOnRefMove)
3273 // This formula cell contains no references, nor needs recalculating
3274 // on reference update. Bail out.
3275 return bCellStateChanged;
3276
3277 std::unique_ptr<ScTokenArray> pOldCode;
3278 if (pUndoDoc)
3279 pOldCode = pCode->Clone();
3280
3281 bool bValChanged = false;
3282 bool bRefModified = false;
3283 bool bRecompile = bCompile;
3284
3285 if (bHasRefs)
3286 {
3287 // Update cell or range references.
3288 sc::RefUpdateResult aRes = pCode->AdjustReferenceOnShift(rCxt, aOldPos);
3289 bRefModified = aRes.mbReferenceModified;
3290 bValChanged = aRes.mbValueChanged;
3291 if (aRes.mbNameModified)
3292 bRecompile = true;
3293 }
3294
3295 if (bValChanged || bRefModified)
3296 bCellStateChanged = true;
3297
3298 if (bOnRefMove)
3299 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3300 bOnRefMove = (bValChanged || (aPos != aOldPos) || bRefModified);
3301
3302 bool bNewListening = false;
3303 bool bInDeleteUndo = false;
3304
3305 if (bHasRefs)
3306 {
3307 // Upon Insert ColRowNames have to be recompiled in case the
3308 // insertion occurs right in front of the range.
3309 if (bHasColRowNames && !bRecompile)
3310 bRecompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
3311
3312 ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
3313 bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
3314
3315 // RelNameRefs are always moved
3316 bool bHasRelName = false;
3317 if (!bRecompile)
3318 {
3319 RelNameRef eRelNameRef = HasRelNameReference();
3320 bHasRelName = (eRelNameRef != RelNameRef::NONE);
3321 bRecompile = (eRelNameRef == RelNameRef::DOUBLE);
3322 }
3323 // Reference changed and new listening needed?
3324 // Except in Insert/Delete without specialities.
3325 bNewListening = (bRefModified || bRecompile
3326 || (bValChanged && bInDeleteUndo) || bHasRelName);
3327
3328 if ( bNewListening )
3329 EndListeningTo(rDocument, pOldCode.get(), aOldPos);
3330 }
3331
3332 // NeedDirty for changes except for Copy and Move/Insert without RelNames
3333 bool bNeedDirty = (bValChanged || bRecompile || bOnRefMove);
3334
3335 if (pUndoDoc && (bValChanged || bOnRefMove))
3336 setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3337
3338 bCompile |= bRecompile;
3339 if (bCompile)
3340 {
3341 CompileTokenArray( bNewListening ); // no Listening
3342 bNeedDirty = true;
3343 }
3344
3345 if ( !bInDeleteUndo )
3346 { // In ChangeTrack Delete-Reject listeners are established in
3347 // InsertCol/InsertRow
3348 if ( bNewListening )
3349 {
3350 // Inserts/Deletes re-establish listeners after all
3351 // UpdateReference calls.
3352 // All replaced shared formula listeners have to be
3353 // established after an Insert or Delete. Do nothing here.
3354 SetNeedsListening( true);
3355 }
3356 }
3357
3358 if (bNeedDirty)
3359 { // Cut off references, invalid or similar?
3360 // Postpone SetDirty() until all listeners have been re-established in
3361 // Inserts/Deletes.
3362 mbPostponedDirty = true;
3363 }
3364
3365 return bCellStateChanged;
3366 }
3367
UpdateReferenceOnMove(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3368 bool ScFormulaCell::UpdateReferenceOnMove(
3369 const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3370 {
3371 if (rCxt.meMode != URM_MOVE)
3372 return false;
3373
3374 ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
3375 if ( pUndoCellPos )
3376 aUndoPos = *pUndoCellPos;
3377 ScAddress aOldPos( aPos );
3378
3379 bool bCellInMoveTarget = rCxt.maRange.Contains(aPos);
3380
3381 if ( bCellInMoveTarget )
3382 {
3383 // The cell is being moved or copied to a new position. I guess the
3384 // position has been updated prior to this call? Determine
3385 // its original position before the move which will be used to adjust
3386 // relative references later.
3387 aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
3388 }
3389
3390 // Check presence of any references or column row names.
3391 bool bHasRefs = pCode->HasReferences();
3392 bool bHasColRowNames = false;
3393 if (!bHasRefs)
3394 {
3395 bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3396 bHasRefs = bHasColRowNames;
3397 }
3398 bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3399
3400 if (!bHasRefs && !bOnRefMove)
3401 // This formula cell contains no references, nor needs recalculating
3402 // on reference update. Bail out.
3403 return false;
3404
3405 bool bCellStateChanged = false;
3406 std::unique_ptr<ScTokenArray> pOldCode;
3407 if (pUndoDoc)
3408 pOldCode = pCode->Clone();
3409
3410 bool bValChanged = false;
3411 bool bRefModified = false;
3412
3413 if (bHasRefs)
3414 {
3415 // Update cell or range references.
3416 sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMove(rCxt, aOldPos, aPos);
3417 bRefModified = aRes.mbReferenceModified || aRes.mbNameModified;
3418 bValChanged = aRes.mbValueChanged;
3419 if (aRes.mbNameModified)
3420 // Re-compile to get the RPN token regenerated to reflect updated names.
3421 bCompile = true;
3422 }
3423
3424 if (bValChanged || bRefModified)
3425 bCellStateChanged = true;
3426
3427 if (bOnRefMove)
3428 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3429 bOnRefMove = (bValChanged || (aPos != aOldPos));
3430
3431 bool bColRowNameCompile = false;
3432 bool bHasRelName = false;
3433 bool bNewListening = false;
3434 bool bInDeleteUndo = false;
3435
3436 if (bHasRefs)
3437 {
3438 // Upon Insert ColRowNames have to be recompiled in case the
3439 // insertion occurs right in front of the range.
3440 if (bHasColRowNames)
3441 bColRowNameCompile = checkCompileColRowName(rCxt, rDocument, *pCode, aOldPos, aPos, bValChanged);
3442
3443 ScChangeTrack* pChangeTrack = rDocument.GetChangeTrack();
3444 bInDeleteUndo = (pChangeTrack && pChangeTrack->IsInDeleteUndo());
3445
3446 // RelNameRefs are always moved
3447 RelNameRef eRelNameRef = HasRelNameReference();
3448 bHasRelName = (eRelNameRef != RelNameRef::NONE);
3449 bCompile |= (eRelNameRef == RelNameRef::DOUBLE);
3450 // Reference changed and new listening needed?
3451 // Except in Insert/Delete without specialties.
3452 bNewListening = (bRefModified || bColRowNameCompile
3453 || bValChanged || bHasRelName)
3454 // #i36299# Don't duplicate action during cut&paste / drag&drop
3455 // on a cell in the range moved, start/end listeners is done
3456 // via ScDocument::DeleteArea() and ScDocument::CopyFromClip().
3457 && !(rDocument.IsInsertingFromOtherDoc() && rCxt.maRange.Contains(aPos));
3458
3459 if ( bNewListening )
3460 EndListeningTo(rDocument, pOldCode.get(), aOldPos);
3461 }
3462
3463 bool bNeedDirty = false;
3464 // NeedDirty for changes except for Copy and Move/Insert without RelNames
3465 if ( bRefModified || bColRowNameCompile ||
3466 (bValChanged && bHasRelName ) || bOnRefMove)
3467 bNeedDirty = true;
3468
3469 if (pUndoDoc && !bCellInMoveTarget && (bValChanged || bRefModified || bOnRefMove))
3470 setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3471
3472 bValChanged = false;
3473
3474 bCompile = (bCompile || bValChanged || bColRowNameCompile);
3475 if ( bCompile )
3476 {
3477 CompileTokenArray( bNewListening ); // no Listening
3478 bNeedDirty = true;
3479 }
3480
3481 if ( !bInDeleteUndo )
3482 { // In ChangeTrack Delete-Reject listeners are established in
3483 // InsertCol/InsertRow
3484 if ( bNewListening )
3485 {
3486 StartListeningTo( rDocument );
3487 }
3488 }
3489
3490 if (bNeedDirty)
3491 { // Cut off references, invalid or similar?
3492 sc::AutoCalcSwitch aACSwitch(rDocument, false);
3493 SetDirty();
3494 }
3495
3496 return bCellStateChanged;
3497 }
3498
UpdateReferenceOnCopy(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3499 bool ScFormulaCell::UpdateReferenceOnCopy(
3500 const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3501 {
3502 if (rCxt.meMode != URM_COPY)
3503 return false;
3504
3505 ScAddress aUndoPos( aPos ); // position for undo cell in pUndoDoc
3506 if ( pUndoCellPos )
3507 aUndoPos = *pUndoCellPos;
3508 ScAddress aOldPos( aPos );
3509
3510 if (rCxt.maRange.Contains(aPos))
3511 {
3512 // The cell is being moved or copied to a new position. I guess the
3513 // position has been updated prior to this call? Determine
3514 // its original position before the move which will be used to adjust
3515 // relative references later.
3516 aOldPos.Set(aPos.Col() - rCxt.mnColDelta, aPos.Row() - rCxt.mnRowDelta, aPos.Tab() - rCxt.mnTabDelta);
3517 }
3518
3519 // Check presence of any references or column row names.
3520 bool bHasRefs = pCode->HasReferences();
3521 bool bHasColRowNames = (formula::FormulaTokenArrayPlainIterator(*pCode).GetNextColRowName() != nullptr);
3522 bHasRefs = bHasRefs || bHasColRowNames;
3523 bool bOnRefMove = pCode->IsRecalcModeOnRefMove();
3524
3525 if (!bHasRefs && !bOnRefMove)
3526 // This formula cell contains no references, nor needs recalculating
3527 // on reference update. Bail out.
3528 return false;
3529
3530 std::unique_ptr<ScTokenArray> pOldCode;
3531 if (pUndoDoc)
3532 pOldCode = pCode->Clone();
3533
3534 if (bOnRefMove)
3535 // Cell may reference itself, e.g. ocColumn, ocRow without parameter
3536 bOnRefMove = (aPos != aOldPos);
3537
3538 bool bNeedDirty = bOnRefMove;
3539
3540 if (pUndoDoc && bOnRefMove)
3541 setOldCodeToUndo(*pUndoDoc, aUndoPos, pOldCode.get(), eTempGrammar, cMatrixFlag);
3542
3543 if (bCompile)
3544 {
3545 CompileTokenArray(); // no Listening
3546 bNeedDirty = true;
3547 }
3548
3549 if (bNeedDirty)
3550 { // Cut off references, invalid or similar?
3551 sc::AutoCalcSwitch aACSwitch(rDocument, false);
3552 SetDirty();
3553 }
3554
3555 return false;
3556 }
3557
UpdateReference(const sc::RefUpdateContext & rCxt,ScDocument * pUndoDoc,const ScAddress * pUndoCellPos)3558 bool ScFormulaCell::UpdateReference(
3559 const sc::RefUpdateContext& rCxt, ScDocument* pUndoDoc, const ScAddress* pUndoCellPos )
3560 {
3561 if (rDocument.IsClipOrUndo())
3562 return false;
3563
3564 if (mxGroup && mxGroup->mpTopCell != this)
3565 {
3566 // This is not a top cell of a formula group. Don't update references.
3567
3568 switch (rCxt.meMode)
3569 {
3570 case URM_INSDEL:
3571 return UpdatePosOnShift(rCxt);
3572 default:
3573 ;
3574 }
3575 return false;
3576 }
3577
3578 switch (rCxt.meMode)
3579 {
3580 case URM_INSDEL:
3581 return UpdateReferenceOnShift(rCxt, pUndoDoc, pUndoCellPos);
3582 case URM_MOVE:
3583 return UpdateReferenceOnMove(rCxt, pUndoDoc, pUndoCellPos);
3584 case URM_COPY:
3585 return UpdateReferenceOnCopy(rCxt, pUndoDoc, pUndoCellPos);
3586 default:
3587 ;
3588 }
3589
3590 return false;
3591 }
3592
UpdateInsertTab(const sc::RefUpdateInsertTabContext & rCxt)3593 void ScFormulaCell::UpdateInsertTab( const sc::RefUpdateInsertTabContext& rCxt )
3594 {
3595 // Adjust tokens only when it's not grouped or grouped top cell.
3596 bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3597 bool bPosChanged = (rCxt.mnInsertPos <= aPos.Tab());
3598 if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
3599 {
3600 if (bPosChanged)
3601 aPos.IncTab(rCxt.mnSheets);
3602
3603 return;
3604 }
3605
3606 EndListeningTo( rDocument );
3607 ScAddress aOldPos = aPos;
3608 // IncTab _after_ EndListeningTo and _before_ Compiler UpdateInsertTab!
3609 if (bPosChanged)
3610 aPos.IncTab(rCxt.mnSheets);
3611
3612 if (!bAdjustCode)
3613 return;
3614
3615 sc::RefUpdateResult aRes = pCode->AdjustReferenceOnInsertedTab(rCxt, aOldPos);
3616 if (aRes.mbNameModified)
3617 // Re-compile after new sheet(s) have been inserted.
3618 bCompile = true;
3619
3620 // no StartListeningTo because the new sheets have not been inserted yet.
3621 }
3622
UpdateDeleteTab(const sc::RefUpdateDeleteTabContext & rCxt)3623 void ScFormulaCell::UpdateDeleteTab( const sc::RefUpdateDeleteTabContext& rCxt )
3624 {
3625 // Adjust tokens only when it's not grouped or grouped top cell.
3626 bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3627 bool bPosChanged = (aPos.Tab() >= rCxt.mnDeletePos + rCxt.mnSheets);
3628 if (rDocument.IsClipOrUndo() || !pCode->HasReferences())
3629 {
3630 if (bPosChanged)
3631 aPos.IncTab(-1*rCxt.mnSheets);
3632 return;
3633 }
3634
3635 EndListeningTo( rDocument );
3636 // IncTab _after_ EndListeningTo and _before_ Compiler UpdateDeleteTab!
3637 ScAddress aOldPos = aPos;
3638 if (bPosChanged)
3639 aPos.IncTab(-1*rCxt.mnSheets);
3640
3641 if (!bAdjustCode)
3642 return;
3643
3644 sc::RefUpdateResult aRes = pCode->AdjustReferenceOnDeletedTab(rCxt, aOldPos);
3645 if (aRes.mbNameModified)
3646 // Re-compile after sheet(s) have been deleted.
3647 bCompile = true;
3648 }
3649
UpdateMoveTab(const sc::RefUpdateMoveTabContext & rCxt,SCTAB nTabNo)3650 void ScFormulaCell::UpdateMoveTab( const sc::RefUpdateMoveTabContext& rCxt, SCTAB nTabNo )
3651 {
3652 // Adjust tokens only when it's not grouped or grouped top cell.
3653 bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3654
3655 if (!pCode->HasReferences() || rDocument.IsClipOrUndo())
3656 {
3657 aPos.SetTab(nTabNo);
3658 return;
3659 }
3660
3661 EndListeningTo(rDocument);
3662 ScAddress aOldPos = aPos;
3663 // SetTab _after_ EndListeningTo and _before_ Compiler UpdateMoveTab !
3664 aPos.SetTab(nTabNo);
3665
3666 // no StartListeningTo because pTab[nTab] not yet correct!
3667
3668 if (!bAdjustCode)
3669 return;
3670
3671 sc::RefUpdateResult aRes = pCode->AdjustReferenceOnMovedTab(rCxt, aOldPos);
3672 if (aRes.mbNameModified)
3673 // Re-compile after sheet(s) have been deleted.
3674 bCompile = true;
3675 }
3676
UpdateInsertTabAbs(SCTAB nTable)3677 void ScFormulaCell::UpdateInsertTabAbs(SCTAB nTable)
3678 {
3679 if (rDocument.IsClipOrUndo())
3680 return;
3681
3682 bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3683 if (!bAdjustCode)
3684 return;
3685
3686 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3687 formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3688 while (p)
3689 {
3690 ScSingleRefData& rRef1 = *p->GetSingleRef();
3691 if (!rRef1.IsTabRel() && nTable <= rRef1.Tab())
3692 rRef1.IncTab(1);
3693 if (p->GetType() == formula::svDoubleRef)
3694 {
3695 ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
3696 if (!rRef2.IsTabRel() && nTable <= rRef2.Tab())
3697 rRef2.IncTab(1);
3698 }
3699 p = aIter.GetNextReferenceRPN();
3700 }
3701 }
3702
TestTabRefAbs(SCTAB nTable)3703 bool ScFormulaCell::TestTabRefAbs(SCTAB nTable)
3704 {
3705 if (rDocument.IsClipOrUndo())
3706 return false;
3707
3708 bool bAdjustCode = !mxGroup || mxGroup->mpTopCell == this;
3709 if (!bAdjustCode)
3710 return false;
3711
3712 bool bRet = false;
3713 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3714 formula::FormulaToken* p = aIter.GetNextReferenceRPN();
3715 while (p)
3716 {
3717 ScSingleRefData& rRef1 = *p->GetSingleRef();
3718 if (!rRef1.IsTabRel())
3719 {
3720 if (nTable != rRef1.Tab())
3721 bRet = true;
3722 else if (nTable != aPos.Tab())
3723 rRef1.SetAbsTab(aPos.Tab());
3724 }
3725 if (p->GetType() == formula::svDoubleRef)
3726 {
3727 ScSingleRefData& rRef2 = p->GetDoubleRef()->Ref2;
3728 if (!rRef2.IsTabRel())
3729 {
3730 if(nTable != rRef2.Tab())
3731 bRet = true;
3732 else if (nTable != aPos.Tab())
3733 rRef2.SetAbsTab(aPos.Tab());
3734 }
3735 }
3736 p = aIter.GetNextReferenceRPN();
3737 }
3738 return bRet;
3739 }
3740
UpdateCompile(bool bForceIfNameInUse)3741 void ScFormulaCell::UpdateCompile( bool bForceIfNameInUse )
3742 {
3743 if ( bForceIfNameInUse && !bCompile )
3744 bCompile = pCode->HasNameOrColRowName();
3745 if ( bCompile )
3746 pCode->SetCodeError( FormulaError::NONE ); // make sure it will really be compiled
3747 CompileTokenArray();
3748 }
3749
lcl_TransposeReference(ScSingleRefData & rRef)3750 static void lcl_TransposeReference(ScSingleRefData& rRef)
3751 {
3752 // References to or over filtered rows are not adjusted
3753 // analog to the normal (non-transposed) case
3754 SCCOLROW nTemp = rRef.Col();
3755 rRef.SetRelCol(rRef.Row());
3756 rRef.SetRelRow(nTemp);
3757 }
3758
3759 // Reference transposition is only called in Clipboard Document
TransposeReference()3760 void ScFormulaCell::TransposeReference()
3761 {
3762 bool bFound = false;
3763 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3764 formula::FormulaToken* t;
3765 while ( ( t = aIter.GetNextReference() ) != nullptr )
3766 {
3767 ScSingleRefData& rRef1 = *t->GetSingleRef();
3768 if ( rRef1.IsColRel() && rRef1.IsRowRel() )
3769 {
3770 bool bDouble = (t->GetType() == formula::svDoubleRef);
3771 ScSingleRefData& rRef2 = (bDouble ? t->GetDoubleRef()->Ref2 : rRef1);
3772 if ( !bDouble || (rRef2.IsColRel() && rRef2.IsRowRel()) )
3773 {
3774 lcl_TransposeReference(rRef1);
3775
3776 if ( bDouble )
3777 lcl_TransposeReference(rRef2);
3778
3779 bFound = true;
3780 }
3781 }
3782 }
3783
3784 if (bFound)
3785 bCompile = true;
3786 }
3787
UpdateTranspose(const ScRange & rSource,const ScAddress & rDest,ScDocument * pUndoDoc)3788 void ScFormulaCell::UpdateTranspose( const ScRange& rSource, const ScAddress& rDest,
3789 ScDocument* pUndoDoc )
3790 {
3791 EndListeningTo( rDocument );
3792
3793 ScAddress aOldPos = aPos;
3794 bool bPosChanged = false; // Whether this cell has been moved
3795
3796 // Dest range is transposed
3797 ScRange aDestRange( rDest, ScAddress(
3798 static_cast<SCCOL>(rDest.Col() + rSource.aEnd.Row() - rSource.aStart.Row()),
3799 static_cast<SCROW>(rDest.Row() + rSource.aEnd.Col() - rSource.aStart.Col()),
3800 rDest.Tab() + rSource.aEnd.Tab() - rSource.aStart.Tab() ) );
3801
3802 // cell within range
3803 if ( aDestRange.Contains( aOldPos ) )
3804 {
3805 // References of these cells were not changed by ScTokenArray::AdjustReferenceOnMove()
3806 // Count back Positions
3807 SCCOL nRelPosX = aOldPos.Col();
3808 SCROW nRelPosY = aOldPos.Row();
3809 SCTAB nRelPosZ = aOldPos.Tab();
3810 ScRefUpdate::DoTranspose( nRelPosX, nRelPosY, nRelPosZ, rDocument, aDestRange, rSource.aStart );
3811 aOldPos.Set( nRelPosX, nRelPosY, nRelPosZ );
3812 bPosChanged = true;
3813 }
3814
3815 std::unique_ptr<ScTokenArray> pOld;
3816 if (pUndoDoc)
3817 pOld = pCode->Clone();
3818 bool bRefChanged = false;
3819
3820 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3821 formula::FormulaToken* t;
3822 while( (t = aIter.GetNextReferenceOrName()) != nullptr )
3823 {
3824 if( t->GetOpCode() == ocName )
3825 {
3826 const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
3827 if (pName && pName->IsModified())
3828 bRefChanged = true;
3829 }
3830 else if( t->GetType() != svIndex )
3831 {
3832 SingleDoubleRefModifier aMod(*t);
3833 ScComplexRefData& rRef = aMod.Ref();
3834 ScRange aAbs = rRef.toAbs(rDocument, aOldPos);
3835 bool bMod = (ScRefUpdate::UpdateTranspose(rDocument, rSource, rDest, aAbs) != UR_NOTHING || bPosChanged);
3836 if (bMod)
3837 {
3838 rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos); // based on the new anchor position.
3839 bRefChanged = true;
3840
3841 // Absolute sheet reference => set 3D flag.
3842 // More than one sheet referenced => has to have both 3D flags.
3843 // If end part has 3D flag => start part must have it too.
3844 // The same behavior as in ScTokenArray::AdjustReferenceOnMove() is used for 3D-Flags.
3845 rRef.Ref2.SetFlag3D(aAbs.aStart.Tab() != aAbs.aEnd.Tab() || !rRef.Ref2.IsTabRel());
3846 rRef.Ref1.SetFlag3D(
3847 (rSource.aStart.Tab() != rDest.Tab() && !bPosChanged)
3848 || !rRef.Ref1.IsTabRel() || rRef.Ref2.IsFlag3D());
3849 }
3850 }
3851 }
3852
3853 if (bRefChanged)
3854 {
3855 if (pUndoDoc)
3856 {
3857 // Similar to setOldCodeToUndo(), but it cannot be used due to the check
3858 // pUndoDoc->GetCellType(aPos) == CELLTYPE_FORMULA
3859 ScFormulaCell* pFCell = new ScFormulaCell(
3860 *pUndoDoc, aPos, pOld ? *pOld : ScTokenArray(*pUndoDoc), eTempGrammar, cMatrixFlag);
3861
3862 pFCell->aResult.SetToken( nullptr); // to recognize it as changed later (Cut/Paste!)
3863 pUndoDoc->SetFormulaCell(aPos, pFCell);
3864 }
3865
3866 bCompile = true;
3867 CompileTokenArray(); // also call StartListeningTo
3868 SetDirty();
3869 }
3870 else
3871 StartListeningTo( rDocument ); // Listener as previous
3872 }
3873
UpdateGrow(const ScRange & rArea,SCCOL nGrowX,SCROW nGrowY)3874 void ScFormulaCell::UpdateGrow( const ScRange& rArea, SCCOL nGrowX, SCROW nGrowY )
3875 {
3876 EndListeningTo( rDocument );
3877
3878 bool bRefChanged = false;
3879
3880 formula::FormulaTokenArrayPlainIterator aIter(*pCode);
3881 formula::FormulaToken* t;
3882
3883 while( (t = aIter.GetNextReferenceOrName()) != nullptr )
3884 {
3885 if( t->GetOpCode() == ocName )
3886 {
3887 const ScRangeData* pName = rDocument.FindRangeNameBySheetAndIndex( t->GetSheet(), t->GetIndex());
3888 if (pName && pName->IsModified())
3889 bRefChanged = true;
3890 }
3891 else if( t->GetType() != svIndex )
3892 {
3893 SingleDoubleRefModifier aMod(*t);
3894 ScComplexRefData& rRef = aMod.Ref();
3895 ScRange aAbs = rRef.toAbs(rDocument, aPos);
3896 bool bMod = (ScRefUpdate::UpdateGrow(rArea, nGrowX, nGrowY, aAbs) != UR_NOTHING);
3897 if (bMod)
3898 {
3899 rRef.SetRange(rDocument.GetSheetLimits(), aAbs, aPos);
3900 bRefChanged = true;
3901 }
3902 }
3903 }
3904
3905 if (bRefChanged)
3906 {
3907 bCompile = true;
3908 CompileTokenArray(); // Also call StartListeningTo
3909 SetDirty();
3910 }
3911 else
3912 StartListeningTo( rDocument ); // Listener as previous
3913 }
3914
3915 // See also ScDocument::FindRangeNamesReferencingSheet()
lcl_FindRangeNamesInUse(sc::UpdatedRangeNames & rIndexes,const ScTokenArray * pCode,const ScDocument & rDoc,int nRecursion)3916 static void lcl_FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes, const ScTokenArray* pCode, const ScDocument& rDoc,
3917 int nRecursion)
3918 {
3919 FormulaTokenArrayPlainIterator aIter(*pCode);
3920 for (FormulaToken* p = aIter.First(); p; p = aIter.Next())
3921 {
3922 if (p->GetOpCode() == ocName)
3923 {
3924 sal_uInt16 nTokenIndex = p->GetIndex();
3925 SCTAB nTab = p->GetSheet();
3926 rIndexes.setUpdatedName( nTab, nTokenIndex);
3927
3928 if (nRecursion < 126) // whatever... 42*3
3929 {
3930 ScRangeData* pSubName = rDoc.FindRangeNameBySheetAndIndex( nTab, nTokenIndex);
3931 if (pSubName)
3932 lcl_FindRangeNamesInUse(rIndexes, pSubName->GetCode(), rDoc, nRecursion+1);
3933 }
3934 }
3935 }
3936 }
3937
FindRangeNamesInUse(sc::UpdatedRangeNames & rIndexes) const3938 void ScFormulaCell::FindRangeNamesInUse(sc::UpdatedRangeNames& rIndexes) const
3939 {
3940 lcl_FindRangeNamesInUse( rIndexes, pCode, rDocument, 0);
3941 }
3942
SetChanged(bool b)3943 void ScFormulaCell::SetChanged(bool b)
3944 {
3945 bChanged = b;
3946 }
3947
SetCode(std::unique_ptr<ScTokenArray> pNew)3948 void ScFormulaCell::SetCode( std::unique_ptr<ScTokenArray> pNew )
3949 {
3950 assert(!mxGroup); // Don't call this if it's shared.
3951 delete pCode;
3952 pCode = pNew.release(); // takes ownership.
3953 }
3954
SetRunning(bool bVal)3955 void ScFormulaCell::SetRunning( bool bVal )
3956 {
3957 bRunning = bVal;
3958 }
3959
CompileDBFormula(sc::CompileFormulaContext & rCxt)3960 void ScFormulaCell::CompileDBFormula( sc::CompileFormulaContext& rCxt )
3961 {
3962 FormulaTokenArrayPlainIterator aIter(*pCode);
3963 for( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
3964 {
3965 OpCode eOp = p->GetOpCode();
3966 if ( eOp == ocDBArea || eOp == ocTableRef )
3967 {
3968 bCompile = true;
3969 CompileTokenArray(rCxt);
3970 SetDirty();
3971 break;
3972 }
3973 }
3974 }
3975
CompileColRowNameFormula(sc::CompileFormulaContext & rCxt)3976 void ScFormulaCell::CompileColRowNameFormula( sc::CompileFormulaContext& rCxt )
3977 {
3978 FormulaTokenArrayPlainIterator aIter(*pCode);
3979 for ( FormulaToken* p = aIter.First(); p; p = aIter.Next() )
3980 {
3981 if ( p->GetOpCode() == ocColRowName )
3982 {
3983 bCompile = true;
3984 CompileTokenArray(rCxt);
3985 SetDirty();
3986 break;
3987 }
3988 }
3989 }
3990
SetPrevious(ScFormulaCell * pF)3991 void ScFormulaCell::SetPrevious( ScFormulaCell* pF ) { pPrevious = pF; }
SetNext(ScFormulaCell * pF)3992 void ScFormulaCell::SetNext( ScFormulaCell* pF ) { pNext = pF; }
SetPreviousTrack(ScFormulaCell * pF)3993 void ScFormulaCell::SetPreviousTrack( ScFormulaCell* pF ) { pPreviousTrack = pF; }
SetNextTrack(ScFormulaCell * pF)3994 void ScFormulaCell::SetNextTrack( ScFormulaCell* pF ) { pNextTrack = pF; }
3995
CreateCellGroup(SCROW nLen,bool bInvariant)3996 ScFormulaCellGroupRef ScFormulaCell::CreateCellGroup( SCROW nLen, bool bInvariant )
3997 {
3998 if (mxGroup)
3999 {
4000 // You can't create a new group if the cell is already a part of a group.
4001 // Is this a sign of some inconsistent or incorrect data structures? Or normal?
4002 SAL_INFO("sc.opencl", "You can't create a new group if the cell is already a part of a group");
4003 return ScFormulaCellGroupRef();
4004 }
4005
4006 mxGroup.reset(new ScFormulaCellGroup);
4007 mxGroup->mpTopCell = this;
4008 mxGroup->mbInvariant = bInvariant;
4009 mxGroup->mnLength = nLen;
4010 mxGroup->mpCode = std::move(*pCode); // Move this to the shared location.
4011 delete pCode;
4012 pCode = &*mxGroup->mpCode;
4013 return mxGroup;
4014 }
4015
SetCellGroup(const ScFormulaCellGroupRef & xRef)4016 void ScFormulaCell::SetCellGroup( const ScFormulaCellGroupRef &xRef )
4017 {
4018 if (!xRef)
4019 {
4020 // Make this cell a non-grouped cell.
4021 if (mxGroup)
4022 pCode = mxGroup->mpCode->Clone().release();
4023
4024 mxGroup = xRef;
4025 return;
4026 }
4027
4028 // Group object has shared token array.
4029 if (!mxGroup)
4030 // Currently not shared. Delete the existing token array first.
4031 delete pCode;
4032
4033 mxGroup = xRef;
4034 pCode = &*mxGroup->mpCode;
4035 mxGroup->mnWeight = 0; // invalidate
4036 }
4037
CompareByTokenArray(const ScFormulaCell & rOther) const4038 ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaCell& rOther ) const
4039 {
4040 // no Matrix formulae yet.
4041 if ( GetMatrixFlag() != ScMatrixMode::NONE )
4042 return NotEqual;
4043
4044 // are these formulas at all similar ?
4045 if ( GetHash() != rOther.GetHash() )
4046 return NotEqual;
4047
4048 if (!pCode->IsShareable() || !rOther.pCode->IsShareable())
4049 return NotEqual;
4050
4051 FormulaToken **pThis = pCode->GetCode();
4052 sal_uInt16 nThisLen = pCode->GetCodeLen();
4053 FormulaToken **pOther = rOther.pCode->GetCode();
4054 sal_uInt16 nOtherLen = rOther.pCode->GetCodeLen();
4055
4056 if ( !pThis || !pOther )
4057 {
4058 // Error: no compiled code for cells !"
4059 return NotEqual;
4060 }
4061
4062 if ( nThisLen != nOtherLen )
4063 return NotEqual;
4064
4065 // No tokens can be an error cell so check error code, otherwise we could
4066 // end up with a series of equal error values instead of individual error
4067 // values. Also if for any reason different errors are set even if all
4068 // tokens are equal, the cells are not equal.
4069 if (pCode->GetCodeError() != rOther.pCode->GetCodeError())
4070 return NotEqual;
4071
4072 bool bInvariant = true;
4073
4074 // check we are basically the same function
4075 for ( sal_uInt16 i = 0; i < nThisLen; i++ )
4076 {
4077 formula::FormulaToken *pThisTok = pThis[i];
4078 formula::FormulaToken *pOtherTok = pOther[i];
4079
4080 if ( pThisTok->GetType() != pOtherTok->GetType() ||
4081 pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
4082 pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
4083 {
4084 // Incompatible type, op-code or param counts.
4085 return NotEqual;
4086 }
4087
4088 switch (pThisTok->GetType())
4089 {
4090 case formula::svMatrix:
4091 case formula::svExternalSingleRef:
4092 case formula::svExternalDoubleRef:
4093 // Ignoring matrix and external references for now.
4094 return NotEqual;
4095
4096 case formula::svSingleRef:
4097 {
4098 // Single cell reference.
4099 const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
4100 if (rRef != *pOtherTok->GetSingleRef())
4101 return NotEqual;
4102
4103 if (rRef.IsRowRel())
4104 bInvariant = false;
4105 }
4106 break;
4107 case formula::svDoubleRef:
4108 {
4109 // Range reference.
4110 const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
4111 const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
4112 if (rRef1 != *pOtherTok->GetSingleRef())
4113 return NotEqual;
4114
4115 if (rRef2 != *pOtherTok->GetSingleRef2())
4116 return NotEqual;
4117
4118 if (rRef1.IsRowRel())
4119 bInvariant = false;
4120
4121 if (rRef2.IsRowRel())
4122 bInvariant = false;
4123 }
4124 break;
4125 case formula::svDouble:
4126 {
4127 if(!rtl::math::approxEqual(pThisTok->GetDouble(), pOtherTok->GetDouble()))
4128 return NotEqual;
4129 }
4130 break;
4131 case formula::svString:
4132 {
4133 if(pThisTok->GetString() != pOtherTok->GetString())
4134 return NotEqual;
4135 }
4136 break;
4137 case formula::svIndex:
4138 {
4139 if(pThisTok->GetIndex() != pOtherTok->GetIndex() || pThisTok->GetSheet() != pOtherTok->GetSheet())
4140 return NotEqual;
4141 }
4142 break;
4143 case formula::svByte:
4144 {
4145 if(pThisTok->GetByte() != pOtherTok->GetByte())
4146 return NotEqual;
4147 }
4148 break;
4149 case formula::svExternal:
4150 {
4151 if (pThisTok->GetExternal() != pOtherTok->GetExternal())
4152 return NotEqual;
4153
4154 if (pThisTok->GetByte() != pOtherTok->GetByte())
4155 return NotEqual;
4156 }
4157 break;
4158 case formula::svError:
4159 {
4160 if (pThisTok->GetError() != pOtherTok->GetError())
4161 return NotEqual;
4162 }
4163 break;
4164 default:
4165 ;
4166 }
4167 }
4168
4169 // If still the same, check lexical names as different names may result in
4170 // identical RPN code.
4171
4172 pThis = pCode->GetArray();
4173 nThisLen = pCode->GetLen();
4174 pOther = rOther.pCode->GetArray();
4175 nOtherLen = rOther.pCode->GetLen();
4176
4177 if ( !pThis || !pOther )
4178 {
4179 // Error: no code for cells !"
4180 return NotEqual;
4181 }
4182
4183 if ( nThisLen != nOtherLen )
4184 return NotEqual;
4185
4186 for ( sal_uInt16 i = 0; i < nThisLen; i++ )
4187 {
4188 formula::FormulaToken *pThisTok = pThis[i];
4189 formula::FormulaToken *pOtherTok = pOther[i];
4190
4191 if ( pThisTok->GetType() != pOtherTok->GetType() ||
4192 pThisTok->GetOpCode() != pOtherTok->GetOpCode() ||
4193 pThisTok->GetParamCount() != pOtherTok->GetParamCount() )
4194 {
4195 // Incompatible type, op-code or param counts.
4196 return NotEqual;
4197 }
4198
4199 switch (pThisTok->GetType())
4200 {
4201 // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code,
4202 // resulting in identical RPN references that could lead to creating
4203 // a formula group from formulas that should not be merged into a group,
4204 // so check also the formula itself.
4205 case formula::svSingleRef:
4206 {
4207 // Single cell reference.
4208 const ScSingleRefData& rRef = *pThisTok->GetSingleRef();
4209 if (rRef != *pOtherTok->GetSingleRef())
4210 return NotEqual;
4211
4212 if (rRef.IsRowRel())
4213 bInvariant = false;
4214 }
4215 break;
4216 case formula::svDoubleRef:
4217 {
4218 // Range reference.
4219 const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef();
4220 const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2();
4221 if (rRef1 != *pOtherTok->GetSingleRef())
4222 return NotEqual;
4223
4224 if (rRef2 != *pOtherTok->GetSingleRef2())
4225 return NotEqual;
4226
4227 if (rRef1.IsRowRel())
4228 bInvariant = false;
4229
4230 if (rRef2.IsRowRel())
4231 bInvariant = false;
4232 }
4233 break;
4234 // All index tokens are names. Different categories already had
4235 // different OpCode values.
4236 case formula::svIndex:
4237 {
4238 if (pThisTok->GetIndex() != pOtherTok->GetIndex())
4239 return NotEqual;
4240 switch (pThisTok->GetOpCode())
4241 {
4242 case ocTableRef:
4243 // nothing, sheet value assumed as -1, silence
4244 // ScTableRefToken::GetSheet() SAL_WARN about
4245 // unhandled
4246 ;
4247 break;
4248 default: // ocName, ocDBArea
4249 if (pThisTok->GetSheet() != pOtherTok->GetSheet())
4250 return NotEqual;
4251 }
4252 }
4253 break;
4254 default:
4255 ;
4256 }
4257 }
4258
4259 return bInvariant ? EqualInvariant : EqualRelativeRef;
4260 }
4261
4262 namespace {
4263
4264 // Split N into optimally equal-sized pieces, each not larger than K.
4265 // Return value P is number of pieces. A returns the number of pieces
4266 // one larger than N/P, 0..P-1.
4267
splitup(int N,int K,int & A)4268 int splitup(int N, int K, int& A)
4269 {
4270 assert(N > 0);
4271 assert(K > 0);
4272
4273 A = 0;
4274
4275 if (N <= K)
4276 return 1;
4277
4278 const int ideal_num_parts = N / K;
4279 if (ideal_num_parts * K == N)
4280 return ideal_num_parts;
4281
4282 const int num_parts = ideal_num_parts + 1;
4283 const int nominal_part_size = N / num_parts;
4284
4285 A = N - num_parts * nominal_part_size;
4286
4287 return num_parts;
4288 }
4289
4290 struct ScDependantsCalculator
4291 {
4292 ScDocument& mrDoc;
4293 const ScTokenArray& mrCode;
4294 const ScFormulaCellGroupRef& mxGroup;
4295 const SCROW mnLen;
4296 const ScAddress& mrPos;
4297 const bool mFromFirstRow;
4298 const SCROW mnStartOffset;
4299 const SCROW mnEndOffset;
4300 const SCROW mnSpanLen;
4301
ScDependantsCalculator__anonb316ae920511::ScDependantsCalculator4302 ScDependantsCalculator(ScDocument& rDoc, const ScTokenArray& rCode, const ScFormulaCell& rCell,
4303 const ScAddress& rPos, bool fromFirstRow, SCROW nStartOffset, SCROW nEndOffset) :
4304 mrDoc(rDoc),
4305 mrCode(rCode),
4306 mxGroup(rCell.GetCellGroup()),
4307 mnLen(mxGroup->mnLength),
4308 mrPos(rPos),
4309 // ScColumn::FetchVectorRefArray() always fetches data from row 0, even if the data is used
4310 // only from further rows. This data fetching could also lead to Interpret() calls, so
4311 // in OpenCL mode the formula in practice depends on those cells too.
4312 mFromFirstRow(fromFirstRow),
4313 mnStartOffset(nStartOffset),
4314 mnEndOffset(nEndOffset),
4315 mnSpanLen(nEndOffset - nStartOffset + 1)
4316 {
4317 }
4318
4319 // FIXME: copy-pasted from ScGroupTokenConverter. factor out somewhere else
4320 // (note already modified a bit, mFromFirstRow)
4321
4322 // I think what this function does is to check whether the relative row reference nRelRow points
4323 // to a row that is inside the range of rows covered by the formula group.
4324
isSelfReferenceRelative__anonb316ae920511::ScDependantsCalculator4325 bool isSelfReferenceRelative(const ScAddress& rRefPos, SCROW nRelRow)
4326 {
4327 if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
4328 return false;
4329
4330 SCROW nEndRow = mrPos.Row() + mnLen - 1;
4331
4332 if (nRelRow <= 0)
4333 {
4334 SCROW nTest = nEndRow;
4335 nTest += nRelRow;
4336 if (nTest >= mrPos.Row())
4337 return true;
4338 }
4339 else
4340 {
4341 SCROW nTest = mrPos.Row(); // top row.
4342 nTest += nRelRow;
4343 if (nTest <= nEndRow)
4344 return true;
4345 // If pointing below the formula, it's always included if going from first row.
4346 if (mFromFirstRow)
4347 return true;
4348 }
4349
4350 return false;
4351 }
4352
4353 // FIXME: another copy-paste
4354
4355 // And this correspondingly checks whether an absolute row is inside the range of rows covered
4356 // by the formula group.
4357
isSelfReferenceAbsolute__anonb316ae920511::ScDependantsCalculator4358 bool isSelfReferenceAbsolute(const ScAddress& rRefPos)
4359 {
4360 if (rRefPos.Col() != mrPos.Col() || rRefPos.Tab() != mrPos.Tab())
4361 return false;
4362
4363 SCROW nEndRow = mrPos.Row() + mnLen - 1;
4364
4365 if (rRefPos.Row() < mrPos.Row())
4366 return false;
4367
4368 // If pointing below the formula, it's always included if going from first row.
4369 if (rRefPos.Row() > nEndRow && !mFromFirstRow)
4370 return false;
4371
4372 return true;
4373 }
4374
4375 // Checks if the doubleref engulfs all of formula group cells
4376 // Note : does not check if there is a partial overlap, that can be done by calling
4377 // isSelfReference[Absolute|Relative]() on both the start and end of the double ref
isDoubleRefSpanGroupRange__anonb316ae920511::ScDependantsCalculator4378 bool isDoubleRefSpanGroupRange(const ScRange& rAbs, bool bIsRef1RowRel, bool bIsRef2RowRel)
4379 {
4380 if (rAbs.aStart.Col() > mrPos.Col() || rAbs.aEnd.Col() < mrPos.Col()
4381 || rAbs.aStart.Tab() > mrPos.Tab() || rAbs.aEnd.Tab() < mrPos.Tab())
4382 {
4383 return false;
4384 }
4385
4386 SCROW nStartRow = mrPos.Row();
4387 SCROW nEndRow = nStartRow + mnLen - 1;
4388 SCROW nRefStartRow = rAbs.aStart.Row();
4389 SCROW nRefEndRow = rAbs.aEnd.Row();
4390
4391 if (bIsRef1RowRel && bIsRef2RowRel &&
4392 ((nRefStartRow <= nStartRow && nRefEndRow >= nEndRow) ||
4393 ((nRefStartRow + mnLen - 1) <= nStartRow &&
4394 (nRefEndRow + mnLen - 1) >= nEndRow)))
4395 return true;
4396
4397 if (!bIsRef1RowRel && nRefStartRow <= nStartRow &&
4398 (nRefEndRow >= nEndRow || (nRefEndRow + mnLen - 1) >= nEndRow))
4399 return true;
4400
4401 if (!bIsRef2RowRel &&
4402 nRefStartRow <= nStartRow && nRefEndRow >= nEndRow)
4403 return true;
4404
4405 // If going from first row, the referenced range must be entirely above the formula,
4406 // otherwise the formula would be included.
4407 if (mFromFirstRow && nRefEndRow >= nStartRow)
4408 return true;
4409
4410 return false;
4411 }
4412
4413 // FIXME: another copy-paste
trimLength__anonb316ae920511::ScDependantsCalculator4414 SCROW trimLength(SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nRow, SCROW nRowLen)
4415 {
4416 SCROW nLastRow = nRow + nRowLen - 1; // current last row.
4417 nLastRow = mrDoc.GetLastDataRow(nTab, nCol1, nCol2, nLastRow);
4418 if (nLastRow < (nRow + nRowLen - 1))
4419 {
4420 // This can end up negative! Was that the original intent, or
4421 // is it accidental? Was it not like that originally but the
4422 // surrounding conditions changed?
4423 nRowLen = nLastRow - nRow + 1;
4424 // Anyway, let's assume it doesn't make sense to return a
4425 // negative or zero value here.
4426 if (nRowLen <= 0)
4427 nRowLen = 1;
4428 }
4429 else if (nLastRow == 0)
4430 // Column is empty.
4431 nRowLen = 1;
4432
4433 return nRowLen;
4434 }
4435
4436 // Because Lookup will extend the Result Vector under certain circumstances listed at:
4437 // https://wiki.documentfoundation.org/Documentation/Calc_Functions/LOOKUP
4438 // then if the Lookup has a Result Vector only accept the Lookup for parallelization
4439 // of the Result Vector has the same dimensions as the Search Vector.
LookupResultVectorMismatch__anonb316ae920511::ScDependantsCalculator4440 bool LookupResultVectorMismatch(sal_Int32 nTokenIdx)
4441 {
4442 if (nTokenIdx >= 3)
4443 {
4444 FormulaToken** pRPNArray = mrCode.GetCode();
4445 if (pRPNArray[nTokenIdx - 1]->GetOpCode() == ocPush && // <- result vector
4446 pRPNArray[nTokenIdx - 2]->GetOpCode() == ocPush && // <- search vector
4447 pRPNArray[nTokenIdx - 2]->GetType() == svDoubleRef &&
4448 pRPNArray[nTokenIdx - 3]->GetOpCode() == ocPush) // <- search criterion
4449 {
4450 auto res = pRPNArray[nTokenIdx - 1];
4451 // If Result vector is just a single cell reference
4452 // LOOKUP extends it as a column vector.
4453 if (res->GetType() == svSingleRef)
4454 return true;
4455
4456 // If Result vector is a cell range and the match position
4457 // falls outside its length, it gets automatically extended
4458 // to the length of Search vector, but in the direction of
4459 // Result vector.
4460 if (res->GetType() == svDoubleRef)
4461 {
4462 ScComplexRefData aRef1 = *res->GetDoubleRef();
4463 ScComplexRefData aRef2 = *pRPNArray[nTokenIdx - 2]->GetDoubleRef();
4464 ScRange resultRange = aRef1.toAbs(mrDoc, mrPos);
4465 ScRange sourceRange = aRef2.toAbs(mrDoc, mrPos);
4466
4467 SCROW nResultRows = resultRange.aEnd.Row() - resultRange.aStart.Row();
4468 SCROW nSourceRows = sourceRange.aEnd.Row() - sourceRange.aStart.Row();
4469 if (nResultRows != nSourceRows)
4470 return true;
4471
4472 SCCOL nResultCols = resultRange.aEnd.Col() - resultRange.aStart.Col();
4473 SCCOL nSourceCols = sourceRange.aEnd.Col() - sourceRange.aStart.Col();
4474 if (nResultCols != nSourceCols)
4475 return true;
4476 }
4477 }
4478 }
4479 return false;
4480 }
4481
DoIt__anonb316ae920511::ScDependantsCalculator4482 bool DoIt(ScRangeList* pSuccessfulDependencies, ScAddress* pDirtiedAddress)
4483 {
4484 // Partially from ScGroupTokenConverter::convert in sc/source/core/data/grouptokenconverter.cxx
4485
4486 ScRangeList aRangeList;
4487
4488 // Self references should be checked by considering the entire formula-group not just the provided span.
4489 bool bHasSelfReferences = false;
4490 bool bInDocShellRecalc = mrDoc.IsInDocShellRecalc();
4491
4492 FormulaToken** pRPNArray = mrCode.GetCode();
4493 sal_uInt16 nCodeLen = mrCode.GetCodeLen();
4494 for (sal_Int32 nTokenIdx = nCodeLen-1; nTokenIdx >= 0; --nTokenIdx)
4495 {
4496 auto p = pRPNArray[nTokenIdx];
4497 if (!bInDocShellRecalc)
4498 {
4499 // The dependency evaluator evaluates all arguments of IF/IFS/SWITCH irrespective
4500 // of the result of the condition expression.
4501 // This is a perf problem if we *don't* intent on recalc'ing all dirty cells
4502 // in the document. So let's disable threading and stop dependency evaluation if
4503 // the call did not originate from ScDocShell::DoRecalc()/ScDocShell::DoHardRecalc()
4504 // for formulae with IF/IFS/SWITCH
4505 OpCode nOpCode = p->GetOpCode();
4506 if (nOpCode == ocIf || nOpCode == ocIfs_MS || nOpCode == ocSwitch_MS)
4507 return false;
4508 }
4509
4510 if (p->GetOpCode() == ocLookup && LookupResultVectorMismatch(nTokenIdx))
4511 {
4512 SAL_INFO("sc.core.formulacell", "Lookup Result Vector size doesn't match Search Vector");
4513 return false;
4514 }
4515
4516 if (p->GetOpCode() == ocRange)
4517 {
4518 // We are just looking at svSingleRef/svDoubleRef, so we will miss that ocRange constructs
4519 // a range from its arguments, and only examining the individual args doesn't capture the
4520 // true range of dependencies
4521 SAL_WARN("sc.core.formulacell", "dynamic range, dropping as candidate for parallelizing");
4522 return false;
4523 }
4524
4525 switch (p->GetType())
4526 {
4527 case svSingleRef:
4528 {
4529 ScSingleRefData aRef = *p->GetSingleRef(); // =Sheet1!A1
4530 if( aRef.IsDeleted())
4531 return false;
4532 ScAddress aRefPos = aRef.toAbs(mrDoc, mrPos);
4533
4534 if (!mrDoc.HasTable(aRefPos.Tab()))
4535 return false; // or true?
4536
4537 if (aRef.IsRowRel())
4538 {
4539 if (isSelfReferenceRelative(aRefPos, aRef.Row()))
4540 {
4541 bHasSelfReferences = true;
4542 continue;
4543 }
4544
4545 // Trim data array length to actual data range.
4546 SCROW nTrimLen = trimLength(aRefPos.Tab(), aRefPos.Col(), aRefPos.Col(), aRefPos.Row() + mnStartOffset, mnSpanLen);
4547
4548 aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row() + mnStartOffset, aRefPos.Tab(),
4549 aRefPos.Col(), aRefPos.Row() + mnStartOffset + nTrimLen - 1, aRefPos.Tab()));
4550 }
4551 else
4552 {
4553 if (isSelfReferenceAbsolute(aRefPos))
4554 {
4555 bHasSelfReferences = true;
4556 continue;
4557 }
4558
4559 aRangeList.Join(ScRange(aRefPos.Col(), aRefPos.Row(), aRefPos.Tab()));
4560 }
4561 }
4562 break;
4563 case svDoubleRef:
4564 {
4565 ScComplexRefData aRef = *p->GetDoubleRef();
4566 if( aRef.IsDeleted())
4567 return false;
4568 ScRange aAbs = aRef.toAbs(mrDoc, mrPos);
4569
4570 // Multiple sheet
4571 if (aAbs.aStart.Tab() != aAbs.aEnd.Tab())
4572 return false;
4573
4574 bool bIsRef1RowRel = aRef.Ref1.IsRowRel();
4575 // Check for self reference.
4576 if (bIsRef1RowRel)
4577 {
4578 if (isSelfReferenceRelative(aAbs.aStart, aRef.Ref1.Row()))
4579 {
4580 bHasSelfReferences = true;
4581 continue;
4582 }
4583 }
4584 else if (isSelfReferenceAbsolute(aAbs.aStart))
4585 {
4586 bHasSelfReferences = true;
4587 continue;
4588 }
4589
4590 bool bIsRef2RowRel = aRef.Ref2.IsRowRel();
4591 if (bIsRef2RowRel)
4592 {
4593 if (isSelfReferenceRelative(aAbs.aEnd, aRef.Ref2.Row()))
4594 {
4595 bHasSelfReferences = true;
4596 continue;
4597 }
4598 }
4599 else if (isSelfReferenceAbsolute(aAbs.aEnd))
4600 {
4601 bHasSelfReferences = true;
4602 continue;
4603 }
4604
4605 if (isDoubleRefSpanGroupRange(aAbs, bIsRef1RowRel, bIsRef2RowRel))
4606 {
4607 bHasSelfReferences = true;
4608 continue;
4609 }
4610
4611 SCROW nFirstRefStartRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnStartOffset : aAbs.aStart.Row();
4612 SCROW nLastRefEndRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnEndOffset : aAbs.aEnd.Row();
4613
4614 SCROW nFirstRefEndRow = bIsRef1RowRel ? aAbs.aStart.Row() + mnEndOffset : aAbs.aStart.Row();
4615 SCROW nLastRefStartRow = bIsRef2RowRel ? aAbs.aEnd.Row() + mnStartOffset : aAbs.aEnd.Row();
4616
4617 // The first row that will be referenced through the doubleref.
4618 SCROW nFirstRefRow = std::min(nFirstRefStartRow, nLastRefStartRow);
4619 // The last row that will be referenced through the doubleref.
4620 SCROW nLastRefRow = std::max(nLastRefEndRow, nFirstRefEndRow);
4621
4622 // Number of rows to be evaluated from nFirstRefRow.
4623 SCROW nArrayLength = nLastRefRow - nFirstRefRow + 1;
4624 assert(nArrayLength > 0);
4625
4626 // Trim trailing empty rows.
4627 nArrayLength = trimLength(aAbs.aStart.Tab(), aAbs.aStart.Col(), aAbs.aEnd.Col(), nFirstRefRow, nArrayLength);
4628
4629 aRangeList.Join(ScRange(aAbs.aStart.Col(), nFirstRefRow, aAbs.aStart.Tab(),
4630 aAbs.aEnd.Col(), nFirstRefRow + nArrayLength - 1, aAbs.aEnd.Tab()));
4631 }
4632 break;
4633 default:
4634 break;
4635 }
4636 }
4637
4638 // Compute dependencies irrespective of the presence of any self references.
4639 // These dependencies would get computed via InterpretTail anyway when we disable group calc, so let's do it now.
4640 // The advantage is that the FG's get marked for cycles early if present, and can avoid lots of complications.
4641 for (size_t i = 0; i < aRangeList.size(); ++i)
4642 {
4643 const ScRange & rRange = aRangeList[i];
4644 assert(rRange.aStart.Tab() == rRange.aEnd.Tab());
4645 for (auto nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); nCol++)
4646 {
4647 SCROW nStartRow = rRange.aStart.Row();
4648 SCROW nLength = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
4649 if( mFromFirstRow )
4650 { // include also all previous rows
4651 nLength += nStartRow;
4652 nStartRow = 0;
4653 }
4654 if (!mrDoc.HandleRefArrayForParallelism(ScAddress(nCol, nStartRow, rRange.aStart.Tab()),
4655 nLength, mxGroup, pDirtiedAddress))
4656 return false;
4657 }
4658 }
4659
4660 if (bHasSelfReferences)
4661 mxGroup->mbPartOfCycle = true;
4662
4663 if (pSuccessfulDependencies && !bHasSelfReferences)
4664 *pSuccessfulDependencies = aRangeList;
4665
4666 return !bHasSelfReferences;
4667 }
4668 };
4669
4670 } // anonymous namespace
4671
InterpretFormulaGroup(SCROW nStartOffset,SCROW nEndOffset)4672 bool ScFormulaCell::InterpretFormulaGroup(SCROW nStartOffset, SCROW nEndOffset)
4673 {
4674 if (!mxGroup || !pCode)
4675 return false;
4676
4677 auto aScope = sc::FormulaLogger::get().enterGroup(rDocument, *this);
4678 ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4679
4680 if (mxGroup->mbPartOfCycle)
4681 {
4682 aScope.addMessage(u"This formula-group is part of a cycle"_ustr);
4683 return false;
4684 }
4685
4686 if (mxGroup->meCalcState == sc::GroupCalcDisabled)
4687 {
4688 static constexpr OUStringLiteral MESSAGE = u"group calc disabled";
4689 aScope.addMessage(MESSAGE);
4690 return false;
4691 }
4692
4693 // Use SC_FORCE_CALCULATION=opencl/threads to force calculation e.g. for unittests
4694 static ForceCalculationType forceType = ScCalcConfig::getForceCalculationType();
4695 if (forceType == ForceCalculationCore
4696 || ( GetWeight() < ScInterpreter::GetGlobalConfig().mnOpenCLMinimumFormulaGroupSize
4697 && forceType != ForceCalculationOpenCL
4698 && forceType != ForceCalculationThreads))
4699 {
4700 mxGroup->meCalcState = sc::GroupCalcDisabled;
4701 aScope.addGroupSizeThresholdMessage(*this);
4702 return false;
4703 }
4704
4705 if (cMatrixFlag != ScMatrixMode::NONE)
4706 {
4707 mxGroup->meCalcState = sc::GroupCalcDisabled;
4708 aScope.addMessage(u"matrix skipped"_ustr);
4709 return false;
4710 }
4711
4712 if( forceType != ForceCalculationNone )
4713 {
4714 // ScConditionEntry::Interpret() creates a temporary cell and interprets it
4715 // without it actually being in the document at the specified position.
4716 // That would confuse opencl/threading code, as they refer to the cell group
4717 // also using the position. This is normally not triggered (single cells
4718 // are normally not in a cell group), but if forced, check for this explicitly.
4719 if( rDocument.GetFormulaCell( aPos ) != this )
4720 {
4721 mxGroup->meCalcState = sc::GroupCalcDisabled;
4722 aScope.addMessage(u"cell not in document"_ustr);
4723 return false;
4724 }
4725 }
4726
4727 // Get rid of -1's in offsets (defaults) or any invalid offsets.
4728 SCROW nMaxOffset = mxGroup->mnLength - 1;
4729 nStartOffset = nStartOffset < 0 ? 0 : std::min(nStartOffset, nMaxOffset);
4730 nEndOffset = nEndOffset < 0 ? nMaxOffset : std::min(nEndOffset, nMaxOffset);
4731
4732 if (nEndOffset < nStartOffset)
4733 {
4734 nStartOffset = 0;
4735 nEndOffset = nMaxOffset;
4736 }
4737
4738 if (nEndOffset == nStartOffset && forceType == ForceCalculationNone)
4739 return false; // Do not use threads for a single row.
4740
4741 // Guard against endless recursion of Interpret() calls, for this to work
4742 // ScFormulaCell::InterpretFormulaGroup() must never be called through
4743 // anything else than ScFormulaCell::Interpret(), same as
4744 // ScFormulaCell::InterpretTail()
4745 RecursionCounter aRecursionCounter( rRecursionHelper, this);
4746
4747 bool bDependencyComputed = false;
4748 bool bDependencyCheckFailed = false;
4749
4750 // Preference order: First try OpenCL, then threading.
4751 // TODO: Do formula-group span computation for OCL too if nStartOffset/nEndOffset are non default.
4752 if( InterpretFormulaGroupOpenCL(aScope, bDependencyComputed, bDependencyCheckFailed))
4753 return true;
4754
4755 if( InterpretFormulaGroupThreading(aScope, bDependencyComputed, bDependencyCheckFailed, nStartOffset, nEndOffset))
4756 return true;
4757
4758 return false;
4759 }
4760
CheckComputeDependencies(sc::FormulaLogger::GroupScope & rScope,bool fromFirstRow,SCROW nStartOffset,SCROW nEndOffset,bool bCalcDependencyOnly,ScRangeList * pSuccessfulDependencies,ScAddress * pDirtiedAddress)4761 bool ScFormulaCell::CheckComputeDependencies(sc::FormulaLogger::GroupScope& rScope, bool fromFirstRow,
4762 SCROW nStartOffset, SCROW nEndOffset,
4763 bool bCalcDependencyOnly,
4764 ScRangeList* pSuccessfulDependencies,
4765 ScAddress* pDirtiedAddress)
4766 {
4767 ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4768 // iterate over code in the formula ...
4769 // ensure all input is pre-calculated -
4770 // to avoid writing during the calculation
4771 if (bCalcDependencyOnly)
4772 {
4773 // Let's not use "ScFormulaGroupDependencyComputeGuard" here as there is no corresponding
4774 // "ScFormulaGroupCycleCheckGuard" for this formula-group.
4775 // (We can only reach here from a multi-group dependency evaluation attempt).
4776 // (These two have to be in pairs always for any given formula-group)
4777 ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
4778 return aCalculator.DoIt(pSuccessfulDependencies, pDirtiedAddress);
4779 }
4780
4781 bool bOKToParallelize = false;
4782 {
4783 ScFormulaGroupCycleCheckGuard aCycleCheckGuard(rRecursionHelper, this);
4784 if (mxGroup->mbPartOfCycle)
4785 {
4786 mxGroup->meCalcState = sc::GroupCalcDisabled;
4787 rScope.addMessage(u"found circular formula-group dependencies"_ustr);
4788 return false;
4789 }
4790
4791 ScFormulaGroupDependencyComputeGuard aDepComputeGuard(rRecursionHelper);
4792 ScDependantsCalculator aCalculator(rDocument, *pCode, *this, mxGroup->mpTopCell->aPos, fromFirstRow, nStartOffset, nEndOffset);
4793 bOKToParallelize = aCalculator.DoIt(pSuccessfulDependencies, pDirtiedAddress);
4794
4795 }
4796
4797 if (rRecursionHelper.IsInRecursionReturn())
4798 {
4799 mxGroup->meCalcState = sc::GroupCalcDisabled;
4800 rScope.addMessage(u"Recursion limit reached, cannot thread this formula group now"_ustr);
4801 return false;
4802 }
4803
4804 if (mxGroup->mbPartOfCycle)
4805 {
4806 mxGroup->meCalcState = sc::GroupCalcDisabled;
4807 rScope.addMessage(u"found circular formula-group dependencies"_ustr);
4808 return false;
4809 }
4810
4811 if (!rRecursionHelper.AreGroupsIndependent())
4812 {
4813 // This call resulted from a dependency calculation for a multigroup-threading attempt,
4814 // but found dependency among the groups.
4815 rScope.addMessage(u"multi-group-dependency failed"_ustr);
4816 return false;
4817 }
4818
4819 if (!bOKToParallelize)
4820 {
4821 mxGroup->meCalcState = sc::GroupCalcDisabled;
4822 rScope.addMessage(u"could not do new dependencies calculation thing"_ustr);
4823 return false;
4824 }
4825
4826 return true;
4827 }
4828
lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef & xGroup,const ScDocument & rDoc,o3tl::sorted_vector<ScFormulaCellGroup * > & rFGSet,std::map<SCCOL,ScFormulaCell * > & rFGMap,bool bLeft)4829 static SCCOL lcl_probeLeftOrRightFGs(const ScFormulaCellGroupRef& xGroup, const ScDocument& rDoc,
4830 o3tl::sorted_vector<ScFormulaCellGroup*>& rFGSet,
4831 std::map<SCCOL, ScFormulaCell*>& rFGMap, bool bLeft)
4832 {
4833 const SCROW nLen = xGroup->mnLength;
4834 const sal_Int32 nWt = xGroup->mnWeight;
4835 ScAddress aAddr(xGroup->mpTopCell->aPos);
4836
4837 SCCOL nColRet = aAddr.Col();
4838
4839 const SCCOL nMaxCol = rDoc.GetAllocatedColumnsCount(aAddr.Tab()) - 1;
4840 if (bLeft)
4841 --nColRet;
4842 else
4843 ++nColRet;
4844
4845 while (nColRet >= 0 && nColRet <= nMaxCol)
4846 {
4847 aAddr.SetCol(nColRet);
4848 const ScFormulaCell* pCell = rDoc.GetFormulaCell(aAddr);
4849 if (!pCell)
4850 break;
4851
4852 if (!pCell->NeedsInterpret())
4853 break;
4854
4855 const ScFormulaCellGroupRef& xNGroup = pCell->GetCellGroup();
4856 if (!xNGroup)
4857 break;
4858
4859 if (!pCell->GetCode()->IsEnabledForThreading())
4860 break;
4861
4862 if (xNGroup->mpTopCell->aPos.Row() != aAddr.Row())
4863 break;
4864
4865 const SCROW nNLen = xNGroup->mnLength;
4866 const sal_Int32 nNWt = pCell->GetWeight();
4867 if (nNLen != nLen || nNWt != nWt)
4868 break;
4869
4870 rFGSet.insert(xNGroup.get());
4871 rFGMap[nColRet] = xNGroup->mpTopCell;
4872
4873 if (bLeft)
4874 --nColRet;
4875 else
4876 ++nColRet;
4877 }
4878
4879 if (bLeft)
4880 ++nColRet;
4881 else
4882 --nColRet;
4883
4884 return nColRet;
4885 }
4886
4887 // To be called only from InterpretFormulaGroup().
InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope & aScope,bool & bDependencyComputed,bool & bDependencyCheckFailed,SCROW nStartOffset,SCROW nEndOffset)4888 bool ScFormulaCell::InterpretFormulaGroupThreading(sc::FormulaLogger::GroupScope& aScope,
4889 bool& bDependencyComputed,
4890 bool& bDependencyCheckFailed,
4891 SCROW nStartOffset,
4892 SCROW nEndOffset)
4893 {
4894 static const bool bThreadingProhibited = std::getenv("SC_NO_THREADED_CALCULATION");
4895 if (!bDependencyCheckFailed && !bThreadingProhibited &&
4896 pCode->IsEnabledForThreading() &&
4897 ScCalcConfig::isThreadingEnabled())
4898 {
4899 ScRangeList aOrigDependencies;
4900 if(!bDependencyComputed && !CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset, false, &aOrigDependencies))
4901 {
4902 bDependencyComputed = true;
4903 bDependencyCheckFailed = true;
4904 return false;
4905 }
4906
4907 bDependencyComputed = true;
4908
4909 // Then do the threaded calculation
4910
4911 class Executor : public comphelper::ThreadTask
4912 {
4913 private:
4914 const unsigned mnThisThread;
4915 const unsigned mnThreadsTotal;
4916 ScDocument* mpDocument;
4917 ScInterpreterContext* mpContext;
4918 const ScAddress& mrTopPos;
4919 SCCOL mnStartCol;
4920 SCCOL mnEndCol;
4921 SCROW mnStartOffset;
4922 SCROW mnEndOffset;
4923
4924 public:
4925 Executor(const std::shared_ptr<comphelper::ThreadTaskTag>& rTag,
4926 unsigned nThisThread,
4927 unsigned nThreadsTotal,
4928 ScDocument* pDocument2,
4929 ScInterpreterContext* pContext,
4930 const ScAddress& rTopPos,
4931 SCCOL nStartCol,
4932 SCCOL nEndCol,
4933 SCROW nStartOff,
4934 SCROW nEndOff) :
4935 comphelper::ThreadTask(rTag),
4936 mnThisThread(nThisThread),
4937 mnThreadsTotal(nThreadsTotal),
4938 mpDocument(pDocument2),
4939 mpContext(pContext),
4940 mrTopPos(rTopPos),
4941 mnStartCol(nStartCol),
4942 mnEndCol(nEndCol),
4943 mnStartOffset(nStartOff),
4944 mnEndOffset(nEndOff)
4945 {
4946 }
4947
4948 virtual void doWork() override
4949 {
4950 ScRange aCalcRange(mnStartCol, mrTopPos.Row() + mnStartOffset, mrTopPos.Tab(),
4951 mnEndCol, mrTopPos.Row() + mnEndOffset, mrTopPos.Tab());
4952 mpDocument->CalculateInColumnInThread(*mpContext, aCalcRange, mnThisThread, mnThreadsTotal);
4953 }
4954
4955 };
4956
4957 SvNumberFormatter* pNonThreadedFormatter = rDocument.GetNonThreadedContext().GetFormatTable();
4958
4959 comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool());
4960 sal_Int32 nThreadCount = rThreadPool.getWorkerCount();
4961
4962 SAL_INFO("sc.threaded", "Running " << nThreadCount << " threads");
4963
4964 o3tl::sorted_vector<ScFormulaCellGroup*> aFGSet;
4965 std::map<SCCOL, ScFormulaCell*> aFGMap;
4966 aFGSet.insert(mxGroup.get());
4967
4968 ScRecursionHelper& rRecursionHelper = rDocument.GetRecursionHelper();
4969 SCCOL nColStart = aPos.Col();
4970 SCCOL nColEnd = nColStart;
4971 if (!rRecursionHelper.HasFormulaGroupSet() && rDocument.IsInDocShellRecalc())
4972 {
4973 nColStart = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, true);
4974 nColEnd = lcl_probeLeftOrRightFGs(mxGroup, rDocument, aFGSet, aFGMap, false);
4975 }
4976
4977 bool bFGOK = true;
4978 ScAddress aDirtiedAddress(ScAddress::INITIALIZE_INVALID);
4979 if (nColStart != nColEnd)
4980 {
4981 ScCheckIndependentFGGuard aGuard(rRecursionHelper, &aFGSet);
4982 for (SCCOL nCurrCol = nColStart; nCurrCol <= nColEnd; ++nCurrCol)
4983 {
4984 if (nCurrCol == aPos.Col())
4985 continue;
4986
4987 bFGOK = aFGMap[nCurrCol]->CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset,
4988 true, nullptr, &aDirtiedAddress);
4989 if (!bFGOK || !aGuard.AreGroupsIndependent())
4990 {
4991 nColEnd = nColStart = aPos.Col();
4992 break;
4993 }
4994 }
4995 }
4996
4997 // tdf#156677 it is possible that if a check of a column in the new range fails that the check has
4998 // now left a cell that the original range depended on in a Dirty state. So if the dirtied cell
4999 // was part of the original dependencies re-run the initial CheckComputeDependencies to fix it.
5000 if (!bFGOK && aDirtiedAddress.IsValid() && aOrigDependencies.Find(aDirtiedAddress))
5001 {
5002 SAL_WARN("sc.core.formulacell", "rechecking dependencies due to a dirtied cell during speculative probe");
5003 const bool bRedoEntryCheckSucceeded = CheckComputeDependencies(aScope, false, nStartOffset, nEndOffset);
5004 assert(bRedoEntryCheckSucceeded && "if it worked on the original range it should work again on that range");
5005 (void)bRedoEntryCheckSucceeded;
5006 }
5007
5008 std::vector<std::unique_ptr<ScInterpreter>> aInterpreters(nThreadCount);
5009 {
5010 assert(!rDocument.IsThreadedGroupCalcInProgress());
5011 rDocument.SetThreadedGroupCalcInProgress(true);
5012
5013 ScMutationDisable aGuard(rDocument, ScMutationGuardFlags::CORE);
5014
5015 // Here we turn off ref-counting for the contents of pCode on the basis
5016 // that pCode is not modified by interpreting and when interpreting is
5017 // complete all token refcounts will be back to their initial ref count
5018 FormulaToken** pArray = pCode->GetArray();
5019 for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
5020 pArray[i]->SetRefCntPolicy(RefCntPolicy::None);
5021
5022 // Start nThreadCount new threads
5023 std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag();
5024 ScThreadedInterpreterContextGetterGuard aContextGetterGuard(nThreadCount, rDocument, pNonThreadedFormatter);
5025 ScInterpreterContext* context = nullptr;
5026
5027 for (int i = 0; i < nThreadCount; ++i)
5028 {
5029 context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
5030 assert(!context->pInterpreter);
5031 aInterpreters[i].reset(new ScInterpreter(this, rDocument, *context, mxGroup->mpTopCell->aPos, *pCode, true));
5032 context->pInterpreter = aInterpreters[i].get();
5033 rDocument.SetupContextFromNonThreadedContext(*context, i);
5034 rThreadPool.pushTask(std::make_unique<Executor>(aTag, i, nThreadCount, &rDocument, context, mxGroup->mpTopCell->aPos,
5035 nColStart, nColEnd, nStartOffset, nEndOffset));
5036 }
5037
5038 SAL_INFO("sc.threaded", "Waiting for threads to finish work");
5039 // Do not join the threads here. They will get joined in ScDocument destructor
5040 // if they don't get joined from elsewhere before (via ThreadPool::waitUntilDone).
5041 rThreadPool.waitUntilDone(aTag, false);
5042
5043 // Drop any caches that reference Tokens before restoring ref counting policy
5044 for (int i = 0; i < nThreadCount; ++i)
5045 aInterpreters[i]->DropTokenCaches();
5046
5047 for (sal_uInt16 i = 0, n = pCode->GetLen(); i < n; ++i)
5048 pArray[i]->SetRefCntPolicy(RefCntPolicy::ThreadSafe);
5049
5050 rDocument.SetThreadedGroupCalcInProgress(false);
5051
5052 for (int i = 0; i < nThreadCount; ++i)
5053 {
5054 context = aContextGetterGuard.GetInterpreterContextForThreadIdx(i);
5055 // This is intentionally done in this main thread in order to avoid locking.
5056 rDocument.MergeContextBackIntoNonThreadedContext(*context, i);
5057 context->pInterpreter = nullptr;
5058 }
5059
5060 SAL_INFO("sc.threaded", "Done");
5061 }
5062
5063 ScAddress aStartPos(mxGroup->mpTopCell->aPos);
5064 SCROW nSpanLen = nEndOffset - nStartOffset + 1;
5065 aStartPos.SetRow(aStartPos.Row() + nStartOffset);
5066 // Reuse one of the previously allocated interpreter objects here.
5067 rDocument.HandleStuffAfterParallelCalculation(nColStart, nColEnd, aStartPos.Row(), nSpanLen,
5068 aStartPos.Tab(), aInterpreters[0].get());
5069
5070 return true;
5071 }
5072
5073 return false;
5074 }
5075
5076 // To be called only from InterpretFormulaGroup().
InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope & aScope,bool & bDependencyComputed,bool & bDependencyCheckFailed)5077 bool ScFormulaCell::InterpretFormulaGroupOpenCL(sc::FormulaLogger::GroupScope& aScope,
5078 bool& bDependencyComputed,
5079 bool& bDependencyCheckFailed)
5080 {
5081 bool bCanVectorize = pCode->IsEnabledForOpenCL();
5082 switch (pCode->GetVectorState())
5083 {
5084 case FormulaVectorEnabled:
5085 case FormulaVectorCheckReference:
5086 break;
5087
5088 // Not good.
5089 case FormulaVectorDisabledByOpCode:
5090 aScope.addMessage(u"group calc disabled due to vector state (non-vector-supporting opcode)"_ustr);
5091 break;
5092 case FormulaVectorDisabledByStackVariable:
5093 aScope.addMessage(u"group calc disabled due to vector state (non-vector-supporting stack variable)"_ustr);
5094 break;
5095 case FormulaVectorDisabledNotInSubSet:
5096 aScope.addMessage(u"group calc disabled due to vector state (opcode not in subset)"_ustr);
5097 break;
5098 case FormulaVectorDisabled:
5099 case FormulaVectorUnknown:
5100 default:
5101 aScope.addMessage(u"group calc disabled due to vector state (unknown)"_ustr);
5102 return false;
5103 }
5104
5105 if (!bCanVectorize)
5106 return false;
5107
5108 if (!ScCalcConfig::isOpenCLEnabled())
5109 {
5110 aScope.addMessage(u"opencl not enabled"_ustr);
5111 return false;
5112 }
5113
5114 // TableOp does tricks with using a cell with different values, just bail out.
5115 if(rDocument.IsInInterpreterTableOp())
5116 return false;
5117
5118 if (bDependencyCheckFailed)
5119 return false;
5120
5121 if(!bDependencyComputed && !CheckComputeDependencies(aScope, true, 0, mxGroup->mnLength - 1))
5122 {
5123 bDependencyComputed = true;
5124 bDependencyCheckFailed = true;
5125 return false;
5126 }
5127
5128 bDependencyComputed = true;
5129
5130 // TODO : Disable invariant formula group interpretation for now in order
5131 // to get implicit intersection to work.
5132 if (mxGroup->mbInvariant && false)
5133 return InterpretInvariantFormulaGroup();
5134
5135 int nMaxGroupLength = INT_MAX;
5136
5137 #ifdef _WIN32
5138 // Heuristic: Certain old low-end OpenCL implementations don't
5139 // work for us with too large group lengths. 1000 was determined
5140 // empirically to be a good compromise.
5141 if (openclwrapper::gpuEnv.mbNeedsTDRAvoidance)
5142 nMaxGroupLength = 1000;
5143 #endif
5144
5145 if (std::getenv("SC_MAX_GROUP_LENGTH"))
5146 nMaxGroupLength = std::atoi(std::getenv("SC_MAX_GROUP_LENGTH"));
5147
5148 int nNumOnePlus;
5149 const int nNumParts = splitup(GetSharedLength(), nMaxGroupLength, nNumOnePlus);
5150
5151 int nOffset = 0;
5152 int nCurChunkSize;
5153 ScAddress aOrigPos = mxGroup->mpTopCell->aPos;
5154 for (int i = 0; i < nNumParts; i++, nOffset += nCurChunkSize)
5155 {
5156 nCurChunkSize = GetSharedLength()/nNumParts + (i < nNumOnePlus ? 1 : 0);
5157
5158 ScFormulaCellGroupRef xGroup;
5159
5160 if (nNumParts == 1)
5161 xGroup = mxGroup;
5162 else
5163 {
5164 // Ugly hack
5165 xGroup = new ScFormulaCellGroup();
5166 xGroup->mpTopCell = mxGroup->mpTopCell;
5167 xGroup->mpTopCell->aPos = aOrigPos;
5168 xGroup->mpTopCell->aPos.IncRow(nOffset);
5169 xGroup->mbInvariant = mxGroup->mbInvariant;
5170 xGroup->mnLength = nCurChunkSize;
5171 xGroup->mpCode = std::move(mxGroup->mpCode); // temporarily transfer
5172 }
5173
5174 ScTokenArray aCode(rDocument);
5175 ScGroupTokenConverter aConverter(aCode, rDocument, *this, xGroup->mpTopCell->aPos);
5176 // TODO avoid this extra compilation
5177 ScCompiler aComp( rDocument, xGroup->mpTopCell->aPos, *pCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, true, cMatrixFlag != ScMatrixMode::NONE );
5178 aComp.CompileTokenArray();
5179 if (aComp.HasUnhandledPossibleImplicitIntersections() || !aConverter.convert(*pCode, aScope))
5180 {
5181 if(aComp.HasUnhandledPossibleImplicitIntersections())
5182 {
5183 SAL_INFO("sc.opencl", "group " << xGroup->mpTopCell->aPos << " has unhandled implicit intersections, disabling");
5184 #ifdef DBG_UTIL
5185 for( const OpCode opcode : aComp.UnhandledPossibleImplicitIntersectionsOpCodes())
5186 {
5187 SAL_INFO("sc.opencl", "unhandled implicit intersection opcode "
5188 << formula::FormulaCompiler().GetOpCodeMap(com::sun::star::sheet::FormulaLanguage::ENGLISH)->getSymbol(opcode)
5189 << "(" << int(opcode) << ")");
5190 }
5191 #endif
5192 }
5193 else
5194 SAL_INFO("sc.opencl", "conversion of group " << xGroup->mpTopCell->aPos << " failed, disabling");
5195
5196 mxGroup->meCalcState = sc::GroupCalcDisabled;
5197
5198 // Undo the hack above
5199 if (nNumParts > 1)
5200 {
5201 mxGroup->mpTopCell->aPos = aOrigPos;
5202 xGroup->mpTopCell = nullptr;
5203 mxGroup->mpCode = std::move(xGroup->mpCode);
5204 }
5205
5206 aScope.addMessage(u"group token conversion failed"_ustr);
5207 return false;
5208 }
5209
5210 // The converted code does not have RPN tokens yet. The interpreter will
5211 // generate them.
5212 xGroup->meCalcState = mxGroup->meCalcState = sc::GroupCalcRunning;
5213 sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic();
5214
5215 if (pInterpreter == nullptr ||
5216 !pInterpreter->interpret(rDocument, xGroup->mpTopCell->aPos, xGroup, aCode))
5217 {
5218 SAL_INFO("sc.opencl", "interpreting group " << mxGroup->mpTopCell->aPos
5219 << " (state " << static_cast<int>(mxGroup->meCalcState) << ") failed, disabling");
5220 mxGroup->meCalcState = sc::GroupCalcDisabled;
5221
5222 // Undo the hack above
5223 if (nNumParts > 1)
5224 {
5225 mxGroup->mpTopCell->aPos = aOrigPos;
5226 xGroup->mpTopCell = nullptr;
5227 mxGroup->mpCode = std::move(xGroup->mpCode);
5228 }
5229
5230 aScope.addMessage(u"group interpretation unsuccessful"_ustr);
5231 return false;
5232 }
5233
5234 aScope.setCalcComplete();
5235
5236 if (nNumParts > 1)
5237 {
5238 xGroup->mpTopCell = nullptr;
5239 mxGroup->mpCode = std::move(xGroup->mpCode);
5240 }
5241 }
5242
5243 if (nNumParts > 1)
5244 mxGroup->mpTopCell->aPos = aOrigPos;
5245 mxGroup->meCalcState = sc::GroupCalcEnabled;
5246 return true;
5247 }
5248
InterpretInvariantFormulaGroup()5249 bool ScFormulaCell::InterpretInvariantFormulaGroup()
5250 {
5251 if (pCode->GetVectorState() == FormulaVectorCheckReference)
5252 {
5253 // An invariant group should only have absolute row references, and no
5254 // external references are allowed.
5255
5256 ScTokenArray aCode(rDocument);
5257 FormulaTokenArrayPlainIterator aIter(*pCode);
5258 for (const formula::FormulaToken* p = aIter.First(); p; p = aIter.Next())
5259 {
5260 switch (p->GetType())
5261 {
5262 case svSingleRef:
5263 {
5264 ScSingleRefData aRef = *p->GetSingleRef();
5265 ScAddress aRefPos = aRef.toAbs(rDocument, aPos);
5266 formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefPos);
5267 if (!pNewToken)
5268 return false;
5269
5270 aCode.AddToken(*pNewToken);
5271 }
5272 break;
5273 case svDoubleRef:
5274 {
5275 ScComplexRefData aRef = *p->GetDoubleRef();
5276 ScRange aRefRange = aRef.toAbs(rDocument, aPos);
5277 formula::FormulaTokenRef pNewToken = rDocument.ResolveStaticReference(aRefRange);
5278 if (!pNewToken)
5279 return false;
5280
5281 aCode.AddToken(*pNewToken);
5282 }
5283 break;
5284 default:
5285 aCode.AddToken(*p);
5286 }
5287 }
5288
5289 ScCompiler aComp(rDocument, aPos, aCode, rDocument.GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE);
5290 aComp.CompileTokenArray(); // Create RPN token array.
5291 ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, aCode);
5292 aInterpreter.Interpret();
5293 aResult.SetToken(aInterpreter.GetResultToken().get());
5294 }
5295 else
5296 {
5297 // Formula contains no references.
5298 ScInterpreter aInterpreter(this, rDocument, rDocument.GetNonThreadedContext(), aPos, *pCode);
5299 aInterpreter.Interpret();
5300 aResult.SetToken(aInterpreter.GetResultToken().get());
5301 }
5302
5303 for ( sal_Int32 i = 0; i < mxGroup->mnLength; i++ )
5304 {
5305 ScAddress aTmpPos = aPos;
5306 aTmpPos.SetRow(mxGroup->mpTopCell->aPos.Row() + i);
5307 ScFormulaCell* pCell = rDocument.GetFormulaCell(aTmpPos);
5308 if (!pCell)
5309 {
5310 SAL_WARN("sc.core.formulacell", "GetFormulaCell not found");
5311 continue;
5312 }
5313
5314 // FIXME: this set of horrors is unclear to me ... certainly
5315 // the above GetCell is profoundly nasty & slow ...
5316 // Ensure the cell truly has a result:
5317 pCell->aResult = aResult;
5318 pCell->ResetDirty();
5319 pCell->SetChanged(true);
5320 }
5321
5322 return true;
5323 }
5324
5325 namespace {
5326
startListeningArea(ScFormulaCell * pCell,ScDocument & rDoc,const ScAddress & rPos,const formula::FormulaToken & rToken)5327 void startListeningArea(
5328 ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
5329 {
5330 const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
5331 const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
5332 ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
5333 ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
5334 if (!(aCell1.IsValid() && aCell2.IsValid()))
5335 return;
5336
5337 if (rToken.GetOpCode() == ocColRowNameAuto)
5338 { // automagically
5339 if ( rRef1.IsColRel() )
5340 { // ColName
5341 aCell2.SetRow(rDoc.MaxRow());
5342 }
5343 else
5344 { // RowName
5345 aCell2.SetCol(rDoc.MaxCol());
5346 }
5347 }
5348 rDoc.StartListeningArea(ScRange(aCell1, aCell2), false, pCell);
5349 }
5350
5351 }
5352
StartListeningTo(ScDocument & rDoc)5353 void ScFormulaCell::StartListeningTo( ScDocument& rDoc )
5354 {
5355 if (mxGroup)
5356 mxGroup->endAllGroupListening(rDoc);
5357
5358 if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
5359 return;
5360
5361 rDoc.SetDetectiveDirty(true); // It has changed something
5362
5363 ScTokenArray* pArr = GetCode();
5364 if( pArr->IsRecalcModeAlways() )
5365 {
5366 rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
5367 SetNeedsListening( false);
5368 return;
5369 }
5370
5371 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5372 formula::FormulaToken* t;
5373 while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5374 {
5375 switch (t->GetType())
5376 {
5377 case svSingleRef:
5378 {
5379 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
5380 if (aCell.IsValid())
5381 rDoc.StartListeningCell(aCell, this);
5382 }
5383 break;
5384 case svDoubleRef:
5385 startListeningArea(this, rDoc, aPos, *t);
5386 break;
5387 default:
5388 ; // nothing
5389 }
5390 }
5391 SetNeedsListening( false);
5392 }
5393
StartListeningTo(sc::StartListeningContext & rCxt)5394 void ScFormulaCell::StartListeningTo( sc::StartListeningContext& rCxt )
5395 {
5396 ScDocument& rDoc = rCxt.getDoc();
5397
5398 if (mxGroup)
5399 mxGroup->endAllGroupListening(rDoc);
5400
5401 if (rDoc.IsClipOrUndo() || rDoc.GetNoListening() || IsInChangeTrack())
5402 return;
5403
5404 rDoc.SetDetectiveDirty(true); // It has changed something
5405
5406 ScTokenArray* pArr = GetCode();
5407 if( pArr->IsRecalcModeAlways() )
5408 {
5409 rDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, this);
5410 SetNeedsListening( false);
5411 return;
5412 }
5413
5414 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5415 formula::FormulaToken* t;
5416 while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5417 {
5418 switch (t->GetType())
5419 {
5420 case svSingleRef:
5421 {
5422 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aPos);
5423 if (aCell.IsValid())
5424 rDoc.StartListeningCell(rCxt, aCell, *this);
5425 }
5426 break;
5427 case svDoubleRef:
5428 startListeningArea(this, rDoc, aPos, *t);
5429 break;
5430 default:
5431 ; // nothing
5432 }
5433 }
5434 SetNeedsListening( false);
5435 }
5436
5437 namespace {
5438
endListeningArea(ScFormulaCell * pCell,ScDocument & rDoc,const ScAddress & rPos,const formula::FormulaToken & rToken)5439 void endListeningArea(
5440 ScFormulaCell* pCell, ScDocument& rDoc, const ScAddress& rPos, const formula::FormulaToken& rToken)
5441 {
5442 const ScSingleRefData& rRef1 = *rToken.GetSingleRef();
5443 const ScSingleRefData& rRef2 = *rToken.GetSingleRef2();
5444 ScAddress aCell1 = rRef1.toAbs(rDoc, rPos);
5445 ScAddress aCell2 = rRef2.toAbs(rDoc, rPos);
5446 if (!(aCell1.IsValid() && aCell2.IsValid()))
5447 return;
5448
5449 if (rToken.GetOpCode() == ocColRowNameAuto)
5450 { // automagically
5451 if ( rRef1.IsColRel() )
5452 { // ColName
5453 aCell2.SetRow(rDoc.MaxRow());
5454 }
5455 else
5456 { // RowName
5457 aCell2.SetCol(rDoc.MaxCol());
5458 }
5459 }
5460
5461 rDoc.EndListeningArea(ScRange(aCell1, aCell2), false, pCell);
5462 }
5463
5464 }
5465
EndListeningTo(ScDocument & rDoc,ScTokenArray * pArr,ScAddress aCellPos)5466 void ScFormulaCell::EndListeningTo( ScDocument& rDoc, ScTokenArray* pArr,
5467 ScAddress aCellPos )
5468 {
5469 if (mxGroup)
5470 mxGroup->endAllGroupListening(rDoc);
5471
5472 if (rDoc.IsClipOrUndo() || IsInChangeTrack())
5473 return;
5474
5475 if (!HasBroadcaster())
5476 return;
5477
5478 rDoc.SetDetectiveDirty(true); // It has changed something
5479
5480 if ( GetCode()->IsRecalcModeAlways() )
5481 {
5482 rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
5483 return;
5484 }
5485
5486 if (!pArr)
5487 {
5488 pArr = GetCode();
5489 aCellPos = aPos;
5490 }
5491 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5492 formula::FormulaToken* t;
5493 while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5494 {
5495 switch (t->GetType())
5496 {
5497 case svSingleRef:
5498 {
5499 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
5500 if (aCell.IsValid())
5501 rDoc.EndListeningCell(aCell, this);
5502 }
5503 break;
5504 case svDoubleRef:
5505 endListeningArea(this, rDoc, aCellPos, *t);
5506 break;
5507 default:
5508 ; // nothing
5509 }
5510 }
5511 }
5512
EndListeningTo(sc::EndListeningContext & rCxt)5513 void ScFormulaCell::EndListeningTo( sc::EndListeningContext& rCxt )
5514 {
5515 if (mxGroup)
5516 mxGroup->endAllGroupListening(rCxt.getDoc());
5517
5518 if (rCxt.getDoc().IsClipOrUndo() || IsInChangeTrack())
5519 return;
5520
5521 if (!HasBroadcaster())
5522 return;
5523
5524 ScDocument& rDoc = rCxt.getDoc();
5525 rDoc.SetDetectiveDirty(true); // It has changed something
5526
5527 ScTokenArray* pArr = rCxt.getOldCode();
5528 ScAddress aCellPos = rCxt.getOldPosition(aPos);
5529 if (!pArr)
5530 pArr = pCode;
5531
5532 if (pArr->IsRecalcModeAlways())
5533 {
5534 rDoc.EndListeningArea(BCA_LISTEN_ALWAYS, false, this);
5535 return;
5536 }
5537
5538 formula::FormulaTokenArrayPlainIterator aIter(*pArr);
5539 formula::FormulaToken* t;
5540 while ( ( t = aIter.GetNextReferenceRPN() ) != nullptr )
5541 {
5542 switch (t->GetType())
5543 {
5544 case svSingleRef:
5545 {
5546 ScAddress aCell = t->GetSingleRef()->toAbs(rDocument, aCellPos);
5547 if (aCell.IsValid())
5548 rDoc.EndListeningCell(rCxt, aCell, *this);
5549 }
5550 break;
5551 case svDoubleRef:
5552 endListeningArea(this, rDoc, aCellPos, *t);
5553 break;
5554 default:
5555 ; // nothing
5556 }
5557 }
5558 }
5559
IsShared() const5560 bool ScFormulaCell::IsShared() const
5561 {
5562 return bool(mxGroup);
5563 }
5564
IsSharedTop() const5565 bool ScFormulaCell::IsSharedTop() const
5566 {
5567 if (!mxGroup)
5568 return false;
5569
5570 return mxGroup->mpTopCell == this;
5571 }
5572
GetSharedTopRow() const5573 SCROW ScFormulaCell::GetSharedTopRow() const
5574 {
5575 return mxGroup ? mxGroup->mpTopCell->aPos.Row() : -1;
5576 }
5577
GetSharedLength() const5578 SCROW ScFormulaCell::GetSharedLength() const
5579 {
5580 return mxGroup ? mxGroup->mnLength : 0;
5581 }
5582
GetWeight() const5583 sal_Int32 ScFormulaCell::GetWeight() const
5584 {
5585 if (!mxGroup)
5586 return 1;
5587
5588 if (mxGroup->mnWeight > 0)
5589 return mxGroup->mnWeight;
5590
5591 double nSharedCodeWeight = GetSharedCode()->GetWeight();
5592 double nResult = nSharedCodeWeight * GetSharedLength();
5593 if (nResult < SAL_MAX_INT32)
5594 mxGroup->mnWeight = nResult;
5595 else
5596 mxGroup->mnWeight = SAL_MAX_INT32;
5597
5598 return mxGroup->mnWeight;
5599 }
5600
GetSharedCode()5601 ScTokenArray* ScFormulaCell::GetSharedCode()
5602 {
5603 return mxGroup ? &*mxGroup->mpCode : nullptr;
5604 }
5605
GetSharedCode() const5606 const ScTokenArray* ScFormulaCell::GetSharedCode() const
5607 {
5608 return mxGroup ? &*mxGroup->mpCode : nullptr;
5609 }
5610
SyncSharedCode()5611 void ScFormulaCell::SyncSharedCode()
5612 {
5613 if (!mxGroup)
5614 // Not a shared formula cell.
5615 return;
5616
5617 pCode = &*mxGroup->mpCode;
5618 }
5619
5620 #if DUMP_COLUMN_STORAGE
5621
Dump() const5622 void ScFormulaCell::Dump() const
5623 {
5624 cout << "-- formula cell (" << aPos.Format(ScRefFlags::VALID | ScRefFlags::TAB_3D, &rDocument) << ")" << endl;
5625 cout << " * shared: " << (mxGroup ? "true" : "false") << endl;
5626 if (mxGroup)
5627 {
5628 cout << " * shared length: " << mxGroup->mnLength << endl;
5629 cout << " * shared calc state: " << mxGroup->meCalcState << endl;
5630 }
5631
5632 sc::TokenStringContext aCxt(rDocument, rDocument.GetGrammar());
5633 cout << " * code: " << pCode->CreateString(aCxt, aPos) << endl;
5634
5635 FormulaError nErrCode = pCode->GetCodeError();
5636 cout << " * code error: ";
5637 if (nErrCode == FormulaError::NONE)
5638 cout << "(none)";
5639 else
5640 {
5641 OUString aStr = ScGlobal::GetErrorString(nErrCode);
5642 cout << " * code error: " << aStr << " (" << int(nErrCode) << ")";
5643 }
5644 cout << endl;
5645
5646 cout << " * result: ";
5647 sc::FormulaResultValue aRV = aResult.GetResult();
5648 switch (aRV.meType)
5649 {
5650 case sc::FormulaResultValue::Value:
5651 cout << aRV.mfValue << " (value)";
5652 break;
5653 case sc::FormulaResultValue::String:
5654 cout << aRV.maString.getString() << " (string)";
5655 break;
5656 case sc::FormulaResultValue::Error:
5657 cout << ScGlobal::GetErrorString(aRV.mnError) << " (error: " << int(aRV.mnError) << ")";
5658 break;
5659 case sc::FormulaResultValue::Invalid:
5660 cout << "(invalid)";
5661 break;
5662 default:
5663 ;
5664 }
5665 cout << endl;
5666 }
5667
5668 #endif
5669
5670 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
5671