/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/ #include <sal/macros.h> #include <sal/log.hxx> #include <rtl/math.hxx> #include <formula/FormulaCompiler.hxx> #include <formula/errorcodes.hxx> #include <formula/token.hxx> #include <formula/tokenarray.hxx> #include <o3tl/string_view.hxx> #include <core_resource.hxx> #include <core_resource.hrc>
SvNumFormatType lcl_GetRetFormat( OpCode eOpCode )
{ switch (eOpCode)
{ case ocEqual: case ocNotEqual: case ocLess: case ocGreater: case ocLessEqual: case ocGreaterEqual: case ocAnd: case ocOr: case ocXor: case ocNot: case ocTrue: case ocFalse: case ocIsEmpty: case ocIsString: case ocIsNonString: case ocIsLogical: case ocIsRef: case ocIsValue: case ocIsFormula: case ocIsNA: case ocIsErr: case ocIsError: case ocIsEven: case ocIsOdd: case ocExact: return SvNumFormatType::LOGICAL; case ocGetActDate: case ocGetDate: case ocEasterSunday : return SvNumFormatType::DATE; case ocGetActTime: return SvNumFormatType::DATETIME; case ocGetTime: return SvNumFormatType::TIME; case ocNPV: case ocPV: case ocSYD: case ocDDB: case ocDB: case ocVBD: case ocSLN: case ocPMT: case ocFV: case ocIpmt: case ocPpmt: case ocCumIpmt: case ocCumPrinc: return SvNumFormatType::CURRENCY; case ocRate: case ocIRR: case ocMIRR: case ocRRI: case ocEffect: case ocNominal: case ocPercentSign: return SvNumFormatType::PERCENT; default: return SvNumFormatType::NUMBER;
}
}
bool isRangeResultFunction( OpCode eOp )
{ switch (eOp)
{ case ocIndirect: case ocOffset: returntrue; default: returnfalse;
}
}
bool isRangeResultOpCode( OpCode eOp )
{ switch (eOp)
{ case ocRange: case ocUnion: case ocIntersect: case ocIndirect: case ocOffset: returntrue; default: returnfalse;
}
}
/** @param pToken MUST be a valid token, caller has to ensure.
@param bRight If bRPN==false, bRight==false means opcodes for left side are checked, bRight==true means opcodes for right side. If bRPN==true it doesn't matter except for the ocSep converted to ocUnion case.
*/ bool isPotentialRangeType( FormulaToken const * pToken, bool bRPN, bool bRight )
{ switch (pToken->GetType())
{ case svByte: // could be range result, but only a few if (bRPN) return isRangeResultOpCode( pToken->GetOpCode()); elseif (bRight) return isRangeResultFunction( pToken->GetOpCode()); else return isPotentialRangeLeftOp( pToken->GetOpCode()); case svSingleRef: case svDoubleRef: case svIndex: // could be range //case svRefList: // um..what? case svExternalSingleRef: case svExternalDoubleRef: case svExternalName: // could be range returntrue; case svSep: // A special case if a previous ocSep was converted to ocUnion it // stays svSep instead of svByte. return bRPN && !bRight && pToken->GetOpCode() == ocUnion; default: // Separators are not part of RPN and right opcodes need to be // other StackVar types or functions and thus svByte. return !bRPN && !bRight && isPotentialRangeLeftOp( pToken->GetOpCode());
}
}
void FormulaCompiler::OpCodeMap::putExternal( const OUString & rSymbol, const OUString & rAddIn )
{ // Different symbols may map to the same AddIn, but the same AddIn may not // map to different symbols, the first pair wins. Same symbol of course may // not map to different AddIns, again the first pair wins and also the // AddIn->symbol mapping is not inserted in other cases. bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
SAL_WARN_IF( !bOk, "formula.core", "OpCodeMap::putExternal: symbol not inserted, " << rSymbol << " -> " << rAddIn); if (bOk)
{
bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second; // Failed insertion of the AddIn is ok for different symbols mapping to // the same AddIn. Make this INFO only.
SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternal: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
}
}
void FormulaCompiler::OpCodeMap::putExternalSoftly( const OUString & rSymbol, const OUString & rAddIn )
{ // Same as putExternal() but no warning, instead info whether inserted or not. bool bOk = maExternalHashMap.emplace(rSymbol, rAddIn).second;
SAL_INFO( "formula.core", "OpCodeMap::putExternalSoftly: symbol " << (bOk ? "" : "not ") << "inserted, " << rSymbol << " -> " << rAddIn); if (bOk)
{
bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
SAL_INFO_IF( !bOk, "formula.core", "OpCodeMap::putExternalSoftly: AddIn not inserted, " << rAddIn << " -> " << rSymbol);
}
}
uno::Sequence< sheet::FormulaToken > FormulaCompiler::OpCodeMap::createSequenceOfFormulaTokens( const FormulaCompiler& rCompiler, const uno::Sequence< OUString >& rNames ) const
{ const sal_Int32 nLen = rNames.getLength();
uno::Sequence< sheet::FormulaToken > aTokens( nLen);
sheet::FormulaToken* pToken = aTokens.getArray();
OUString const * pName = rNames.getConstArray();
OUString const * const pStop = pName + nLen; for ( ; pName < pStop; ++pName, ++pToken)
{
OpCodeHashMap::const_iterator iLook( maHashMap.find( *pName)); if (iLook != maHashMap.end())
pToken->OpCode = (*iLook).second; else
{
OUString aIntName; if (hasExternals())
{
ExternalHashMap::const_iterator iExt( maExternalHashMap.find( *pName)); if (iExt != maExternalHashMap.end())
aIntName = (*iExt).second; // Check for existence not needed here, only name-mapping is of // interest.
} if (aIntName.isEmpty())
aIntName = rCompiler.FindAddInFunction(*pName, !isEnglish()); // bLocalFirst=false for english if (aIntName.isEmpty())
pToken->OpCode = getOpCodeUnknown(); else
{
pToken->OpCode = ocExternal;
pToken->Data <<= aIntName;
}
}
} return aTokens;
}
// Unfortunately uno::Sequence can't grow without cumbersome reallocs. As // we don't know in advance how many elements it will have we use a // temporary vector to add elements and then copy to Sequence :-(
::std::vector< FormulaOpCodeMapEntry > aVec;
for (auto& i : aMap)
{
size_t nIndex = static_cast< size_t >( i.nOff ); if (aVec.size() <= nIndex)
{ // The offsets really should be aligned with the size, so if // the vector was preallocated above this code to resize it is // just a measure in case the table isn't in sync with the API, // usually it isn't executed.
aEntry.Token.OpCode = getOpCodeUnknown();
aVec.resize( nIndex + 1, aEntry );
}
aEntry.Token.OpCode = i.eOp;
aVec[nIndex] = aEntry;
}
} else
{ /* FIXME: Once we support error constants in formulas we'll need a map * group for that, e.g. FormulaMapGroup::ERROR_CONSTANTS, and fill
* SC_OPCODE_START_ERRORS to SC_OPCODE_STOP_ERRORS. */
// Anything else but SPECIAL. if ((nGroups & FormulaMapGroup::SEPARATORS) != 0)
{ staticconst sal_uInt16 aOpCodes[] = {
SC_OPCODE_OPEN,
SC_OPCODE_CLOSE,
SC_OPCODE_SEP,
};
lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
} if ((nGroups & FormulaMapGroup::ARRAY_SEPARATORS) != 0)
{ staticconst sal_uInt16 aOpCodes[] = {
SC_OPCODE_ARRAY_OPEN,
SC_OPCODE_ARRAY_CLOSE,
SC_OPCODE_ARRAY_ROW_SEP,
SC_OPCODE_ARRAY_COL_SEP
};
lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) );
} if ((nGroups & FormulaMapGroup::UNARY_OPERATORS) != 0)
{ // Due to the nature of the percent operator following its operand // it isn't sorted into unary operators for compiler interna.
lclPushOpCodeMapEntry( aVec, mpTable.get(), ocPercentSign ); // "+" can be used as unary operator too, push only if binary group is not set if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) == 0)
lclPushOpCodeMapEntry( aVec, mpTable.get(), ocAdd ); // regular unary operators for (sal_uInt16 nOp = SC_OPCODE_START_UN_OP; nOp < SC_OPCODE_STOP_UN_OP && nOp < mnSymbols; ++nOp)
{
lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
}
} if ((nGroups & FormulaMapGroup::BINARY_OPERATORS) != 0)
{ for (sal_uInt16 nOp = SC_OPCODE_START_BIN_OP; nOp < SC_OPCODE_STOP_BIN_OP && nOp < mnSymbols; ++nOp)
{ switch (nOp)
{ // AND and OR in fact are functions but for legacy reasons // are sorted into binary operators for compiler interna. case SC_OPCODE_AND : case SC_OPCODE_OR : break; // nothing, default:
lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
}
}
} if ((nGroups & FormulaMapGroup::FUNCTIONS) != 0)
{ // Function names are not consecutive, skip the gaps between // functions with no parameter, functions with 1 parameter
lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_NO_PAR,
::std::min< sal_uInt16 >( SC_OPCODE_STOP_NO_PAR, mnSymbols ) );
lclPushOpCodeMapEntries( aVec, mpTable.get(), SC_OPCODE_START_1_PAR,
::std::min< sal_uInt16 >( SC_OPCODE_STOP_1_PAR, mnSymbols ) ); // Additional functions not within range of functions. staticconst sal_uInt16 aOpCodes[] = {
SC_OPCODE_IF,
SC_OPCODE_IF_ERROR,
SC_OPCODE_IF_NA,
SC_OPCODE_CHOOSE,
SC_OPCODE_LET,
SC_OPCODE_AND,
SC_OPCODE_OR
};
lclPushOpCodeMapEntries( aVec, mpTable.get(), aOpCodes, SAL_N_ELEMENTS(aOpCodes) ); // functions with 2 or more parameters. for (sal_uInt16 nOp = SC_OPCODE_START_2_PAR; nOp < SC_OPCODE_STOP_2_PAR && nOp < mnSymbols; ++nOp)
{ switch (nOp)
{ // NO_NAME is in SPECIAL. case SC_OPCODE_NO_NAME : break; // nothing, default:
lclPushOpCodeMapEntry( aVec, mpTable.get(), nOp );
}
} // If AddIn functions are present in this mapping, use them, and only those. if (hasExternals())
{ for (autoconst& elem : maExternalHashMap)
{
FormulaOpCodeMapEntry aEntry;
aEntry.Name = elem.first;
aEntry.Token.Data <<= elem.second;
aEntry.Token.OpCode = ocExternal;
aVec.push_back( aEntry);
}
} else
{
rCompiler.fillAddInToken( aVec, isEnglish());
}
}
} return uno::Sequence< FormulaOpCodeMapEntry >(aVec.data(), aVec.size());
}
void FormulaCompiler::OpCodeMap::putOpCode( const OUString & rStr, const OpCode eOp, const CharClass* pCharClass )
{ if (0 < eOp && sal_uInt16(eOp) < mnSymbols)
{ bool bPutOp = mpTable[eOp].isEmpty(); bool bRemoveFromMap = false; if (!bPutOp)
{ switch (eOp)
{ // These OpCodes are meant to overwrite and also remove an // existing mapping. case ocCurrency:
bPutOp = true;
bRemoveFromMap = true; break; // These separator OpCodes are meant to overwrite and also // remove an existing mapping if it is not used for one of the // other separators. case ocArrayColSep:
bPutOp = true;
bRemoveFromMap = (mpTable[ocArrayRowSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]); break; case ocArrayRowSep:
bPutOp = true;
bRemoveFromMap = (mpTable[ocArrayColSep] != mpTable[eOp] && mpTable[ocSep] != mpTable[eOp]); break; // For ocSep keep the ";" in map but remove any other if it is // not used for ocArrayColSep or ocArrayRowSep. case ocSep:
bPutOp = true;
bRemoveFromMap = (mpTable[eOp] != ";" &&
mpTable[ocArrayColSep] != mpTable[eOp] &&
mpTable[ocArrayRowSep] != mpTable[eOp]); break; // These OpCodes are known to be duplicates in the Excel // external API mapping because of different parameter counts // in different BIFF versions. Names are identical and entries // are ignored. case ocLinest: case ocTrend: case ocLogest: case ocGrowth: case ocTrunc: case ocFixed: case ocGetDayOfWeek: case ocHLookup: case ocVLookup: case ocGetDiffDate360: if (rStr == mpTable[eOp]) return;
[[fallthrough]]; // These OpCodes are known to be added to an existing mapping, // but only for the OOXML external API mapping. This is *not* // FormulaLanguage::OOXML. Keep the first // (correct) definition for the OpCode, all following are // additional alias entries in the map. case ocErrorType: case ocMultiArea: case ocBackSolver: case ocEasterSunday: case ocCurrent: case ocStyle: if (mbEnglish &&
FormulaGrammar::extractFormulaLanguage( meGrammar) == FormulaGrammar::GRAM_EXTERNAL)
{ // Both bPutOp and bRemoveFromMap stay false. break;
}
[[fallthrough]]; default:
SAL_WARN("formula.core", "OpCodeMap::putOpCode: reusing OpCode " << static_cast<sal_uInt16>(eOp)
<< ", replacing '" << mpTable[eOp] << "' with '" << rStr << "' in "
<< (mbEnglish ? "" : "non-") << "English map 0x" << ::std::hex << meGrammar);
}
}
// Case preserving opcode -> string, upper string -> opcode if (bRemoveFromMap)
{
OUString aUpper( pCharClass ? pCharClass->uppercase( mpTable[eOp]) : rStr.toAsciiUpperCase()); // Ensure we remove a mapping only for the requested OpCode.
OpCodeHashMap::const_iterator it( maHashMap.find( aUpper)); if (it != maHashMap.end() && (*it).second == eOp)
maHashMap.erase( it);
} if (bPutOp)
mpTable[eOp] = rStr;
OUString aUpper( pCharClass ? pCharClass->uppercase( rStr) : rStr.toAsciiUpperCase());
maHashMap.emplace(aUpper, eOp);
} else
{
SAL_WARN( "formula.core", "OpCodeMap::putOpCode: OpCode out of range");
}
}
// TODO: For now, just replace the separators to the Excel English // variants. Later, if we want to properly map Excel functions with Calc // functions, we'll need to do a little more work here.
mxSymbolsEnglishXL->putOpCode( OUString(','), ocSep, nullptr);
mxSymbolsEnglishXL->putOpCode( OUString(','), ocArrayColSep, nullptr);
mxSymbolsEnglishXL->putOpCode( OUString(';'), ocArrayRowSep, nullptr);
fillFromAddInMap( rxMap, eGrammar); // Fill from collection for AddIns not already present. if (FormulaGrammar::GRAM_ENGLISH == eGrammar)
fillFromAddInCollectionEnglishName( rxMap); else
{
fillFromAddInCollectionUpperName( rxMap); if (FormulaGrammar::GRAM_API == eGrammar)
{ // Add known but not in AddInMap English names, e.g. from the // PricingFunctions AddIn or any user supplied AddIn.
fillFromAddInCollectionEnglishName( rxMap);
} elseif (FormulaGrammar::GRAM_OOXML == eGrammar)
{ // Add specified Add-In compatibility name.
fillFromAddInCollectionExcelName( rxMap);
}
}
}
bool FormulaCompiler::IsOpCodeVolatile( OpCode eOp )
{ bool bRet = false; switch (eOp)
{ // no parameters: case ocRandom: case ocGetActDate: case ocGetActTime: // one parameter: case ocFormula: case ocInfo: // more than one parameters: // ocIndirect otherwise would have to do // StopListening and StartListening on a reference for every // interpreted value. case ocIndirect: // ocOffset results in indirect references. case ocOffset: // ocDebugVar shows internal value that may change as the internal state changes. case ocDebugVar: // ocRandArray is a volatile function. case ocRandArray:
bRet = true; break; default:
bRet = false; break;
} return bRet;
}
bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp )
{ switch (eOp)
{ case ocIf: case ocIfError: case ocIfNA: case ocChoose: case ocLet: returntrue; default:
;
} returnfalse;
}
bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode )
{ switch (eOpCode)
{ case ocDde : case ocGrowth : case ocTrend : case ocLogest : case ocLinest : case ocFrequency : case ocMatSequence : case ocMatTrans : case ocMatMult : case ocMatInv : case ocMatrixUnit : case ocModalValue_Multi : case ocFourier : case ocFilter : case ocSort : case ocSortBy : case ocRandArray : case ocChooseCols : case ocChooseRows : case ocDrop : case ocExpand : case ocHStack : case ocVStack : case ocTake : case ocTextSplit : case ocToCol : case ocToRow : case ocUnique : case ocLet : case ocWrapCols : case ocWrapRows : returntrue; default:
{ // added to avoid warnings
}
} returnfalse;
}
// For bOverrideKnownBad when copying from the English core map (ODF 1.1 // and API) to the native map (UI "use English function names") replace the // known bad legacy function names with correct ones. if (r.mbCore &&
FormulaGrammar::extractFormulaLanguage( meGrammar) == sheet::FormulaLanguage::NATIVE &&
FormulaGrammar::extractFormulaLanguage( r.meGrammar) == sheet::FormulaLanguage::ENGLISH)
{ for (sal_uInt16 i = 1; i < n; ++i)
{
OUString aSymbol;
OpCode eOp = OpCode(i); switch (eOp)
{ case ocRRI:
aSymbol = "RRI"; break; case ocTableOp:
aSymbol = "MULTIPLE.OPERATIONS"; break; default:
aSymbol = r.mpTable[i];
}
putCopyOpCode( aSymbol, eOp, pCharClass);
}
} else
{ for (sal_uInt16 i = 1; i < n; ++i)
{
OpCode eOp = OpCode(i); const OUString& rSymbol = r.mpTable[i];
putCopyOpCode( rSymbol, eOp, pCharClass);
}
}
// This was meant to copy to native map that does not have AddIn symbols // but needs them from the source map. It is unclear what should happen if // the destination already had externals, so do it only if it doesn't. if (!hasExternals())
{
maExternalHashMap = r.maExternalHashMap;
maReverseExternalHashMap = r.maReverseExternalHashMap;
mbCore = r.mbCore; if (mbEnglish != r.mbEnglish)
{ // For now keep mbEnglishLocale setting, which is false for a // non-English native map we're copying to. /* TODO: if (!mbEnglish && r.mbEnglish) mbEnglishLocale = "getUseEnglishLocaleFromConfiguration()"; or set from outside i.e. via ScCompiler.
*/
mbEnglish = r.mbEnglish;
}
}
}
FormulaError FormulaCompiler::GetErrorConstant( const OUString& rName ) const
{
FormulaError nError = FormulaError::NONE;
OpCodeHashMap::const_iterator iLook( mxSymbols->getHashMap().find( rName)); if (iLook != mxSymbols->getHashMap().end())
{ switch ((*iLook).second)
{ // Not all may make sense in a formula, but these we know as // opcodes. case ocErrNull:
nError = FormulaError::NoCode; break; case ocErrDivZero:
nError = FormulaError::DivisionByZero; break; case ocErrValue:
nError = FormulaError::NoValue; break; case ocErrRef:
nError = FormulaError::NoRef; break; case ocErrName:
nError = FormulaError::NoName; break; case ocErrNum:
nError = FormulaError::IllegalFPOperation; break; case ocErrNA:
nError = FormulaError::NotAvailable; break; default:
; // nothing
}
} else
{ // Per convention recognize detailed "#ERRxxx!" constants, always // untranslated. Error numbers are sal_uInt16 so at most 5 decimal // digits. if (rName.startsWithIgnoreAsciiCase("#ERR") && rName.getLength() <= 10 && rName[rName.getLength()-1] == '!')
{
sal_uInt32 nErr = o3tl::toUInt32(rName.subView( 4, rName.getLength() - 5)); if (0 < nErr && nErr <= SAL_MAX_UINT16 && isPublishedFormulaError(static_cast<FormulaError>(nErr)))
nError = static_cast<FormulaError>(nErr);
}
} return nError;
}
void FormulaCompiler::AppendErrorConstant( OUStringBuffer& rBuffer, FormulaError nError ) const
{
OpCode eOp; switch (nError)
{ case FormulaError::NoCode:
eOp = ocErrNull; break; case FormulaError::DivisionByZero:
eOp = ocErrDivZero; break; case FormulaError::NoValue:
eOp = ocErrValue; break; case FormulaError::NoRef:
eOp = ocErrRef; break; case FormulaError::NoName:
eOp = ocErrName; break; case FormulaError::IllegalFPOperation:
eOp = ocErrNum; break; case FormulaError::NotAvailable:
eOp = ocErrNA; break; default:
{ // Per convention create detailed "#ERRxxx!" constants, always // untranslated.
rBuffer.append("#ERR");
rBuffer.append(static_cast<sal_Int32>(nError));
rBuffer.append('!'); return;
}
}
rBuffer.append( mxSymbols->getSymbol( eOp));
}
constexpr short nRecursionMax = 100;
bool FormulaCompiler::GetToken()
{
FormulaCompilerRecursionGuard aRecursionGuard( nRecursion ); if ( nRecursion > nRecursionMax )
{
SetError( FormulaError::StackOverflow );
mpLastToken = mpToken = new FormulaByteToken( ocStop ); returnfalse;
} if ( bAutoCorrect && !pStack )
{ // don't merge stacked subroutine code into entered formula
aCorrectedFormula += aCorrectedSymbol;
aCorrectedSymbol.clear();
} bool bStop = false; if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
bStop = true; else
{
FormulaTokenRef pSpacesToken; short nWasColRowName; if ( pArr->OpCodeBefore( maArrIterator.GetIndex() ) == ocColRowName )
nWasColRowName = 1; else
nWasColRowName = 0;
OpCode eTmpOp;
mpToken = maArrIterator.Next(); while (mpToken && ((eTmpOp = mpToken->GetOpCode()) == ocSpaces || eTmpOp == ocWhitespace))
{ if (eTmpOp == ocSpaces)
{ // For significant whitespace remember last ocSpaces token. // Usually there's only one even for multiple spaces.
pSpacesToken = mpToken; if ( nWasColRowName )
nWasColRowName++;
} if ( bAutoCorrect && !pStack )
CreateStringFromToken( aCorrectedFormula, mpToken.get() );
mpToken = maArrIterator.Next();
} if ( bAutoCorrect && !pStack && mpToken )
CreateStringFromToken( aCorrectedSymbol, mpToken.get() ); if( !mpToken )
{ if( pStack )
{
PopTokenArray(); // mpLastToken was popped as well and corresponds to the // then current last token during PushTokenArray(), e.g. for // HandleRange(). return GetToken();
} else
bStop = true;
} else
{ if ( nWasColRowName >= 2 && mpToken->GetOpCode() == ocColRowName )
{ // convert an ocSpaces to ocIntersect in RPN
mpLastToken = mpToken = new FormulaByteToken( ocIntersect );
maArrIterator.StepBack(); // we advanced to the second ocColRowName, step back
} elseif (pSpacesToken && FormulaGrammar::isExcelSyntax( meGrammar) &&
mpLastToken && mpToken &&
isPotentialRangeType( mpToken.get(), false, true) &&
(mpLastToken->GetOpCode() == ocClose || isPotentialRangeType( mpLastToken.get(), false, false)))
{ // Let IntersectionLine() <- Factor() decide how to treat this, // once the actual arguments are determined in RPN.
mpLastToken = mpToken = std::move(pSpacesToken);
maArrIterator.StepBack(); // step back from next non-spaces token returntrue;
}
}
} if( bStop )
{
mpLastToken = mpToken = new FormulaByteToken( ocStop ); returnfalse;
}
// Remember token for next round and any PushTokenArray() calls that may // occur in handlers.
mpLastToken = mpToken;
if ( mpToken->IsExternalRef() )
{ return HandleExternalReference(*mpToken);
} else
{ switch (mpToken->GetOpCode())
{ case ocSubTotal: case ocAggregate:
glSubTotal = true; break; case ocStringName: if( HandleStringName()) returntrue; else returnfalse; case ocName: if( HandleRange())
{ // Expanding ocName might have introduced tokens such as ocStyle that prevent formula threading, // but those wouldn't be present in the raw tokens array, so ensure RPN tokens will be checked too.
needsRPNTokenCheck = true; returntrue;
} returnfalse; case ocColRowName: return HandleColRowName(); case ocDBArea: return HandleDbData(); case ocTableRef: return HandleTableRef(); case ocPush: if( mbComputeII )
HandleIIOpCode(mpToken.get(), nullptr, 0); break; default:
; // nothing
}
} returntrue;
}
// RPN creation by recursion void FormulaCompiler::Factor()
{ if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError) return;
CurrentFactor pFacToken( this );
OpCode eOp = mpToken->GetOpCode(); if (eOp == ocPush || eOp == ocColRowNameAuto || eOp == ocMatRef || eOp == ocDBArea
|| eOp == ocTableRef
|| (!mbJumpCommandReorder && ((eOp == ocName) || (eOp == ocColRowName) || (eOp == ocBad)))
)
{
PutCode( mpToken );
eOp = NextToken(); if( eOp == ocOpen )
{ // PUSH( is an error that may be caused by an unknown function.
SetError(
( mpToken->GetType() == svString
|| mpToken->GetType() == svSingleRef )
? FormulaError::NoName : FormulaError::OperatorExpected ); if ( bAutoCorrect && !pStack )
{ // assume multiplication
aCorrectedFormula += mxSymbols->getSymbol( ocMul);
bCorrected = true;
NextToken();
eOp = Expression(); if( eOp != ocClose )
SetError( FormulaError::PairExpected); else
NextToken();
}
}
} elseif( eOp == ocOpen )
{
NextToken();
eOp = Expression(); while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
{ // range list (A1;A2) converted to (A1~A2)
pFacToken = mpToken;
NextToken();
CheckSetForceArrayParameter( mpToken, 0);
eOp = Expression(); // Do not ignore error here, regardless of mbStopOnError, to not // change the formula expression in case of an unexpected state. if (pArr->GetCodeError() == FormulaError::NONE && pc >= 2)
{ // Left and right operands must be reference or function // returning reference to form a range list. const FormulaToken* p = pCode[-2]; if (p && isPotentialRangeType( p, true, false))
{
p = pCode[-1]; if (p && isPotentialRangeType( p, true, true))
{
pFacToken->NewOpCode( ocUnion, FormulaToken::PrivateAccess()); // XXX NOTE: the token's eType is still svSep here!
PutCode( pFacToken);
}
}
}
} if (eOp != ocClose)
SetError( FormulaError::PairExpected); else
NextToken();
/* TODO: if no conversion to ocUnion is involved this could collect * such expression as a list or (matrix) vector to be passed as * argument for one parameter (which in fact the ocUnion svRefList is a * special case of), which would require a new StackVar type and needed * to be handled by the interpreter for functions that could support it * (i.e. already handle VAR_ARGS or svRefList parameters). This is also * not defined by ODF. * Does Excel handle =SUM((1;2))? * As is, the interpreter catches extraneous uncalculated
* subexpressions like 1 of (1;2) as error. */
} else
{ if( nNumFmt == SvNumFormatType::UNDEFINED )
nNumFmt = lcl_GetRetFormat( eOp );
if ( IsOpCodeVolatile( eOp) )
pArr->SetExclusiveRecalcModeAlways(); else
{ switch( eOp )
{ // Functions recalculated on every document load. // ONLOAD_LENIENT here to be able to distinguish and not // force a recalc (if not in an ALWAYS or ONLOAD_MUST // context) but keep an imported result from for example // OOXML a DDE call. Will be recalculated for ODFF. case ocConvertOOo : case ocDde: case ocMacro: case ocWebservice:
pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); break; // RANDBETWEEN() is volatile like RAND(). Other Add-In // functions may have to be recalculated or not, we don't // know, classify as ONLOAD_LENIENT. case ocExternal: if (mpToken->GetExternal() == "com.sun.star.sheet.addin.Analysis.getRandbetween")
pArr->SetExclusiveRecalcModeAlways(); else
pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT ); break; // If the referred cell is moved the value changes. case ocColumn : case ocRow :
pArr->SetRecalcModeOnRefMove(); break; // ocCell needs recalc on move for some possible type values. // And recalc mode on load, tdf#60645 case ocCell :
pArr->SetRecalcModeOnRefMove();
pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST ); break; case ocHyperLink : // Cell with hyperlink needs to be calculated on load to // get its matrix result generated.
pArr->AddRecalcMode( ScRecalcMode::ONLOAD_MUST );
pArr->SetHyperLink( true); break; default:
; // nothing
}
} if (SC_OPCODE_START_NO_PAR <= eOp && eOp < SC_OPCODE_STOP_NO_PAR)
{
pFacToken = mpToken;
eOp = NextToken(); if (eOp != ocOpen)
{
SetError( FormulaError::PairExpected);
PutCode( pFacToken );
} else
{
eOp = NextToken(); if (eOp != ocClose)
SetError( FormulaError::PairExpected);
PutCode( pFacToken);
NextToken();
}
} elseif (SC_OPCODE_START_1_PAR <= eOp && eOp < SC_OPCODE_STOP_1_PAR)
{ if (eOp == ocIsoWeeknum && FormulaGrammar::isODFF( meGrammar ))
{ // tdf#50950 ocIsoWeeknum can have 2 arguments when saved by older versions of Calc; // the opcode then has to be changed to ocWeek for backward compatibility
pFacToken = mpToken;
eOp = NextToken(); bool bNoParam = false; if (eOp == ocOpen)
{
eOp = NextToken(); if (eOp == ocClose)
bNoParam = true; else
{
CheckSetForceArrayParameter( mpToken, 0);
eOp = Expression();
}
} else
SetError( FormulaError::PairExpected);
sal_uInt32 nSepCount = 0; const sal_uInt16 nSepPos = maArrIterator.GetIndex() - 1; // separator position, if any if( !bNoParam )
{
nSepCount++; while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
{
NextToken();
CheckSetForceArrayParameter( mpToken, nSepCount);
nSepCount++; if (nSepCount > FORMULA_MAXPARAMS)
SetError( FormulaError::CodeOverflow);
eOp = Expression();
}
} if (eOp != ocClose)
SetError( FormulaError::PairExpected); else
NextToken();
pFacToken->SetByte( nSepCount ); if (nSepCount == 2)
{ // An old mode!=1 indicates ISO week, remove argument if // literal double value and keep function. Anything else // can not be resolved, there exists no "like ISO but week // starts on Sunday" mode in WEEKNUM and for an expression // we can't determine. // Current index is nSepPos+3 if expression stops, or // nSepPos+4 if expression continues after the call because // we just called NextToken() to move away from it. if (pc >= 2 && (maArrIterator.GetIndex() == nSepPos + 3 || maArrIterator.GetIndex() == nSepPos + 4) &&
pArr->TokenAt(nSepPos+1)->GetType() == svDouble &&
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.