diff options
author | Eike Rathke <erack@redhat.com> | 2013-01-11 16:50:48 +0100 |
---|---|---|
committer | Eike Rathke <erack@redhat.com> | 2013-01-11 17:01:44 +0100 |
commit | 3914d00c32c4226d3a73ffdd8985c887016887e0 (patch) | |
tree | 214d06dd8f9a28e8196ba7d26275d4bd16179ae2 | |
parent | 5fd9feafa5d73121636af9397d42e046cc394719 (diff) |
fdo#56124 changes to "add functions IFERROR and IFNA"
* reimplemented actual ScIfError() function for matrix cases and simpler
pop/push of non-error values
* various smaller adaptions, see also inline comments of
https://gerrit.libreoffice.org/1522
Change-Id: I80a8235a2cbb548c0ef993862e76741af46e30d2
-rw-r--r-- | formula/inc/formula/errorcodes.hxx | 3 | ||||
-rw-r--r-- | formula/source/core/api/FormulaCompiler.cxx | 69 | ||||
-rw-r--r-- | sc/qa/unit/ucalc.cxx | 44 | ||||
-rw-r--r-- | sc/source/core/tool/interpr1.cxx | 266 | ||||
-rw-r--r-- | sc/source/core/tool/parclass.cxx | 4 | ||||
-rw-r--r-- | sc/source/core/tool/token.cxx | 5 | ||||
-rw-r--r-- | sc/source/ui/src/scfuncs.src | 14 |
7 files changed, 247 insertions, 158 deletions
diff --git a/formula/inc/formula/errorcodes.hxx b/formula/inc/formula/errorcodes.hxx index a99dc7b9d4e0..1e145e04132b 100644 --- a/formula/inc/formula/errorcodes.hxx +++ b/formula/inc/formula/errorcodes.hxx @@ -72,6 +72,9 @@ const sal_uInt16 errNestedArray = 533; // be used to distinguish that condition from all other (inherited) errors. Do // not use for anything else! Never push or inherit the error otherwise! const sal_uInt16 errNotNumericString = 534; +// ScInterpreter internal: jump matrix already has a result at this position, +// do not overwrite in case of empty code path. +const sal_uInt16 errJumpMatHasResult = 535; // Interpreter: NA() not available condition, not a real error const sal_uInt16 NOTAVAILABLE = 0x7fff; diff --git a/formula/source/core/api/FormulaCompiler.cxx b/formula/source/core/api/FormulaCompiler.cxx index e91912368a5c..d4d69ddbd84b 100644 --- a/formula/source/core/api/FormulaCompiler.cxx +++ b/formula/source/core/api/FormulaCompiler.cxx @@ -1179,12 +1179,21 @@ void FormulaCompiler::Factor() { // the PC counters are -1 pFacToken = mpToken; - if ( eOp == ocIf ) - pFacToken->GetJump()[ 0 ] = 3; // if, else, behind - else if ( eOp == ocChose ) - pFacToken->GetJump()[ 0 ] = MAXJUMPCOUNT+1; - else - pFacToken->GetJump()[ 0 ] = 2; // if, else + switch (eOp) + { + case ocIf: + pFacToken->GetJump()[ 0 ] = 3; // if, else, behind + break; + case ocChose: + pFacToken->GetJump()[ 0 ] = MAXJUMPCOUNT+1; + break; + case ocIfError: + case ocIfNA: + pFacToken->GetJump()[ 0 ] = 2; // if, behind + break; + default: + SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump count case?"); + } eOp = NextToken(); if (eOp == ocOpen) { @@ -1193,15 +1202,30 @@ void FormulaCompiler::Factor() } else SetError(errPairExpected); - short nJumpCount = 0; PutCode( pFacToken ); - // during AutoCorrect (since pArr->GetCodeError() is + // During AutoCorrect (since pArr->GetCodeError() is // ignored) an unlimited ocIf would crash because // ScRawToken::Clone() allocates the JumpBuffer according to // nJump[0]*2+2, which is 3*2+2 on ocIf and 2*2+2 ocIfError and ocIfNA. + short nJumpMax; OpCode eFacOpCode = pFacToken->GetOpCode(); - const short nJumpMax = ( eFacOpCode == ocIf ? 3 : - ( eFacOpCode == ocChose ? MAXJUMPCOUNT : 2 ) ); + switch (eFacOpCode) + { + case ocIf: + nJumpMax = 3; + break; + case ocChose: + nJumpMax = MAXJUMPCOUNT; + break; + case ocIfError: + case ocIfNA: + nJumpMax = 2; + break; + default: + nJumpMax = 0; + SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump max case?"); + } + short nJumpCount = 0; while ( (nJumpCount < (MAXJUMPCOUNT - 1)) && (eOp == ocSep) && (!pArr->GetCodeError() || bIgnoreErrors) ) { @@ -1221,12 +1245,27 @@ void FormulaCompiler::Factor() if ( ++nJumpCount <= nJumpMax ) pFacToken->GetJump()[ nJumpCount ] = pc-1; eFacOpCode = pFacToken->GetOpCode(); - if ( ( eFacOpCode == ocIf && nJumpCount > 3) || - ( ( eFacOpCode == ocIfError || eFacOpCode == ocIfNA ) && nJumpCount > 2 ) || - ( nJumpCount >= MAXJUMPCOUNT ) ) - SetError(errIllegalParameter); - else + bool bLimitOk; + switch (eFacOpCode) + { + case ocIf: + bLimitOk = (nJumpCount <= 3); + break; + case ocChose: + bLimitOk = (nJumpCount < MAXJUMPCOUNT); /* TODO: check, really <, not <=? */ + break; + case ocIfError: + case ocIfNA: + bLimitOk = (nJumpCount <= 2); + break; + default: + bLimitOk = false; + SAL_WARN( "formula.core", "FormulaCompiler::Factor: forgot to add a jump limit case?"); + } + if (bLimitOk) pFacToken->GetJump()[ 0 ] = nJumpCount; + else + SetError(errIllegalParameter); } } else if ( eOp == ocMissing ) diff --git a/sc/qa/unit/ucalc.cxx b/sc/qa/unit/ucalc.cxx index 01fe181899b0..fb2cb1be8c38 100644 --- a/sc/qa/unit/ucalc.cxx +++ b/sc/qa/unit/ucalc.cxx @@ -673,12 +673,12 @@ void testFuncCOUNTIF(ScDocument* pDoc) void testFuncIFERROR(ScDocument* pDoc) { - // IFERROR/IFNA (fdo 56124) + // IFERROR/IFNA (fdo#56124) // Empty A1:A39 first. clearRange(pDoc, ScRange(0, 0, 0, 0, 40, 0)); - // Raw data (rows 1 through 9) + // Raw data (rows 1 through 10) const char* aData[] = { "1", "e", @@ -686,6 +686,7 @@ void testFuncIFERROR(ScDocument* pDoc) "=SQRT(-2)", "=A4", "=1/0", + "=NA()", "bar", "4", "gee" @@ -699,18 +700,21 @@ void testFuncIFERROR(ScDocument* pDoc) // formulas and results struct { - const char* pFormula; double fResult; + const char* pFormula; const char* pResult; } aChecks[] = { - { "=IFERROR(A1;-7)", 1 }, - { "{=IFERROR(3*A1:A2;2002)}", 3 }, - { "{=IFERROR(3*A1:A2;1998)}", 1998 }, - { "=IFERROR(A3;9)", 4 }, - { "=IFERROR(A4;9)", 9 }, - { "=IFERROR(A5;-7", -7 }, - { "=IFERROR(A6;-7)", -7 }, - { "=IFERROR(A2;-7)", -7 }, - { "=IFNA(VLOOKUP(\"4\",A7:A9;1;0);-2)", 4 }, - { "=IFNA(VLOOKUP(\"fop\",A7:A9;1;0);-2)", -2 } + { "=IFERROR(A1;9)", "1" }, + { "{=IFERROR(3*A1:A2;2002)}", "3" }, + { "{=IFERROR(3*A1:A2;1998)}", "1998" }, + { "=IFERROR(A2;-7)", "-7" }, + { "=IFERROR(A3;9)", "4" }, + { "=IFERROR(A4;-7)", "-7" }, + { "=IFERROR(A5;-7)", "-7" }, + { "=IFERROR(A6;-7)", "-7" }, + { "=IFERROR(A7;-7)", "-7" }, + { "=IFNA(A6;9)", "#DIV/0!" }, + { "=IFNA(A7;-7)", "-7" }, + { "=IFNA(VLOOKUP(\"4\",A8:A10;1;0);-2)", "4" }, + { "=IFNA(VLOOKUP(\"fop\",A8:A10;1;0);-2)", "-2" } }; nRows = SAL_N_ELEMENTS(aChecks); @@ -723,15 +727,15 @@ void testFuncIFERROR(ScDocument* pDoc) for (SCROW i = 0; i < nRows; ++i) { - double result; + rtl::OUString aResult; SCROW nRow = 20 + i; - pDoc->GetValue(0, nRow, 0, result); - bool bGood = result == aChecks[i].fResult; + pDoc->GetString(0, nRow, 0, aResult); + bool bGood = (aResult == rtl::OUString::createFromAscii( aChecks[i].pResult)); if (!bGood) { cerr << "row " << (nRow+1) << ": formula" << aChecks[i].pFormula - << " expected=" << aChecks[i].fResult << " actual=" << result << endl; - CPPUNIT_ASSERT_MESSAGE("Unexpected result for COUNTIF", false); + << " expected=" << aChecks[i].pResult << " actual=" << aResult << endl; + CPPUNIT_ASSERT_MESSAGE("Unexpected result for IFERROR/IFNA", false); } } } @@ -4219,8 +4223,6 @@ void Test::testFunctionLists() "CELL", "CURRENT", "FORMULA", - "IFERROR", - "IFNA", "INFO", "ISBLANK", "ISERR", @@ -4242,6 +4244,8 @@ void Test::testFunctionLists() "AND", "FALSE", "IF", + "IFERROR", + "IFNA", "NOT", "OR", "TRUE", diff --git a/sc/source/core/tool/interpr1.cxx b/sc/source/core/tool/interpr1.cxx index 0c7817b88450..0ef12c742ded 100644 --- a/sc/source/core/tool/interpr1.cxx +++ b/sc/source/core/tool/interpr1.cxx @@ -54,6 +54,7 @@ #include <math.h> #include <vector> #include <memory> +#include <limits> #include "cellkeytranslator.hxx" #include "lookupcache.hxx" #include "rangenam.hxx" @@ -243,135 +244,185 @@ void ScInterpreter::ScIfJump() } -void ScInterpreter::ScIfError( bool bNAonly ) +/** Store a matrix value in another matrix in the context of that other matrix + is the result matrix of a jump matrix. All arguments must be valid and are + not checked. */ +static void lcl_storeJumpMatResult( const ScMatrix* pMat, ScMatrix* pResMat, SCSIZE nC, SCSIZE nR ) { - RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "sc", "er", "ScInterpreter::ScIfError" ); - const short* pJump = pCur->GetJump(); - if ( !pJump ) + if ( pMat->IsValue( nC, nR ) ) { - PushIllegalParameter(); - return; + double fVal = pMat->GetDouble( nC, nR ); + pResMat->PutDouble( fVal, nC, nR ); } + else if ( pMat->IsEmpty( nC, nR ) ) + { + pResMat->PutEmpty( nC, nR ); + } + else + { + const String& rStr = pMat->GetString( nC, nR ); + pResMat->PutString( rStr, nC, nR ); + } +} + + +void ScInterpreter::ScIfError( bool bNAonly ) +{ + RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "sc", "Donkers/erAck", "ScInterpreter::ScIfError" ); + const short* pJump = pCur->GetJump(); short nJumpCount = pJump[ 0 ]; - if ( nJumpCount != 2 ) + if (!sp) { - PushIllegalArgument(); + PushError( errUnknownStackVariable); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); return; } - if ( nGlobalError == 0 || ( bNAonly && nGlobalError != NOTAVAILABLE ) ) + FormulaTokenRef xToken( pStack[ sp - 1 ] ); + bool bError = false; + sal_uInt16 nOldGlobalError = nGlobalError; + nGlobalError = 0; + + MatrixDoubleRefToMatrix(); + switch (GetStackType()) { - MatrixDoubleRefToMatrix(); - switch ( GetStackType() ) - { - case svDoubleRef: - case svSingleRef: + default: + Pop(); + break; + case svError: + PopError(); + bError = true; + break; + case svDoubleRef: + case svSingleRef: { ScAddress aAdr; - if ( !PopDoubleRefOrSingleRef( aAdr ) ) + if (!PopDoubleRefOrSingleRef( aAdr)) + bError = true; + else { - PushIllegalArgument(); - return; + ScBaseCell* pCell = GetCell( aAdr); + nGlobalError = GetCellErrCode( pCell); + if (nGlobalError) + bError = true; } - ScBaseCell* pCell = GetCell( aAdr ); - sal_uInt16 nErr = GetCellErrCode( pCell ); - if ( nErr == 0 || ( bNAonly && nErr != NOTAVAILABLE ) ) + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + { + double fVal; + String aStr; + // Handles also existing jump matrix case and sets error on + // elements. + GetDoubleOrStringFromMatrix( fVal, aStr); + if (nGlobalError) + bError = true; + } + break; + case svMatrix: + { + const ScMatrixRef pMat = PopMatrix(); + if (!pMat || (nGlobalError && (!bNAonly || nGlobalError == NOTAVAILABLE))) { - // no error, return 1st argument - if ( pCell && pCell->HasValueData() ) - { - PushDouble( GetCellValue(aAdr, pCell) ); - aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); - return; - } - else + bError = true; + break; // switch + } + // If the matrix has no queried error at all we can simply use + // it as result and don't need to bother with jump matrix. + SCSIZE nErrorCol = ::std::numeric_limits<SCSIZE>::max(), + nErrorRow = ::std::numeric_limits<SCSIZE>::max(); + SCSIZE nCols, nRows; + pMat->GetDimensions( nCols, nRows ); + if (nCols == 0 || nRows == 0) + { + bError = true; + break; // switch + } + for (SCSIZE nC=0; nC < nCols && !bError; ++nC) + { + for (SCSIZE nR=0; nR < nRows && !bError; ++nR) { - String aInputString; - GetCellString( aInputString, pCell ); - PushString( aInputString ); - aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); - return; + sal_uInt16 nErr = pMat->GetError( nC, nR ); + if (nErr && (!bNAonly || nErr == NOTAVAILABLE)) + { + bError = true; + nErrorCol = nC; + nErrorRow = nR; + } } } - } - break; - case svMatrix: - { - nFuncFmtType = NUMBERFORMAT_NUMBER; - ScMatrixRef pMat = PopMatrix(); - if ( pMat && ( !nGlobalError || ( bNAonly && nGlobalError != NOTAVAILABLE ) ) ) - { - FormulaTokenRef xNew; - ScTokenMatrixMap::const_iterator aMapIter; - SCSIZE nCols, nRows; - pMat->SetErrorInterpreter( NULL ); - pMat->GetDimensions( nCols, nRows ); - if ( nCols > 0 && nRows > 0 ) + if (!bError) + break; // switch, we're done and have the result + + FormulaTokenRef xNew; + ScTokenMatrixMap::const_iterator aMapIter; + if (pTokenMatrixMap && ((aMapIter = pTokenMatrixMap->find( pCur)) != pTokenMatrixMap->end())) + { + xNew = (*aMapIter).second; + } + else + { + const ScMatrix* pMatPtr = pMat.get(); + ScJumpMatrix* pJumpMat = new ScJumpMatrix( nCols, nRows ); + ScMatrix* pResMatPtr = pJumpMat->GetResultMatrix(); + // Init all jumps to no error to save single calls. Error + // is the exceptional condition. + const double fFlagResult = CreateDoubleError( errJumpMatHasResult); + pJumpMat->SetAllJumps( fFlagResult, pJump[ nJumpCount ], pJump[ nJumpCount ] ); + // Up to first error position simply store results, no need + // to evaluate error conditions again. + SCSIZE nC = 0, nR = 0; + for ( ; nC < nCols && (nC != nErrorCol || nR != nErrorRow); /*nop*/ ) { - if ( pTokenMatrixMap && - ( ( aMapIter = pTokenMatrixMap->find( pCur ) ) != pTokenMatrixMap->end() ) ) + for ( ; nR < nRows && (nC != nErrorCol || nR != nErrorRow); ++nR) { - xNew = (*aMapIter).second; + lcl_storeJumpMatResult( pMatPtr, pResMatPtr, nC, nR); } - else + if (nC != nErrorCol || nR != nErrorRow) + ++nC; + } + // Now the mixed cases. + for ( ; nC < nCols; ++nC) + { + for ( ; nR < nRows; ++nR) { - ScJumpMatrix* pJumpMat = new ScJumpMatrix( nCols, nRows ); - for ( SCSIZE nC = 0; nC < nCols; ++nC ) - { - for ( SCSIZE nR = 0; nR < nRows; ++nR ) - { - double fVal; - sal_uInt16 nErr; - bool bIsValue = pMat->IsValue(nC, nR); - if ( bIsValue ) - { - fVal = pMat->GetDouble( nC, nR ); - nErr = !( pMat->GetError( nC, nR ) == 0 || - ( bNAonly && - pMat->GetError( nC, nR )!= NOTAVAILABLE ) ); - } - else - { - fVal = 0.0; - nErr = 1; - } - - if ( nErr == 0 ) - { // no error, return 1st argument - pJumpMat->SetJump( nC, nR, fVal, - pJump[ nJumpCount ], pJump[ nJumpCount ] ); - } - else - { - // error, return 2nd argument - pJumpMat->SetJump( nC, nR, fVal, - pJump[ 1 ], pJump[ nJumpCount ] ); - } - } + sal_uInt16 nErr = pMat->GetError( nC, nR ); + if (nErr && (!bNAonly || nErr == NOTAVAILABLE)) + { // TRUE, THEN path + pJumpMat->SetJump( nC, nR, 1.0, pJump[ 1 ], pJump[ nJumpCount ] ); + } + else + { // FALSE, EMPTY path, store result instead + lcl_storeJumpMatResult( pMatPtr, pResMatPtr, nC, nR); } - xNew = new ScJumpMatrixToken( pJumpMat ); - GetTokenMatrixMap().insert( ScTokenMatrixMap::value_type( pCur, xNew )); } } - PushTempToken( xNew.get() ); - // set endpoint of path for main code line - aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); - return; + xNew = new ScJumpMatrixToken( pJumpMat ); + GetTokenMatrixMap().insert( ScTokenMatrixMap::value_type( pCur, xNew )); } - } - break; - default: - { - //other stacktypes, no error, return 1st argument - aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + nGlobalError = nOldGlobalError; + PushTempToken( xNew.get() ); + // set endpoint of path for main code line + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); return; } - } + break; } - // error, return 2nd argument - nGlobalError = 0; - aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] ); + if (bError && (!bNAonly || nGlobalError == NOTAVAILABLE)) + { + // error, calculate 2nd argument + nGlobalError = 0; + aCode.Jump( pJump[ 1 ], pJump[ nJumpCount ] ); + } + else + { + // no error, push 1st argument and continue + nGlobalError = nOldGlobalError; + PushTempToken( xToken.get()); + aCode.Jump( pJump[ nJumpCount ], pJump[ nJumpCount ] ); + } } @@ -692,18 +743,7 @@ bool ScInterpreter::JumpMatrix( short nStackLevel ) } else { - if ( pMat->IsValue( nC, nR ) ) - { - fVal = pMat->GetDouble( nC, nR ); - pResMat->PutDouble( fVal, nC, nR ); - } - else if ( pMat->IsEmpty( nC, nR ) ) - pResMat->PutEmpty( nC, nR ); - else - { - const String& rStr = pMat->GetString( nC, nR ); - pResMat->PutString( rStr, nC, nR ); - } + lcl_storeJumpMatResult( pMat.get(), pResMat.get(), nC, nR); } lcl_AdjustJumpMatrix( pJumpMatrix, pResMat, nCols, nRows ); } @@ -734,7 +774,7 @@ bool ScInterpreter::JumpMatrix( short nStackLevel ) pJumpMatrix->GetJump( nC, nR, fBool, nStart, nNext, nStop ); while ( bCont && nStart == nNext ) { // push all results that have no jump path - if ( pResMat ) + if ( pResMat && (GetDoubleErrorValue( fBool) != errJumpMatHasResult) ) { // a false without path results in an empty path value if ( fBool == 0.0 ) diff --git a/sc/source/core/tool/parclass.cxx b/sc/source/core/tool/parclass.cxx index 68144ddcde24..6f3cae07861e 100644 --- a/sc/source/core/tool/parclass.cxx +++ b/sc/source/core/tool/parclass.cxx @@ -55,8 +55,8 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] = // created inside those functions and ConvertMatrixParameters() is not // called for them. { ocIf, {{ Array, Reference, Reference }, 0 }}, - { ocIfError, {{ Array, Reference, Reference }, false }}, - { ocIfNA, {{ Array, Reference }, false }}, + { ocIfError, {{ Array, Reference }, 0 }}, + { ocIfNA, {{ Array, Reference }, 0 }}, { ocChose, {{ Array, Reference }, 1 }}, // Other specials. { ocOpen, {{ Bounds }, 0 }}, diff --git a/sc/source/core/tool/token.cxx b/sc/source/core/tool/token.cxx index c498eda2576d..4ea3d4e056b6 100644 --- a/sc/source/core/tool/token.cxx +++ b/sc/source/core/tool/token.cxx @@ -126,10 +126,13 @@ void ScRawToken::SetOpCode( OpCode e ) switch (eOp) { case ocIf: + eType = svJump; + nJump[ 0 ] = 3; // If, Else, Behind + break; case ocIfError: case ocIfNA: eType = svJump; - nJump[ 0 ] = 3; // If, Else, Behind + nJump[ 0 ] = 2; // If, Behind break; case ocChose: eType = svJump; diff --git a/sc/source/ui/src/scfuncs.src b/sc/source/ui/src/scfuncs.src index 6238fbaf0e90..ce354e692950 100644 --- a/sc/source/ui/src/scfuncs.src +++ b/sc/source/ui/src/scfuncs.src @@ -2661,9 +2661,9 @@ Resource RID_SC_FUNCTION_DESCRIPTIONS1 ExtraData = { 0; - ID_FUNCTION_GRP_INFO; + ID_FUNCTION_GRP_LOGIC; U2S( HID_FUNC_IFERROR ); - 2; 0; 1; + 2; 0; 0; 0; }; String 2 // Name of Parameter 1 @@ -2676,7 +2676,7 @@ Resource RID_SC_FUNCTION_DESCRIPTIONS1 }; String 4 // Name of Parameter 2 { - Text [ en-US ] = "else value" ; + Text [ en-US ] = "alternative value" ; }; String 5 // Description of Parameter 2 { @@ -2688,12 +2688,12 @@ Resource RID_SC_FUNCTION_DESCRIPTIONS1 { String 1 // Description { - Text [ en-US ] = "Returns value if not an NA, else alternative." ; + Text [ en-US ] = "Returns value if not a #N/A error, else alternative." ; }; ExtraData = { 0; - ID_FUNCTION_GRP_INFO; + ID_FUNCTION_GRP_LOGIC; U2S( HID_FUNC_IFNA ); 2; 0; 0; 0; @@ -2708,11 +2708,11 @@ Resource RID_SC_FUNCTION_DESCRIPTIONS1 }; String 4 // Name of Parameter 2 { - Text [ en-US ] = "else value" ; + Text [ en-US ] = "alternative value" ; }; String 5 // Description of Parameter 2 { - Text [ en-US ] = "The alternative to be returned, should value be an NA." ; + Text [ en-US ] = "The alternative to be returned, should value be a #N/A error." ; }; }; // -=*# Resource for function ODER #*=- |