diff options
-rw-r--r-- | formula/source/core/api/FormulaCompiler.cxx | 24 | ||||
-rw-r--r-- | formula/source/ui/dlg/formula.cxx | 2 | ||||
-rw-r--r-- | include/formula/FormulaCompiler.hxx | 16 | ||||
-rw-r--r-- | sc/inc/compiler.hxx | 16 | ||||
-rw-r--r-- | sc/qa/unit/ucalc.hxx | 10 | ||||
-rw-r--r-- | sc/qa/unit/ucalc_formula.cxx | 197 | ||||
-rw-r--r-- | sc/source/core/data/column.cxx | 3 | ||||
-rw-r--r-- | sc/source/core/data/column4.cxx | 3 | ||||
-rw-r--r-- | sc/source/core/data/formulacell.cxx | 60 | ||||
-rw-r--r-- | sc/source/core/data/simpleformulacalc.cxx | 2 | ||||
-rw-r--r-- | sc/source/core/tool/compiler.cxx | 178 | ||||
-rw-r--r-- | sc/source/core/tool/interpr4.cxx | 53 | ||||
-rw-r--r-- | sc/source/filter/oox/formulabuffer.cxx | 4 | ||||
-rw-r--r-- | sc/source/ui/view/viewfunc.cxx | 2 |
14 files changed, 449 insertions, 121 deletions
diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx index f5e06e77deba..671d2c2b9f62 100644 --- a/formula/source/core/api/FormulaCompiler.cxx +++ b/formula/source/core/api/FormulaCompiler.cxx @@ -697,7 +697,7 @@ void FormulaCompiler::OpCodeMap::putOpCode( const OUString & rStr, const OpCode // class FormulaCompiler -FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr ) +FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr, bool bComputeII, bool bMatrixFlag ) : nCurrentFactorParam(0), pArr( &rArr ), @@ -714,13 +714,15 @@ FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr ) glSubTotal( false ), needsRPNTokenCheck( false ), mbJumpCommandReorder(true), - mbStopOnError(true) + mbStopOnError(true), + mbComputeII(bComputeII), + mbMatrixFlag(bMatrixFlag) { } FormulaTokenArray FormulaCompiler::smDummyTokenArray; -FormulaCompiler::FormulaCompiler() +FormulaCompiler::FormulaCompiler(bool bComputeII, bool bMatrixFlag) : nCurrentFactorParam(0), pArr( nullptr ), @@ -737,7 +739,9 @@ FormulaCompiler::FormulaCompiler() glSubTotal( false ), needsRPNTokenCheck( false ), mbJumpCommandReorder(true), - mbStopOnError(true) + mbStopOnError(true), + mbComputeII(bComputeII), + mbMatrixFlag(bMatrixFlag) { } @@ -1549,6 +1553,7 @@ void FormulaCompiler::Factor() else { // standard handling of 1-parameter opcodes + OpCode eMyLastOp = eOp; pFacToken = mpToken; eOp = NextToken(); if( nNumFmt == SvNumFormatType::UNDEFINED && eOp == ocNot ) @@ -1564,7 +1569,14 @@ void FormulaCompiler::Factor() if (eOp != ocClose) SetError( FormulaError::PairExpected); else if ( pArr->GetCodeError() == FormulaError::NONE ) + { pFacToken->SetByte( 1 ); + if (mbComputeII && IsIIOpCode(eMyLastOp)) + { + FormulaToken** pArg = pCode - 1; + HandleIIOpCode(eMyLastOp, pFacToken->GetInForceArray(), &pArg, 1); + } + } PutCode( pFacToken ); NextToken(); } @@ -1606,7 +1618,7 @@ void FormulaCompiler::Factor() sal_uInt32 nSepCount = 0; if( !bNoParam ) { - bool bDoIICompute = IsIIOpCode(eMyLastOp); + bool bDoIICompute = mbComputeII && IsIIOpCode(eMyLastOp); // Array of FormulaToken double pointers to collect the parameters of II opcodes. FormulaToken*** pArgArray = nullptr; if (bDoIICompute) @@ -1633,7 +1645,7 @@ void FormulaCompiler::Factor() pArgArray[nSepCount - 1] = pCode - 1; // Add rest of the arguments } if (bDoIICompute) - HandleIIOpCode(eMyLastOp, pArgArray, + HandleIIOpCode(eMyLastOp, pFacToken->GetInForceArray(), pArgArray, std::min(nSepCount, static_cast<sal_uInt32>(FORMULA_MAXPARAMSII))); } if (bBadName) diff --git a/formula/source/ui/dlg/formula.cxx b/formula/source/ui/dlg/formula.cxx index e5b07260915a..5abc04622e15 100644 --- a/formula/source/ui/dlg/formula.cxx +++ b/formula/source/ui/dlg/formula.cxx @@ -831,6 +831,8 @@ void FormulaDlg_Impl::UpdateTokenArray( const OUString& rStrExp) // #i101512# Disable special handling of jump commands. pCompiler->EnableJumpCommandReorder(false); pCompiler->EnableStopOnError(false); + pCompiler->SetComputeIIFlag(true); + pCompiler->SetMatrixFlag(m_bUserMatrixFlag); pCompiler->CompileTokenArray(); } diff --git a/include/formula/FormulaCompiler.hxx b/include/formula/FormulaCompiler.hxx index 321e288fcc55..1f0bb0d95c5b 100644 --- a/include/formula/FormulaCompiler.hxx +++ b/include/formula/FormulaCompiler.hxx @@ -76,8 +76,8 @@ private: FormulaCompiler(const FormulaCompiler&) = delete; FormulaCompiler& operator=(const FormulaCompiler&) = delete; public: - FormulaCompiler(); - FormulaCompiler(FormulaTokenArray& _rArr); + FormulaCompiler(bool bComputeII = false, bool bMatrixFlag = false); + FormulaCompiler(FormulaTokenArray& _rArr, bool bComputeII = false, bool bMatrixFlag = false); virtual ~FormulaCompiler(); /** Mappings from strings to OpCodes and vice versa. */ @@ -269,6 +269,12 @@ public: static void ResetNativeSymbols(); static void SetNativeSymbols( const OpCodeMapPtr& xMap ); + /** Sets the implicit intersection compute flag */ + void SetComputeIIFlag(bool bSet) { mbComputeII = bSet; } + + /** Sets the matrix flag for the formula*/ + void SetMatrixFlag(bool bSet) { mbMatrixFlag = bSet; } + /** Separators mapped when loading opcodes from the resource, values other than RESOURCE_BASE may override the resource strings. Used by OpCodeList implementation via loadSymbols(). @@ -327,7 +333,8 @@ protected: // This is no-op for this class. virtual bool IsIIOpCode(OpCode /*nOpCode*/) const { return false; } // Handles II opcode and passes the parameter array and number of parameters. - virtual void HandleIIOpCode(OpCode /*nOpCode*/, FormulaToken*** /*pppToken*/, sal_uInt8 /*nNumParams*/) {} + virtual void HandleIIOpCode(OpCode /*nOpCode*/, formula::ParamClass /*eClass*/, + FormulaToken*** /*pppToken*/, sal_uInt8 /*nNumParams*/) {} OUString aCorrectedFormula; // autocorrected Formula OUString aCorrectedSymbol; // autocorrected Symbol @@ -359,6 +366,9 @@ protected: bool mbJumpCommandReorder; /// Whether or not to reorder RPN for jump commands. bool mbStopOnError; /// Whether to stop compilation on first encountered error. + bool mbComputeII; // whether to attempt computing implicit intersection ranges while building the RPN array. + bool mbMatrixFlag; // whether the formula is a matrix formula (needed for II computation) + private: void InitSymbolsNative() const; /// only SymbolsNative, on first document creation void InitSymbolsEnglish() const; /// only SymbolsEnglish, maybe later diff --git a/sc/inc/compiler.hxx b/sc/inc/compiler.hxx index ffd783a32506..767d08b36e62 100644 --- a/sc/inc/compiler.hxx +++ b/sc/inc/compiler.hxx @@ -335,24 +335,25 @@ private: static void InitCharClassEnglish(); public: - ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, const ScInterpreterContext* pContext = nullptr ); + ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, + bool bComputeII = false, bool bMatrixFlag = false, const ScInterpreterContext* pContext = nullptr ); /** If eGrammar == GRAM_UNSPECIFIED then the grammar of pDocument is used, if pDocument==nullptr then GRAM_DEFAULT. */ ScCompiler( ScDocument* pDocument, const ScAddress&, formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_UNSPECIFIED, - const ScInterpreterContext* pContext = nullptr ); + bool bComputeII = false, bool bMatrixFlag = false, const ScInterpreterContext* pContext = nullptr ); ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr, - const ScInterpreterContext* pContext = nullptr ); + bool bComputeII = false, bool bMatrixFlag = false, const ScInterpreterContext* pContext = nullptr ); /** If eGrammar == GRAM_UNSPECIFIED then the grammar of pDocument is used, if pDocument==nullptr then GRAM_DEFAULT. */ ScCompiler( ScDocument* pDocument, const ScAddress&, ScTokenArray& rArr, formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_UNSPECIFIED, - const ScInterpreterContext* pContext = nullptr ); + bool bComputeII = false, bool bMatrixFlag = false, const ScInterpreterContext* pContext = nullptr ); virtual ~ScCompiler() override; @@ -451,6 +452,10 @@ public: static bool IsCharFlagAllConventions( OUString const & rStr, sal_Int32 nPos, ScCharFlags nFlags ); + /** TODO : Move this to somewhere appropriate. */ + static bool DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, + const ScAddress& rFormulaPos); + private: // FormulaCompiler virtual OUString FindAddInFunction( const OUString& rUpperName, bool bLocalFirst ) const override; @@ -480,7 +485,8 @@ private: { return c < 128 ? pConv->getCharTableFlags(c, cLast) : ScCharFlags::NONE; } bool IsIIOpCode(OpCode nOpCode) const override; - void HandleIIOpCode(OpCode nOpCode, formula::FormulaToken*** pppToken, sal_uInt8 nNumParams) override; + void HandleIIOpCode(OpCode nOpCode, formula::ParamClass eClass, formula::FormulaToken*** pppToken, sal_uInt8 nNumParams) override; + void ReplaceDoubleRefII(formula::FormulaToken** ppDoubleRefTok); bool AdjustSumRangeShape(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange); void CorrectSumRange(const ScComplexRefData& rBaseRange, ScComplexRefData& rSumRange, formula::FormulaToken** ppSumRangeToken); }; diff --git a/sc/qa/unit/ucalc.hxx b/sc/qa/unit/ucalc.hxx index f1d6acb476eb..03305a8ad74e 100644 --- a/sc/qa/unit/ucalc.hxx +++ b/sc/qa/unit/ucalc.hxx @@ -143,7 +143,10 @@ public: void testFormulaRefData(); void testFormulaCompiler(); void testFormulaCompilerJumpReordering(); - void testFormulaCompilerImplicitIntersection(); + void testFormulaCompilerImplicitIntersection2Param(); + void testFormulaCompilerImplicitIntersection1ParamNoChange(); + void testFormulaCompilerImplicitIntersection1ParamWithChange(); + void testFormulaCompilerImplicitIntersection1NoGroup(); void testFormulaRefUpdate(); void testFormulaRefUpdateRange(); void testFormulaRefUpdateSheets(); @@ -568,7 +571,10 @@ public: CPPUNIT_TEST(testFormulaRefData); CPPUNIT_TEST(testFormulaCompiler); CPPUNIT_TEST(testFormulaCompilerJumpReordering); - CPPUNIT_TEST(testFormulaCompilerImplicitIntersection); + CPPUNIT_TEST(testFormulaCompilerImplicitIntersection2Param); + CPPUNIT_TEST(testFormulaCompilerImplicitIntersection1ParamNoChange); + CPPUNIT_TEST(testFormulaCompilerImplicitIntersection1ParamWithChange); + CPPUNIT_TEST(testFormulaCompilerImplicitIntersection1NoGroup); CPPUNIT_TEST(testFormulaRefUpdate); CPPUNIT_TEST(testFormulaRefUpdateRange); CPPUNIT_TEST(testFormulaRefUpdateSheets); diff --git a/sc/qa/unit/ucalc_formula.cxx b/sc/qa/unit/ucalc_formula.cxx index 126bed6c9289..5900e3555ada 100644 --- a/sc/qa/unit/ucalc_formula.cxx +++ b/sc/qa/unit/ucalc_formula.cxx @@ -1105,7 +1105,7 @@ void Test::testFormulaCompilerJumpReordering() } } -void Test::testFormulaCompilerImplicitIntersection() +void Test::testFormulaCompilerImplicitIntersection2Param() { struct TestCaseFormula { @@ -1231,6 +1231,201 @@ void Test::testFormulaCompilerImplicitIntersection() } } +void Test::testFormulaCompilerImplicitIntersection1ParamNoChange() +{ + struct TestCaseFormulaNoChange + { + OUString aFormula; + ScAddress aCellAddress; + bool bMatrixFormula; + bool bForcedArray; + }; + + m_pDoc->InsertTab(0, "Formula"); + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + { + ScAddress aStartAddr(4, 5, 0); + TestCaseFormulaNoChange aCasesNoChange[] = + { + { + OUString("=COS(A$2:A$100)"), // No change because of abs col ref. + aStartAddr, + false, + false + }, + { + OUString("=COS($A7:$A100)"), // No intersection + aStartAddr, + false, + false + }, + { + OUString("=COS($A5:$C7)"), // No intersection 2-D range + aStartAddr, + false, + false + }, + { + OUString("=SUMPRODUCT(COS(A6:A10))"), // COS() in forced array mode + aStartAddr, + false, + true + }, + { + OUString("=COS(A6:A10)"), // Matrix formula + aStartAddr, + true, + false + } + }; + + for (auto& rCase : aCasesNoChange) + { + if (rCase.bMatrixFormula) + { + ScMarkData aMark; + aMark.SelectOneTable(0); + SCCOL nColStart = rCase.aCellAddress.Col(); + SCROW nRowStart = rCase.aCellAddress.Row(); + m_pDoc->InsertMatrixFormula(nColStart, nRowStart, nColStart, nRowStart + 4, + aMark, rCase.aFormula); + } + else + m_pDoc->SetString(rCase.aCellAddress, rCase.aFormula); + + const ScFormulaCell* pCell = m_pDoc->GetFormulaCell(rCase.aCellAddress); + const ScTokenArray* pCode = pCell->GetCode(); + CPPUNIT_ASSERT(pCode); + + sal_uInt16 nRPNLen = pCode->GetCodeLen(); + sal_uInt16 nRawLen = pCode->GetLen(); + sal_uInt16 nRawArgPos; + if (rCase.bForcedArray) + { + nRawArgPos = 4; + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong raw token count.", static_cast<sal_uInt16>(7), nRawLen); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong RPN token count.", static_cast<sal_uInt16>(3), nRPNLen); + } + else + { + nRawArgPos = 2; + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong raw token count.", static_cast<sal_uInt16>(4), nRawLen); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong RPN token count.", static_cast<sal_uInt16>(2), nRPNLen); + } + + FormulaToken** ppRawTokens = pCode->GetArray(); + FormulaToken** ppRPNTokens = pCode->GetCode(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong type of raw token(argument to COS)", svDoubleRef, ppRawTokens[nRawArgPos]->GetType()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong type of RPN token(argument to COS)", svDoubleRef, ppRPNTokens[0]->GetType()); + + ScComplexRefData aArgRangeRaw = *ppRawTokens[nRawArgPos]->GetDoubleRef(); + ScComplexRefData aArgRangeRPN = *ppRPNTokens[0]->GetDoubleRef(); + bool bRawMatchRPNToken(aArgRangeRaw == aArgRangeRPN); + CPPUNIT_ASSERT_MESSAGE("raw arg token and RPN arg token contents do not match", bRawMatchRPNToken); + } + } +} + +void Test::testFormulaCompilerImplicitIntersection1ParamWithChange() +{ + struct TestCaseFormula + { + OUString aFormula; + ScAddress aCellAddress; + ScAddress aArgAddr; + }; + + m_pDoc->InsertTab(0, "Formula"); + m_pDoc->InsertTab(1, "Formula1"); + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + { + ScAddress aStartAddr(10, 5, 0); + TestCaseFormula aCasesWithChange[] = + { + { + OUString("=COS($A6:$A100)"), // Corner case with intersection + aStartAddr, + ScAddress(0, 5, 0) + }, + { + OUString("=COS($A2:$A6)"), // Corner case with intersection + aStartAddr, + ScAddress(0, 5, 0) + }, + { + OUString("=COS($A2:$A100)"), // Typical 1D case + aStartAddr, + ScAddress(0, 5, 0) + }, + { + OUString("=COS($Formula.$A1:$C3)"), // 2D corner case + ScAddress(0, 0, 1), // Formula in sheet 1 + ScAddress(0, 0, 0) + }, + { + OUString("=COS($Formula.$A1:$C3)"), // 2D corner case + ScAddress(0, 2, 1), // Formula in sheet 1 + ScAddress(0, 2, 0) + }, + { + OUString("=COS($Formula.$A1:$C3)"), // 2D corner case + ScAddress(2, 0, 1), // Formula in sheet 1 + ScAddress(2, 0, 0) + }, + { + OUString("=COS($Formula.$A1:$C3)"), // 2D corner case + ScAddress(2, 2, 1), // Formula in sheet 1 + ScAddress(2, 2, 0) + }, + { + OUString("=COS($Formula.$A1:$C3)"), // Typical 2D case + ScAddress(1, 1, 1), // Formula in sheet 1 + ScAddress(1, 1, 0) + } + }; + + for (auto& rCase : aCasesWithChange) + { + m_pDoc->SetString(rCase.aCellAddress, rCase.aFormula); + + const ScFormulaCell* pCell = m_pDoc->GetFormulaCell(rCase.aCellAddress); + const ScTokenArray* pCode = pCell->GetCode(); + CPPUNIT_ASSERT(pCode); + + sal_uInt16 nRPNLen = pCode->GetCodeLen(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong RPN token count.", static_cast<sal_uInt16>(2), nRPNLen); + + FormulaToken** ppRPNTokens = pCode->GetCode(); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong type of RPN token(argument to COS)", svSingleRef, ppRPNTokens[0]->GetType()); + + ScSingleRefData aArgAddrRPN = *ppRPNTokens[0]->GetSingleRef(); + ScAddress aArgAddrActual = aArgAddrRPN.toAbs(rCase.aCellAddress); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Computed implicit intersection singleref is wrong", rCase.aArgAddr, aArgAddrActual); + } + } +} + +void Test::testFormulaCompilerImplicitIntersection1NoGroup() +{ + m_pDoc->InsertTab(0, "Formula"); + sc::AutoCalcSwitch aACSwitch(*m_pDoc, true); // turn auto calc on. + + m_pDoc->SetString(ScAddress(1,2,0), "=COS(A1:A5)"); // B3 + m_pDoc->SetString(ScAddress(1,3,0), "=COS(A1:A5)"); // B4 + + // Implicit intersection optimization in ScCompiler::HandleIIOpCode() internally changes + // these to "=COS(A3)" and "=COS(A4)", but these shouldn't be merged into a formula group, + // otherwise B4's formula would then be "=COS(A2:A6)". + ASSERT_FORMULA_EQUAL(*m_pDoc, ScAddress(1,2,0), "COS(A1:A5)", "Formula in B3 has changed."); + ASSERT_FORMULA_EQUAL(*m_pDoc, ScAddress(1,3,0), "COS(A1:A5)", "Formula in B4 has changed."); + + m_pDoc->DeleteTab(0); +} + void Test::testFormulaRefUpdate() { m_pDoc->InsertTab(0, "Formula"); diff --git a/sc/source/core/data/column.cxx b/sc/source/core/data/column.cxx index d8f164e0c916..268ec1b7793c 100644 --- a/sc/source/core/data/column.cxx +++ b/sc/source/core/data/column.cxx @@ -2112,7 +2112,8 @@ class UpdateRefOnNonCopy // We need to re-compile the token array when a range name is // modified, to correctly reflect the new references in the // name. - ScCompiler aComp(&mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar()); + ScCompiler aComp(&mpCxt->mrDoc, rTopCell.aPos, *rTopCell.GetCode(), mpCxt->mrDoc.GetGrammar(), + true, rTopCell.GetMatrixFlag() != ScMatrixMode::NONE); aComp.CompileTokenArray(); } diff --git a/sc/source/core/data/column4.cxx b/sc/source/core/data/column4.cxx index 832351d275a7..34566ac15bb1 100644 --- a/sc/source/core/data/column4.cxx +++ b/sc/source/core/data/column4.cxx @@ -928,7 +928,8 @@ public: ScTokenArray* pNewCode = aComp.CompileString(aFormula); // Generate RPN tokens. - ScCompiler aComp2(mpDoc, pCell->aPos, *pNewCode); + ScCompiler aComp2(mpDoc, pCell->aPos, *pNewCode, formula::FormulaGrammar::GRAM_UNSPECIFIED, + true, pCell->GetMatrixFlag() != ScMatrixMode::NONE); aComp2.CompileTokenArray(); pCell->SetCode(pNewCode); diff --git a/sc/source/core/data/formulacell.cxx b/sc/source/core/data/formulacell.cxx index e489f60936b7..3800397a9ca7 100644 --- a/sc/source/core/data/formulacell.cxx +++ b/sc/source/core/data/formulacell.cxx @@ -562,7 +562,8 @@ void ScFormulaCellGroup::compileCode( if (mpCode->GetLen() && mpCode->GetCodeError() == FormulaError::NONE && !mpCode->GetCodeLen()) { - ScCompiler aComp(&rDoc, rPos, *mpCode, eGram); + bool bMatrixFormula = mpTopCell->GetMatrixFlag() != ScMatrixMode::NONE; + ScCompiler aComp(&rDoc, rPos, *mpCode, eGram, true, bMatrixFormula); mbSubTotal = aComp.CompileTokenArray(); mnFormatType = aComp.GetNumFormatType(); } @@ -700,7 +701,7 @@ ScFormulaCell::ScFormulaCell( // Generate RPN token array. if (pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen()) { - ScCompiler aComp( pDocument, aPos, *pCode, eTempGrammar); + ScCompiler aComp( pDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE ); bSubTotal = aComp.CompileTokenArray(); nFormatType = aComp.GetNumFormatType(); } @@ -747,7 +748,7 @@ ScFormulaCell::ScFormulaCell( // RPN array generation if( pCode->GetLen() && pCode->GetCodeError() == FormulaError::NONE && !pCode->GetCodeLen() ) { - ScCompiler aComp( pDocument, aPos, *pCode, eTempGrammar); + ScCompiler aComp( pDocument, aPos, *pCode, eTempGrammar, true, cMatrixFlag != ScMatrixMode::NONE ); bSubTotal = aComp.CompileTokenArray(); nFormatType = aComp.GetNumFormatType(); } @@ -993,7 +994,7 @@ void ScFormulaCell::GetFormula( OUStringBuffer& rBuffer, } else { - ScCompiler aComp( pDocument, aPos, *pCode, eGrammar, pContext ); + ScCompiler aComp( pDocument, aPos, *pCode, eGrammar, false, false, pContext ); aComp.CreateStringFromTokenArray( rBuffer ); } } @@ -1004,7 +1005,7 @@ void ScFormulaCell::GetFormula( OUStringBuffer& rBuffer, } else { - ScCompiler aComp( pDocument, aPos, *pCode, eGrammar, pContext ); + ScCompiler aComp( pDocument, aPos, *pCode, eGrammar, false, false, pContext ); aComp.CreateStringFromTokenArray( rBuffer ); } @@ -1031,7 +1032,7 @@ OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInt { ScTokenArray aCode; aCode.AddToken( FormulaErrorToken( pCode->GetCodeError())); - ScCompiler aComp(rCxt, aPos, aCode, pContext); + ScCompiler aComp(rCxt, aPos, aCode, false, false, pContext); aComp.CreateStringFromTokenArray(aBuf); return aBuf.makeStringAndClear(); } @@ -1058,7 +1059,7 @@ OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInt } else { - ScCompiler aComp(rCxt, aPos, *pCode, pContext); + ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext); aComp.CreateStringFromTokenArray(aBuf); } } @@ -1069,7 +1070,7 @@ OUString ScFormulaCell::GetFormula( sc::CompileFormulaContext& rCxt, const ScInt } else { - ScCompiler aComp(rCxt, aPos, *pCode, pContext); + ScCompiler aComp(rCxt, aPos, *pCode, false, false, pContext); aComp.CreateStringFromTokenArray(aBuf); } @@ -1199,7 +1200,7 @@ void ScFormulaCell::CompileTokenArray( bool bNoListening ) if( !bNoListening && pCode->GetCodeLen() ) EndListeningTo( pDocument ); - ScCompiler aComp(pDocument, aPos, *pCode, pDocument->GetGrammar()); + ScCompiler aComp(pDocument, aPos, *pCode, pDocument->GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE); bSubTotal = aComp.CompileTokenArray(); if( pCode->GetCodeError() == FormulaError::NONE ) { @@ -1239,7 +1240,7 @@ void ScFormulaCell::CompileTokenArray( sc::CompileFormulaContext& rCxt, bool bNo if( !bNoListening && pCode->GetCodeLen() ) EndListeningTo( pDocument ); - ScCompiler aComp(rCxt, aPos, *pCode); + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); bSubTotal = aComp.CompileTokenArray(); if( pCode->GetCodeError() == FormulaError::NONE ) { @@ -1277,7 +1278,7 @@ void ScFormulaCell::CompileXML( sc::CompileFormulaContext& rCxt, ScProgress& rPr if (bWasInFormulaTree) pDocument->RemoveFromFormulaTree( this); rCxt.setGrammar(eTempGrammar); - ScCompiler aComp(rCxt, aPos, *pCode); + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); OUString aFormula, aFormulaNmsp; aComp.CreateStringFromXMLTokenArray( aFormula, aFormulaNmsp ); pDocument->DecXMLImportedFormulaCount( aFormula.getLength() ); @@ -1395,7 +1396,7 @@ void ScFormulaCell::CalcAfterLoad( sc::CompileFormulaContext& rCxt, bool bStartL // The RPN array is not created when a Calc 3.0-Doc has been read as the Range Names exist until now. if( pCode->GetLen() && !pCode->GetCodeLen() && pCode->GetCodeError() == FormulaError::NONE ) { - ScCompiler aComp(rCxt, aPos, *pCode); + ScCompiler aComp(rCxt, aPos, *pCode, true, cMatrixFlag != ScMatrixMode::NONE); bSubTotal = aComp.CompileTokenArray(); nFormatType = aComp.GetNumFormatType(); bDirty = true; @@ -4110,6 +4111,39 @@ ScFormulaCell::CompareState ScFormulaCell::CompareByTokenArray( const ScFormulaC switch (pThisTok->GetType()) { + // ScCompiler::HandleIIOpCode() may optimize some refs only in RPN code, + // resulting in identical RPN references that could lead to creating + // a formula group from formulas that should not be merged into a group, + // so check also the formula itself. + case formula::svSingleRef: + { + // Single cell reference. + const ScSingleRefData& rRef = *pThisTok->GetSingleRef(); + if (rRef != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef.IsRowRel()) + bInvariant = false; + } + break; + case formula::svDoubleRef: + { + // Range reference. + const ScSingleRefData& rRef1 = *pThisTok->GetSingleRef(); + const ScSingleRefData& rRef2 = *pThisTok->GetSingleRef2(); + if (rRef1 != *pOtherTok->GetSingleRef()) + return NotEqual; + + if (rRef2 != *pOtherTok->GetSingleRef2()) + return NotEqual; + + if (rRef1.IsRowRel()) + bInvariant = false; + + if (rRef2.IsRowRel()) + bInvariant = false; + } + break; // All index tokens are names. Different categories already had // different OpCode values. case formula::svIndex: @@ -4802,7 +4836,7 @@ bool ScFormulaCell::InterpretInvariantFormulaGroup() } } - ScCompiler aComp(pDocument, aPos, aCode, pDocument->GetGrammar()); + ScCompiler aComp(pDocument, aPos, aCode, pDocument->GetGrammar(), true, cMatrixFlag != ScMatrixMode::NONE); aComp.CompileTokenArray(); // Create RPN token array. ScInterpreter aInterpreter(this, pDocument, pDocument->GetNonThreadedContext(), aPos, aCode); aInterpreter.Interpret(); diff --git a/sc/source/core/data/simpleformulacalc.cxx b/sc/source/core/data/simpleformulacalc.cxx index a710d8a34cbf..9882fd7a352f 100644 --- a/sc/source/core/data/simpleformulacalc.cxx +++ b/sc/source/core/data/simpleformulacalc.cxx @@ -28,7 +28,7 @@ ScSimpleFormulaCalculator::ScSimpleFormulaCalculator( ScDocument* pDoc, const Sc , mbMatrixFormula(bMatrixFormula) { // compile already here - ScCompiler aComp(mpDoc, maAddr, eGram); + ScCompiler aComp(mpDoc, maAddr, eGram, true, bMatrixFormula); mpCode.reset(aComp.CompileString(rFormula)); if(mpCode->GetCodeError() == FormulaError::NONE && mpCode->GetLen()) aComp.CompileTokenArray(); diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx index 4ec04d968042..d8a68b50fb43 100644 --- a/sc/source/core/tool/compiler.cxx +++ b/sc/source/core/tool/compiler.cxx @@ -1733,8 +1733,8 @@ struct ConventionXL_R1C1 : public ScCompiler::Convention, public ConventionXL }; ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, ScTokenArray& rArr, - const ScInterpreterContext* pContext ) - : FormulaCompiler(rArr), + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(rArr, bComputeII, bMatrixFlag), pDoc(rCxt.getDoc()), aPos(rPos), mpFormatter(pContext? pContext->GetFormatTable() : pDoc->GetFormatTable()), @@ -1753,8 +1753,9 @@ ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, } ScCompiler::ScCompiler( ScDocument* pDocument, const ScAddress& rPos, ScTokenArray& rArr, - formula::FormulaGrammar::Grammar eGrammar, const ScInterpreterContext* pContext ) - : FormulaCompiler(rArr), + formula::FormulaGrammar::Grammar eGrammar, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(rArr, bComputeII, bMatrixFlag), pDoc( pDocument ), aPos( rPos ), mpFormatter(pContext ? pContext->GetFormatTable() : pDoc->GetFormatTable()), @@ -1774,8 +1775,10 @@ ScCompiler::ScCompiler( ScDocument* pDocument, const ScAddress& rPos, ScTokenArr eGrammar ); } -ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, const ScInterpreterContext* pContext ) - : pDoc(rCxt.getDoc()), +ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(bComputeII, bMatrixFlag), + pDoc(rCxt.getDoc()), aPos(rPos), mpFormatter(pContext ? pContext->GetFormatTable() : pDoc ? pDoc->GetFormatTable() : nullptr), mnCurrentSheetTab(-1), @@ -1793,8 +1796,9 @@ ScCompiler::ScCompiler( sc::CompileFormulaContext& rCxt, const ScAddress& rPos, } ScCompiler::ScCompiler( ScDocument* pDocument, const ScAddress& rPos, - formula::FormulaGrammar::Grammar eGrammar, const ScInterpreterContext* pContext ) - : + formula::FormulaGrammar::Grammar eGrammar, + bool bComputeII, bool bMatrixFlag, const ScInterpreterContext* pContext ) + : FormulaCompiler(bComputeII, bMatrixFlag), pDoc( pDocument ), aPos( rPos ), mpFormatter(pContext ? pContext->GetFormatTable() : pDoc ? pDoc->GetFormatTable() : nullptr), @@ -5791,50 +5795,154 @@ formula::ParamClass ScCompiler::GetForceArrayParameter( const formula::FormulaTo bool ScCompiler::IsIIOpCode(OpCode nOpCode) const { - if (nOpCode == ocSumIf || nOpCode == ocAverageIf) + if (nOpCode == ocSumIf || nOpCode == ocAverageIf || (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR)) return true; return false; } -void ScCompiler::HandleIIOpCode(OpCode nOpCode, FormulaToken*** pppToken, sal_uInt8 nNumParams) +void ScCompiler::HandleIIOpCode(OpCode nOpCode, formula::ParamClass eClass, FormulaToken*** pppToken, sal_uInt8 nNumParams) { - switch (nOpCode) + if (!mbComputeII) + return; + + if (nOpCode == ocSumIf || nOpCode == ocAverageIf) { - case ocSumIf: - case ocAverageIf: + if (nNumParams != 3) + return; + + if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2])) + return; + + if ((*pppToken[0])->GetType() != svDoubleRef) + return; + + const StackVar eSumRangeType = (*pppToken[2])->GetType(); + + if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef ) + return; + + const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef(); + + ScComplexRefData aSumRange; + if (eSumRangeType == svSingleRef) { - if (nNumParams != 3) - return; + aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef(); + aSumRange.Ref2 = aSumRange.Ref1; + } + else + aSumRange = *(*pppToken[2])->GetDoubleRef(); - if (!(pppToken[0] && pppToken[2] && *pppToken[0] && *pppToken[2])) - return; + CorrectSumRange(rBaseRange, aSumRange, pppToken[2]); + } + else if (nOpCode >= SC_OPCODE_START_1_PAR && nOpCode < SC_OPCODE_STOP_1_PAR) + { + if (nNumParams != 1) + return; - if ((*pppToken[0])->GetType() != svDoubleRef) - return; + if (eClass == formula::ForceArray || mbMatrixFlag) + return; - const StackVar eSumRangeType = (*pppToken[2])->GetType(); + if ((*pppToken[0])->GetType() != svDoubleRef) + return; - if ( eSumRangeType != svSingleRef && eSumRangeType != svDoubleRef ) - return; + ReplaceDoubleRefII(pppToken[0]); + } +} - const ScComplexRefData& rBaseRange = *(*pppToken[0])->GetDoubleRef(); +void ScCompiler::ReplaceDoubleRefII(FormulaToken** ppDoubleRefTok) +{ + const ScComplexRefData& rRange = *(*ppDoubleRefTok)->GetDoubleRef(); + + // Can't do optimization reliably in this case (when row references are absolute). + // Example : =SIN(A$1:A$10) filled in a formula group starting at B5 and of length 100. + // If we just optimize the argument $A$1:$A$10 to singleref "A5" for the top cell in the fg, then + // the results in cells B11:B104 will be incorrect (sin(0) = 0, assuming empty cells in A11:A104) + // instead of the #VALUE! errors we would expect. We need to know the formula-group length to + // fix this, but that is unknown at this stage, so skip such cases. + if (!rRange.Ref1.IsRowRel() && !rRange.Ref2.IsRowRel()) + return; - ScComplexRefData aSumRange; - if (eSumRangeType == svSingleRef) - { - aSumRange.Ref1 = *(*pppToken[2])->GetSingleRef(); - aSumRange.Ref2 = aSumRange.Ref1; - } - else - aSumRange = *(*pppToken[2])->GetDoubleRef(); + ScRange aAbsRange = rRange.toAbs(aPos); + if (aAbsRange.aStart == aAbsRange.aEnd) + return; // Nothing to do (trivial case). + + ScAddress aAddr; + + if (!DoubleRefToPosSingleRefScalarCase(aAbsRange, aAddr, aPos)) + return; - CorrectSumRange(rBaseRange, aSumRange, pppToken[2]); + ScSingleRefData aSingleRef; + aSingleRef.InitFlags(); + aSingleRef.SetColRel(rRange.Ref1.IsColRel()); + aSingleRef.SetRowRel(true); + aSingleRef.SetTabRel(rRange.Ref1.IsTabRel()); + aSingleRef.SetAddress(aAddr, aPos); + + // Replace the original doubleref token with computed singleref token + FormulaToken* pNewSingleRefTok = new ScSingleRefToken(aSingleRef); + (*ppDoubleRefTok)->DecRef(); + *ppDoubleRefTok = pNewSingleRefTok; + pNewSingleRefTok->IncRef(); +} + +bool ScCompiler::DoubleRefToPosSingleRefScalarCase(const ScRange& rRange, ScAddress& rAdr, const ScAddress& rFormulaPos) +{ + assert(rRange.aStart != rRange.aEnd); + + bool bOk = false; + SCCOL nMyCol = rFormulaPos.Col(); + SCROW nMyRow = rFormulaPos.Row(); + SCTAB nMyTab = rFormulaPos.Tab(); + SCCOL nCol = 0; + SCROW nRow = 0; + SCTAB nTab; + nTab = rRange.aStart.Tab(); + if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) + { + nRow = rRange.aStart.Row(); + if ( nRow == rRange.aEnd.Row() ) + { + bOk = true; + nCol = nMyCol; + } + else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() + && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) + { + bOk = true; + nCol = nMyCol; + nRow = nMyRow; + } + } + else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) + { + nCol = rRange.aStart.Col(); + if ( nCol == rRange.aEnd.Col() ) + { + bOk = true; + nRow = nMyRow; + } + else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() + && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) + { + bOk = true; + nCol = nMyCol; + nRow = nMyRow; } - break; - default: - ; } + if ( bOk ) + { + if ( nTab == rRange.aEnd.Tab() ) + ; // all done + else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() ) + nTab = nMyTab; + else + bOk = false; + if ( bOk ) + rAdr.Set( nCol, nRow, nTab ); + } + + return bOk; } static void lcl_GetColRowDeltas(const ScRange& rRange, SCCOL& rXDelta, SCROW& rYDelta) diff --git a/sc/source/core/tool/interpr4.cxx b/sc/source/core/tool/interpr4.cxx index c600cdca34b1..6194fa5abe02 100644 --- a/sc/source/core/tool/interpr4.cxx +++ b/sc/source/core/tool/interpr4.cxx @@ -65,6 +65,7 @@ #include <doubleref.hxx> #include <queryparam.hxx> #include <tokenarray.hxx> +#include <compiler.hxx> #include <math.h> #include <float.h> @@ -2004,56 +2005,8 @@ bool ScInterpreter::DoubleRefToPosSingleRef( const ScRange& rRange, ScAddress& r return bOk; } - SCCOL nMyCol = aPos.Col(); - SCROW nMyRow = aPos.Row(); - SCTAB nMyTab = aPos.Tab(); - SCCOL nCol = 0; - SCROW nRow = 0; - SCTAB nTab; - nTab = rRange.aStart.Tab(); - if ( rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) - { - nRow = rRange.aStart.Row(); - if ( nRow == rRange.aEnd.Row() ) - { - bOk = true; - nCol = nMyCol; - } - else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() - && rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) - { - bOk = true; - nCol = nMyCol; - nRow = nMyRow; - } - } - else if ( rRange.aStart.Row() <= nMyRow && nMyRow <= rRange.aEnd.Row() ) - { - nCol = rRange.aStart.Col(); - if ( nCol == rRange.aEnd.Col() ) - { - bOk = true; - nRow = nMyRow; - } - else if ( nTab != nMyTab && nTab == rRange.aEnd.Tab() - && rRange.aStart.Col() <= nMyCol && nMyCol <= rRange.aEnd.Col() ) - { - bOk = true; - nCol = nMyCol; - nRow = nMyRow; - } - } - if ( bOk ) - { - if ( nTab == rRange.aEnd.Tab() ) - ; // all done - else if ( nTab <= nMyTab && nMyTab <= rRange.aEnd.Tab() ) - nTab = nMyTab; - else - bOk = false; - if ( bOk ) - rAdr.Set( nCol, nRow, nTab ); - } + bOk = ScCompiler::DoubleRefToPosSingleRefScalarCase(rRange, rAdr, aPos); + if ( !bOk ) SetError( FormulaError::NoValue ); return bOk; diff --git a/sc/source/filter/oox/formulabuffer.cxx b/sc/source/filter/oox/formulabuffer.cxx index 87529dcad0d0..9f7977b5ddb2 100644 --- a/sc/source/filter/oox/formulabuffer.cxx +++ b/sc/source/filter/oox/formulabuffer.cxx @@ -121,7 +121,7 @@ void applySharedFormulas( sal_Int32 nId = rEntry.mnSharedId; const OUString& rTokenStr = rEntry.maTokenStr; - ScCompiler aComp(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML); + ScCompiler aComp(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false); aComp.SetNumberFormatter(&rFormatter); ScTokenArray* pArray = aComp.CompileString(rTokenStr); if (pArray) @@ -206,7 +206,7 @@ void applyCellFormulas( continue; } - ScCompiler aCompiler(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML); + ScCompiler aCompiler(&rDoc.getDoc(), aPos, formula::FormulaGrammar::GRAM_OOXML, true, false); aCompiler.SetNumberFormatter(&rFormatter); aCompiler.SetExternalLinks(rExternalLinks); ScTokenArray* pCode = aCompiler.CompileString(rItem.maTokenStr); diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx index 306d020ec947..9e29d72da734 100644 --- a/sc/source/ui/view/viewfunc.cxx +++ b/sc/source/ui/view/viewfunc.cxx @@ -400,7 +400,7 @@ void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, { // formula, compile with autoCorrection i = rMark.GetFirstSelected(); ScAddress aPos( nCol, nRow, i ); - ScCompiler aComp( pDoc, aPos, pDoc->GetGrammar()); + ScCompiler aComp( pDoc, aPos, pDoc->GetGrammar(), true, false ); //2do: enable/disable autoCorrection via calcoptions aComp.SetAutoCorrection( true ); if ( rString[0] == '+' || rString[0] == '-' ) |