/* -*- 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/alloca.h>
#include <sal/log.hxx>
#include <formula/FormulaCompiler.hxx>
#include <formula/errorcodes.hxx>
#include <formula/token.hxx>
#include <formula/tokenarray.hxx>
#include <core_resource.hxx>
#include <core_resource.hrc>
#include <osl/mutex.hxx>
#include <svl/zforlist.hxx>
#include <unotools/resmgr.hxx>
#include <com/sun/star/sheet/FormulaOpCodeMapEntry.hpp>
#include <com/sun/star/sheet/FormulaMapGroup.hpp>
#include <com/sun/star/sheet/FormulaMapGroupSpecialOffset.hpp>
#include <rtl/strbuf.hxx>
#include <algorithm>
namespace formula
{
using namespace ::com::sun::star;
static const sal_Char* pInternal[2] = { "TTT", "__DEBUG_VAR" };
namespace {
class FormulaCompilerRecursionGuard
{
private:
short& rRecursion;
public:
explicit FormulaCompilerRecursionGuard( short& rRec )
: rRecursion( rRec ) { ++rRecursion; }
~FormulaCompilerRecursionGuard() { --rRecursion; }
};
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;
}
}
inline void lclPushOpCodeMapEntry( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
const OUString* pTable, sal_uInt16 nOpCode )
{
sheet::FormulaOpCodeMapEntry aEntry;
aEntry.Token.OpCode = nOpCode;
aEntry.Name = pTable[nOpCode];
rVec.push_back( aEntry);
}
void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
const OUString* pTable, sal_uInt16 nOpCodeBeg, sal_uInt16 nOpCodeEnd )
{
for (sal_uInt16 nOpCode = nOpCodeBeg; nOpCode < nOpCodeEnd; ++nOpCode)
lclPushOpCodeMapEntry( rVec, pTable, nOpCode );
}
void lclPushOpCodeMapEntries( ::std::vector< sheet::FormulaOpCodeMapEntry >& rVec,
const OUString* pTable, const sal_uInt16* pnOpCodes, size_t nCount )
{
for (const sal_uInt16* pnEnd = pnOpCodes + nCount; pnOpCodes < pnEnd; ++pnOpCodes)
lclPushOpCodeMapEntry( rVec, pTable, *pnOpCodes );
}
class OpCodeList
{
public:
OpCodeList(bool bLocalized, const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr&,
FormulaCompiler::SeparatorType = FormulaCompiler::SeparatorType::SEMICOLON_BASE );
private:
bool getOpCodeString( OUString& rStr, sal_uInt16 nOp );
void putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp, const CharClass* pCharClass );
private:
FormulaCompiler::SeparatorType meSepType;
const std::pair<const char*, int>* mpSymbols;
bool mbLocalized;
};
OpCodeList::OpCodeList(bool bLocalized, const std::pair<const char*, int>* pSymbols, const FormulaCompiler::NonConstOpCodeMapPtr& xMap,
FormulaCompiler::SeparatorType eSepType)
: meSepType(eSepType)
, mpSymbols(pSymbols)
, mbLocalized(bLocalized)
{
SvtSysLocale aSysLocale;
const CharClass* pCharClass = (xMap->isEnglish() ? nullptr : aSysLocale.GetCharClassPtr());
if (meSepType == FormulaCompiler::SeparatorType::RESOURCE_BASE)
{
for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
{
putDefaultOpCode( xMap, i, pCharClass);
}
}
else
{
for (sal_uInt16 i = 0; i <= SC_OPCODE_LAST_OPCODE_ID; ++i)
{
OUString aOpStr;
if ( getOpCodeString( aOpStr, i) )
xMap->putOpCode( aOpStr, OpCode(i), pCharClass);
else
putDefaultOpCode( xMap, i, pCharClass);
}
}
}
bool OpCodeList::getOpCodeString( OUString& rStr, sal_uInt16 nOp )
{
switch (nOp)
{
case SC_OPCODE_SEP:
{
if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
{
rStr = ";";
return true;
}
}
break;
case SC_OPCODE_ARRAY_COL_SEP:
{
if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
{
rStr = ";";
return true;
}
}
break;
case SC_OPCODE_ARRAY_ROW_SEP:
{
if (meSepType == FormulaCompiler::SeparatorType::SEMICOLON_BASE)
{
rStr = "|";
return true;
}
}
break;
}
return false;
}
void OpCodeList::putDefaultOpCode( const FormulaCompiler::NonConstOpCodeMapPtr& xMap, sal_uInt16 nOp,
const CharClass* pCharClass )
{
const char* pKey = nullptr;
for (const std::pair<const char*, int>* pSymbol = mpSymbols; pSymbol->first; ++pSymbol)
{
if (nOp == pSymbol->second)
{
pKey = pSymbol->first;
break;
}
}
if (!pKey)
return;
OUString sKey = !mbLocalized ? OUString::createFromAscii(pKey) : ForResId(pKey);
xMap->putOpCode(sKey, OpCode(nOp), pCharClass);
}
// static
const sal_Unicode* lcl_UnicodeStrChr( const sal_Unicode* pStr, sal_Unicode c )
{
if ( !pStr )
return nullptr;
while ( *pStr )
{
if ( *pStr == c )
return pStr;
pStr++;
}
return nullptr;
}
struct OpCodeMapData
{
FormulaCompiler::NonConstOpCodeMapPtr mxSymbolMap;
osl::Mutex maMtx;
};
bool isPotentialRangeLeftOp( OpCode eOp )
{
switch (eOp)
{
case ocClose:
return true;
default:
return false;
}
}
bool isRangeResultFunction( OpCode eOp )
{
switch (eOp)
{
case ocIndirect:
case ocOffset:
return true;
default:
return false;
}
}
bool isRangeResultOpCode( OpCode eOp )
{
switch (eOp)
{
case ocRange:
case ocUnion:
case ocIntersect:
case ocIndirect:
case ocOffset:
return true;
default:
return false;
}
}
/**
@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.
*/
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());
else if (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
return true;
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());
}
}
bool isIntersectable( FormulaToken** pCode1, FormulaToken** pCode2 )
{
FormulaToken* pToken1 = *pCode1;
FormulaToken* pToken2 = *pCode2;
if (pToken1 && pToken2)
return isPotentialRangeType( pToken1, true, false) && isPotentialRangeType( pToken2, true, true);
return false;
}
bool isAdjacentRpnEnd( sal_uInt16 nPC,
FormulaToken const * const * const pCode,
FormulaToken const * const * const pCode1,
FormulaToken const * const * const pCode2 )
{
return nPC >= 2 && pCode1 && pCode2 &&
(pCode2 - pCode1 == 1) && (pCode - pCode2 == 1) &&
(*pCode1 != nullptr) && (*pCode2 != nullptr);
}
bool isAdjacentOrGapRpnEnd( sal_uInt16 nPC,
FormulaToken const * const * const pCode,
FormulaToken const * const * const pCode1,
FormulaToken const * const * const pCode2 )
{
return nPC >= 2 && pCode1 && pCode2 &&
(pCode2 > pCode1) && (pCode - pCode2 == 1) &&
(*pCode1 != nullptr) && (*pCode2 != nullptr);
}
} // namespace
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 )
{
bool bOk = maReverseExternalHashMap.emplace(rAddIn, rSymbol).second;
if (bOk)
maExternalHashMap.emplace(rSymbol, rAddIn);
}
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;
}
uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::createSequenceOfAvailableMappings(
const FormulaCompiler& rCompiler, const sal_Int32 nGroups ) const
{
using namespace sheet;
// 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;
if (nGroups == FormulaMapGroup::SPECIAL)
{
// Use specific order, keep in sync with
// offapi/com/sun/star/sheet/FormulaMapGroupSpecialOffset.idl
static const struct
{
sal_Int32 nOff;
OpCode eOp;
} aMap[] = {
{ FormulaMapGroupSpecialOffset::PUSH , ocPush } ,
{ FormulaMapGroupSpecialOffset::CALL , ocCall } ,
{ FormulaMapGroupSpecialOffset::STOP , ocStop } ,
{ FormulaMapGroupSpecialOffset::EXTERNAL , ocExternal } ,
{ FormulaMapGroupSpecialOffset::NAME , ocName } ,
{ FormulaMapGroupSpecialOffset::NO_NAME , ocNoName } ,
{ FormulaMapGroupSpecialOffset::MISSING , ocMissing } ,
{ FormulaMapGroupSpecialOffset::BAD , ocBad } ,
{ FormulaMapGroupSpecialOffset::SPACES , ocSpaces } ,
{ FormulaMapGroupSpecialOffset::MAT_REF , ocMatRef } ,
{ FormulaMapGroupSpecialOffset::DB_AREA , ocDBArea } ,
/* TODO: { FormulaMapGroupSpecialOffset::TABLE_REF , ocTableRef } , */
{ FormulaMapGroupSpecialOffset::MACRO , ocMacro } ,
{ FormulaMapGroupSpecialOffset::COL_ROW_NAME , ocColRowName }
};
const size_t nCount = SAL_N_ELEMENTS(aMap);
// Preallocate vector elements.
if (aVec.size() < nCount)
{
FormulaOpCodeMapEntry aEntry;
aEntry.Token.OpCode = getOpCodeUnknown();
aVec.resize( nCount, aEntry);
} // if (aVec.size() < nCount)
FormulaOpCodeMapEntry aEntry;
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)
{
static const 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)
{
static const 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.
static const sal_uInt16 aOpCodes[] = {
SC_OPCODE_IF,
SC_OPCODE_IF_ERROR,
SC_OPCODE_IF_NA,
SC_OPCODE_CHOOSE,
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 (auto const& 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[ocArrayColSep] != 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;
SAL_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;
}
SAL_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");
}
}
// class FormulaCompiler
FormulaCompiler::FormulaCompiler( FormulaTokenArray& rArr, bool bComputeII, bool bMatrixFlag )
:
nCurrentFactorParam(0),
pArr( &rArr ),
maArrIterator( rArr ),
pCode( nullptr ),
pStack( nullptr ),
eLastOp( ocPush ),
nRecursion( 0 ),
nNumFmt( SvNumFormatType::UNDEFINED ),
pc( 0 ),
meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
bAutoCorrect( false ),
bCorrected( false ),
glSubTotal( false ),
needsRPNTokenCheck( false ),
mbJumpCommandReorder(true),
mbStopOnError(true),
mbComputeII(bComputeII),
mbMatrixFlag(bMatrixFlag)
{
}
FormulaTokenArray FormulaCompiler::smDummyTokenArray;
FormulaCompiler::FormulaCompiler(bool bComputeII, bool bMatrixFlag)
:
nCurrentFactorParam(0),
pArr( nullptr ),
maArrIterator( smDummyTokenArray ),
pCode( nullptr ),
pStack( nullptr ),
eLastOp( ocPush ),
nRecursion(0),
nNumFmt( SvNumFormatType::UNDEFINED ),
pc( 0 ),
meGrammar( formula::FormulaGrammar::GRAM_UNSPECIFIED ),
bAutoCorrect( false ),
bCorrected( false ),
glSubTotal( false ),
needsRPNTokenCheck( false ),
mbJumpCommandReorder(true),
mbStopOnError(true),
mbComputeII(bComputeII),
mbMatrixFlag(bMatrixFlag)
{
}
FormulaCompiler::~FormulaCompiler()
{
}
FormulaCompiler::OpCodeMapPtr FormulaCompiler::GetOpCodeMap( const sal_Int32 nLanguage ) const
{
FormulaCompiler::OpCodeMapPtr xMap;
using namespace sheet;
switch (nLanguage)
{
case FormulaLanguage::ODFF :
if (!mxSymbolsODFF)
InitSymbolsODFF();
xMap = mxSymbolsODFF;
break;
case FormulaLanguage::ODF_11 :
if (!mxSymbolsPODF)
InitSymbolsPODF();
xMap = mxSymbolsPODF;
break;
case FormulaLanguage::ENGLISH :
if (!mxSymbolsEnglish)
InitSymbolsEnglish();
xMap = mxSymbolsEnglish;
break;
case FormulaLanguage::NATIVE :
if (!mxSymbolsNative)
InitSymbolsNative();
xMap = mxSymbolsNative;
break;
case FormulaLanguage::XL_ENGLISH:
if (!mxSymbolsEnglishXL)
InitSymbolsEnglishXL();
xMap = mxSymbolsEnglishXL;
break;
case FormulaLanguage::OOXML:
if (!mxSymbolsOOXML)
InitSymbolsOOXML();
xMap = mxSymbolsOOXML;
break;
case FormulaLanguage::API :
if (!mxSymbolsAPI)
InitSymbolsAPI();
xMap = mxSymbolsAPI;
break;
default:
; // nothing, NULL map returned
}
return xMap;
}
OUString FormulaCompiler::FindAddInFunction( const OUString& /*rUpperName*/, bool /*bLocalFirst*/ ) const
{
return OUString();
}
FormulaCompiler::OpCodeMapPtr FormulaCompiler::CreateOpCodeMap(
const uno::Sequence<
const sheet::FormulaOpCodeMapEntry > & rMapping,
bool bEnglish )
{
using sheet::FormulaOpCodeMapEntry;
// Filter / API maps are never Core
NonConstOpCodeMapPtr xMap( new OpCodeMap( SC_OPCODE_LAST_OPCODE_ID + 1, false,
FormulaGrammar::mergeToGrammar( FormulaGrammar::setEnglishBit(
FormulaGrammar::GRAM_EXTERNAL, bEnglish), FormulaGrammar::CONV_UNSPECIFIED)));
SvtSysLocale aSysLocale;
const CharClass* pCharClass = (xMap->isEnglish() ? nullptr : aSysLocale.GetCharClassPtr());
for (auto const& rMapEntry : rMapping)
{
OpCode eOp = OpCode(rMapEntry.Token.OpCode);
if (eOp != ocExternal)
xMap->putOpCode( rMapEntry.Name, eOp, pCharClass);
else
{
OUString aExternalName;
if (rMapEntry.Token.Data >>= aExternalName)
xMap->putExternal( rMapEntry.Name, aExternalName);
else
{
SAL_WARN( "formula.core", "FormulaCompiler::CreateOpCodeMap: no Token.Data external name");
}
}
}
return xMap;
}
void lcl_fillNativeSymbols( FormulaCompiler::NonConstOpCodeMapPtr& xMap, bool bDestroy = false )
{
static OpCodeMapData aSymbolMap;
osl::MutexGuard aGuard(&aSymbolMap.maMtx);
if ( bDestroy )
{
aSymbolMap.mxSymbolMap.reset();
}
else if (!aSymbolMap.mxSymbolMap)
{
// Core
aSymbolMap.mxSymbolMap.reset(
new FormulaCompiler::OpCodeMap(
SC_OPCODE_LAST_OPCODE_ID + 1, true, FormulaGrammar::GRAM_NATIVE_UI));
OpCodeList aOpCodeListSymbols(false, RID_STRLIST_FUNCTION_NAMES_SYMBOLS, aSymbolMap.mxSymbolMap);
OpCodeList aOpCodeListNative(true, RID_STRLIST_FUNCTION_NAMES, aSymbolMap.mxSymbolMap);
// No AddInMap for native core mapping.
}
xMap = aSymbolMap.mxSymbolMap;
}
const OUString& FormulaCompiler::GetNativeSymbol( OpCode eOp )
{
NonConstOpCodeMapPtr xSymbolsNative;
lcl_fillNativeSymbols( xSymbolsNative);
return xSymbolsNative->getSymbol( eOp );
}
sal_Unicode FormulaCompiler::GetNativeSymbolChar( OpCode eOp )
{
return GetNativeSymbol(eOp)[0];
}
void FormulaCompiler::InitSymbolsNative() const
{
lcl_fillNativeSymbols( mxSymbolsNative);
}
void FormulaCompiler::InitSymbolsEnglish() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
mxSymbolsEnglish = aMap.mxSymbolMap;
}
void FormulaCompiler::InitSymbolsPODF() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF, FormulaGrammar::GRAM_PODF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
mxSymbolsPODF = aMap.mxSymbolMap;
}
void FormulaCompiler::InitSymbolsAPI() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
// XFunctionAccess API always used PODF grammar, keep it.
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_API, FormulaGrammar::GRAM_PODF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
mxSymbolsAPI = aMap.mxSymbolMap;
}
void FormulaCompiler::InitSymbolsODFF() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF, FormulaGrammar::GRAM_ODFF, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
mxSymbolsODFF = aMap.mxSymbolMap;
}
void FormulaCompiler::InitSymbolsEnglishXL() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH, FormulaGrammar::GRAM_ENGLISH, aMap.mxSymbolMap);
mxSymbolsEnglishXL = aMap.mxSymbolMap;
// 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);
}
void FormulaCompiler::InitSymbolsOOXML() const
{
static OpCodeMapData aMap;
osl::MutexGuard aGuard(&aMap.maMtx);
if (!aMap.mxSymbolMap)
loadSymbols(RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML, FormulaGrammar::GRAM_OOXML, aMap.mxSymbolMap, SeparatorType::RESOURCE_BASE);
mxSymbolsOOXML = aMap.mxSymbolMap;
}
void FormulaCompiler::loadSymbols(const std::pair<const char*, int>* pSymbols, FormulaGrammar::Grammar eGrammar,
NonConstOpCodeMapPtr& rxMap, SeparatorType eSepType) const
{
if ( !rxMap.get() )
{
// not Core
rxMap.reset( new OpCodeMap( SC_OPCODE_LAST_OPCODE_ID + 1, eGrammar != FormulaGrammar::GRAM_ODFF, eGrammar ));
OpCodeList aOpCodeList(false, pSymbols, rxMap, eSepType);
fillFromAddInMap( rxMap, eGrammar);
// Fill from collection for AddIns not already present.
if ( FormulaGrammar::GRAM_ENGLISH != eGrammar )
fillFromAddInCollectionUpperName( rxMap);
else
fillFromAddInCollectionEnglishName( rxMap);
}
}
void FormulaCompiler::fillFromAddInCollectionUpperName( const NonConstOpCodeMapPtr& /*xMap */) const
{
}
void FormulaCompiler::fillFromAddInCollectionEnglishName( const NonConstOpCodeMapPtr& /*xMap */) const
{
}
void FormulaCompiler::fillFromAddInMap( const NonConstOpCodeMapPtr& /*xMap*/, FormulaGrammar::Grammar /*_eGrammar */) const
{
}
OpCode FormulaCompiler::GetEnglishOpCode( const OUString& rName ) const
{
FormulaCompiler::OpCodeMapPtr xMap = GetOpCodeMap( sheet::FormulaLanguage::ENGLISH);
formula::OpCodeHashMap::const_iterator iLook( xMap->getHashMap().find( rName ) );
bool bFound = (iLook != xMap->getHashMap().end());
return bFound ? (*iLook).second : ocNone;
}
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/ocIndirectXL otherwise would have to do
// StopListening and StartListening on a reference for every
// interpreted value.
case ocIndirect:
case ocIndirectXL:
// ocOffset results in indirect references.
case ocOffset:
// ocDebugVar shows internal value that may change as the internal state changes.
case ocDebugVar:
bRet = true;
break;
default:
bRet = false;
break;
}
return bRet;
}
bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp )
{
switch (eOp)
{
case ocIf:
case ocIfError:
case ocIfNA:
case ocChoose:
return true;
default:
;
}
return false;
}
// Remove quotes, escaped quotes are unescaped.
bool FormulaCompiler::DeQuote( OUString& rStr )
{
sal_Int32 nLen = rStr.getLength();
if ( nLen > 1 && rStr[0] == '\'' && rStr[ nLen-1 ] == '\'' )
{
rStr = rStr.copy( 1, nLen-2 );
rStr = rStr.replaceAll( "\\\'", "\'" );
return true;
}
return false;
}
void FormulaCompiler::fillAddInToken(
::std::vector< sheet::FormulaOpCodeMapEntry >& /*_rVec*/,
bool /*_bIsEnglish*/) const
{
}
bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode )
{
switch (eOpCode)
{
case ocDde :
case ocGrowth :
case ocTrend :
case ocLogest :
case ocLinest :
case ocFrequency :
case ocMatTrans :
case ocMatMult :
case ocMatInv :
case ocMatrixUnit :
case ocModalValue_Multi :
return true;
default:
{
// added to avoid warnings
}
}
return false;
}
void FormulaCompiler::OpCodeMap::putCopyOpCode( const OUString& rSymbol, OpCode eOp )
{
SAL_WARN_IF( !mpTable[eOp].isEmpty() && rSymbol.isEmpty(), "formula.core",
"OpCodeMap::putCopyOpCode: NOT replacing OpCode " << static_cast<sal_uInt16>(eOp)
<< " '" << mpTable[eOp] << "' with empty name!");
if (!mpTable[eOp].isEmpty() && rSymbol.isEmpty())
maHashMap.emplace(mpTable[eOp], eOp);
else
{
mpTable[eOp] = rSymbol;
maHashMap.emplace(rSymbol, eOp);
}
}
void FormulaCompiler::OpCodeMap::copyFrom( const OpCodeMap& r )
{
maHashMap = OpCodeHashMap( mnSymbols);
sal_uInt16 n = r.getSymbolCount();
SAL_WARN_IF( n != mnSymbols, "formula.core",
"OpCodeMap::copyFrom: unequal size, this: " << mnSymbols << " that: " << n);
if (n > mnSymbols)
n = mnSymbols;
// OpCode 0 (ocPush) should never be in a map.
SAL_WARN_IF( !mpTable[0].isEmpty() || !r.mpTable[0].isEmpty(), "formula.core",
"OpCodeMap::copyFrom: OpCode 0 assigned, this: '"
<< mpTable[0] << "' that: '" << r.mpTable[0] << "'");
// 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);
}
}
else
{
for (sal_uInt16 i = 1; i < n; ++i)
{
OpCode eOp = OpCode(i);
const OUString& rSymbol = r.mpTable[i];
putCopyOpCode( rSymbol, eOp);
}
}
// TODO: maybe copy the external maps too?
}
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 = rName.copy( 4, rName.getLength() - 5).toUInt32();
if (0 < nErr && nErr <= SAL_MAX_UINT16 && isPublishedFormulaError(static_cast<FormulaError>(nErr)))
nError = static_cast<FormulaError>(nErr);
}
}
return nError;
}
void FormulaCompiler::EnableJumpCommandReorder( bool bEnable )
{
mbJumpCommandReorder = bEnable;
}
void FormulaCompiler::EnableStopOnError( bool bEnable )
{
mbStopOnError = bEnable;
}
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));
}
bool FormulaCompiler::GetToken()
{
static const short nRecursionMax = 42;
FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
if ( nRecursion > nRecursionMax )
{
SetError( FormulaError::StackOverflow );
mpLastToken = mpToken = new FormulaByteToken( ocStop );
return false;
}
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;
mpToken = maArrIterator.Next();
while( mpToken && mpToken->GetOpCode() == 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
}
else if (pSpacesToken && FormulaGrammar::isExcelSyntax( meGrammar) &&
mpLastToken && mpToken &&
isPotentialRangeType( mpLastToken.get(), false, false) &&
isPotentialRangeType( mpToken.get(), false, true))
{
// Let IntersectionLine() <- Factor() decide how to treat this,
// once the actual arguments are determined in RPN.
mpLastToken = mpToken = pSpacesToken;
maArrIterator.StepBack(); // step back from next non-spaces token
return true;
}
}
}
if( bStop )
{
mpLastToken = mpToken = new FormulaByteToken( ocStop );
return false;
}
// 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 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;
return true;
}
return false;
case ocColRowName:
return HandleColRowName();
case ocDBArea:
return HandleDbData();
case ocTableRef:
return HandleTableRef();
case ocPush:
if( mbComputeII )
HandleIIOpCode(mpToken.get(), nullptr, 0);
break;
default:
; // nothing
}
}
return true;
}
// 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 == ocDBArea)
|| (eOp == ocTableRef) || (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();
}
}
}
else if( 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 bIgnoreErrors, otherwise
// errors like =(1;) would also result in display of =(1~)
if (pArr->GetCodeError() == FormulaError::NONE)
{
pFacToken->NewOpCode( ocUnion, FormulaToken::PrivateAccess());
PutCode( pFacToken);
}
}
if (eOp != ocClose)
SetError( FormulaError::PairExpected);
else
NextToken();
}
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 ocExternal:
case ocWebservice:
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();
}
}
else if (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 &&
pArr->TokenAt(nSepPos+1)->GetDouble() != 1.0 &&
pArr->TokenAt(nSepPos+2)->GetOpCode() == ocClose &&
pArr->RemoveToken( nSepPos, 2) == 2)
{
maArrIterator.AfterRemoveToken( nSepPos, 2);
// Remove the ocPush/svDouble just removed also from
// the compiler local RPN array.
--pCode; --pc;
(*pCode)->DecRef(); // may be dead now
pFacToken->SetByte( nSepCount - 1 );
}
else
{
// For the remaining two arguments cases use the
// compatibility function.
pFacToken->NewOpCode( ocWeeknumOOo, FormulaToken::PrivateAccess());
}
}
PutCode( pFacToken );
}
else
{
// standard handling of 1-parameter opcodes
pFacToken = mpToken;
eOp = NextToken();
if( nNumFmt == SvNumFormatType::UNDEFINED && eOp == ocNot )
nNumFmt = SvNumFormatType::LOGICAL;
if (eOp == ocOpen)
{
NextToken();
CheckSetForceArrayParameter( mpToken, 0);
eOp = Expression();
}
else
SetError( FormulaError::PairExpected);
if (eOp != ocClose)
SetError( FormulaError::PairExpected);
else if ( pArr->GetCodeError() == FormulaError::NONE )
{
pFacToken->SetByte( 1 );
if (mbComputeII)
{
FormulaToken** pArg = pCode - 1;
HandleIIOpCode(pFacToken, &pArg, 1);
}
}
PutCode( pFacToken );
NextToken();
}
}
else if ((SC_OPCODE_START_2_PAR <= eOp && eOp < SC_OPCODE_STOP_2_PAR)
|| eOp == ocExternal
|| eOp == ocMacro
|| eOp == ocAnd
|| eOp == ocOr
|| eOp == ocBad
|| ( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
|| (!mbJumpCommandReorder && IsOpCodeJumpCommand(eOp)))
{
pFacToken = mpToken;
OpCode eMyLastOp = eOp;
eOp = NextToken();
bool bNoParam = false;
bool bBadName = false;
if (eOp == ocOpen)
{
eOp = NextToken();
if (eOp == ocClose)
bNoParam = true;
else
{
CheckSetForceArrayParameter( mpToken, 0);
eOp = Expression();
}
}
else if (eMyLastOp == ocBad)
{
// Just a bad name, not an unknown function, no parameters, no
// closing expected.
bBadName = true;
bNoParam = true;
}
else
SetError( FormulaError::PairExpected);
sal_uInt32 nSepCount = 0;
if( !bNoParam )
{
bool bDoIICompute = mbComputeII;
// Array of FormulaToken double pointers to collect the parameters of II opcodes.
FormulaToken*** pArgArray = nullptr;
if (bDoIICompute)
{
pArgArray = static_cast<FormulaToken***>(alloca(sizeof(FormulaToken**)*FORMULA_MAXPARAMSII));
if (!pArgArray)
bDoIICompute = false;
}
nSepCount++;
if (bDoIICompute)
pArgArray[nSepCount-1] = pCode - 1; // Add first argument
while ((eOp == ocSep) && (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
{
NextToken();
CheckSetForceArrayParameter( mpToken, nSepCount);
nSepCount++;
if (nSepCount > FORMULA_MAXPARAMS)
SetError( FormulaError::CodeOverflow);
eOp = Expression();
if (bDoIICompute && nSepCount <= FORMULA_MAXPARAMSII)
pArgArray[nSepCount - 1] = pCode - 1; // Add rest of the arguments
}
if (bDoIICompute)
HandleIIOpCode(pFacToken, pArgArray,
std::min(nSepCount, static_cast<sal_uInt32>(FORMULA_MAXPARAMSII)));
}
if (bBadName)
; // nothing, keep current token for return
else if (eOp != ocClose)
SetError( FormulaError::PairExpected);
else
NextToken();
// Jumps are just normal functions for the FunctionAutoPilot tree view
if (!mbJumpCommandReorder && pFacToken->GetType() == svJump)
pFacToken = new FormulaFAPToken( pFacToken->GetOpCode(), nSepCount, pFacToken );
else
pFacToken->SetByte( nSepCount );
PutCode( pFacToken );
}
else if (IsOpCodeJumpCommand(eOp))
{
// the PC counters are -1
pFacToken = mpToken;
switch (eOp)
{
case ocIf:
pFacToken->GetJump()[ 0 ] = 3; // if, else, behind
break;
case ocChoose:
pFacToken->GetJump()[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
break;
case ocIfError:
case ocIfNA:
pFacToken->GetJump()[ 0 ] = 2; // if, behind
break;
default:
SAL_WARN("formula.core","Jump OpCode: " << +eOp);
assert(!"FormulaCompiler::Factor: someone forgot to add a jump count case");
}
eOp = NextToken();
if (eOp == ocOpen)
{
NextToken();
CheckSetForceArrayParameter( mpToken, 0);
eOp = Expression();
}
else
SetError( FormulaError::PairExpected);
PutCode( pFacToken );
// 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();
switch (eFacOpCode)
{
case ocIf:
nJumpMax = 3;
break;
case ocChoose:
nJumpMax = FORMULA_MAXJUMPCOUNT;
break;
case ocIfError:
case ocIfNA:
nJumpMax = 2;
break;
default:
nJumpMax = 0;
SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
assert(!"FormulaCompiler::Factor: someone forgot to add a jump max case");
}
short nJumpCount = 0;
while ( (nJumpCount < (FORMULA_MAXJUMPCOUNT - 1)) && (eOp == ocSep)
&& (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
{
if ( ++nJumpCount <= nJumpMax )
pFacToken->GetJump()[nJumpCount] = pc-1;
NextToken();
CheckSetForceArrayParameter( mpToken, nJumpCount - 1);
eOp = Expression();
// ocSep or ocClose terminate the subexpression
PutCode( mpToken );
}
if (eOp != ocClose)
SetError( FormulaError::PairExpected);
else
{
NextToken();
// always limit to nJumpMax, no arbitrary overwrites
if ( ++nJumpCount <= nJumpMax )
pFacToken->GetJump()[ nJumpCount ] = pc-1;
eFacOpCode = pFacToken->GetOpCode();
bool bLimitOk;
switch (eFacOpCode)
{
case ocIf:
bLimitOk = (nJumpCount <= 3);
break;
case ocChoose:
bLimitOk = (nJumpCount < FORMULA_MAXJUMPCOUNT);
break;
case ocIfError:
case ocIfNA:
bLimitOk = (nJumpCount <= 2);
break;
default:
bLimitOk = false;
SAL_WARN("formula.core","Jump OpCode: " << +eFacOpCode);
assert(!"FormulaCompiler::Factor: someone forgot to add a jump limit case");
}
if (bLimitOk)
pFacToken->GetJump()[ 0 ] = nJumpCount;
else
SetError( FormulaError::IllegalParameter);
}
}
else if ( eOp == ocMissing )
{
PutCode( mpToken );
NextToken();
}
else if ( eOp == ocClose )
{
SetError( FormulaError::ParameterExpected );
}
else if ( eOp == ocSep )
{ // Subsequent ocSep
SetError( FormulaError::ParameterExpected );
if ( bAutoCorrect && !pStack )
{
aCorrectedSymbol.clear();
bCorrected = true;
}
}
else if ( mpToken->IsExternalRef() )
{
PutCode( mpToken);
NextToken();
}
else
{
SetError( FormulaError::UnknownToken );
if ( bAutoCorrect && !pStack )
{
if ( eOp == ocStop )
{ // trailing operator w/o operand
sal_Int32 nLen = aCorrectedFormula.getLength();
if ( nLen )
aCorrectedFormula = aCorrectedFormula.copy( 0, nLen - 1 );
aCorrectedSymbol.clear();
bCorrected = true;
}
}
}
}
}
void FormulaCompiler::RangeLine()
{
Factor();
while (mpToken->GetOpCode() == ocRange)
{
FormulaToken** pCode1 = pCode - 1;
FormulaTokenRef p = mpToken;
NextToken();
Factor();
FormulaToken** pCode2 = pCode - 1;
if (!MergeRangeReference( pCode1, pCode2))
PutCode(p);
}
}
void FormulaCompiler::IntersectionLine()
{
RangeLine();
while (mpToken->GetOpCode() == ocIntersect || mpToken->GetOpCode() == ocSpaces)
{
sal_uInt16 nCodeIndex = maArrIterator.GetIndex() - 1;
FormulaToken** pCode1 = pCode - 1;
FormulaTokenRef p = mpToken;
NextToken();
RangeLine();
FormulaToken** pCode2 = pCode - 1;
if (p->GetOpCode() == ocSpaces)
{
// Convert to intersection if both left and right are references or
// functions (potentially returning references, if not then a space
// or no space would be a syntax error anyway), not other operators
// or operands. Else discard.
if (isAdjacentOrGapRpnEnd( pc, pCode, pCode1, pCode2) && isIntersectable( pCode1, pCode2))
{
FormulaTokenRef pIntersect( new FormulaByteToken( ocIntersect));
// Replace ocSpaces with ocIntersect so that when switching
// formula syntax the correct operator string is created.
pArr->ReplaceToken( nCodeIndex, pIntersect.get(), FormulaTokenArray::ReplaceMode::CODE_ONLY);
PutCode( pIntersect);
}
}
else
{
PutCode(p);
}
}
}
void FormulaCompiler::UnionLine()
{
IntersectionLine();
while (mpToken->GetOpCode() == ocUnion)
{
FormulaTokenRef p = mpToken;
NextToken();
IntersectionLine();
PutCode(p);
}
}
void FormulaCompiler::UnaryLine()
{
if( mpToken->GetOpCode() == ocAdd )
GetToken();
else if (SC_OPCODE_START_UN_OP <= mpToken->GetOpCode() &&
mpToken->GetOpCode() < SC_OPCODE_STOP_UN_OP)
{
FormulaTokenRef p = mpToken;
NextToken();
UnaryLine();
if (mbComputeII)
{
FormulaToken** pArg = pCode - 1;
HandleIIOpCode(p.get(), &pArg, 1);
}
PutCode( p );
}
else
UnionLine();
}
void FormulaCompiler::PostOpLine()
{
UnaryLine();
while ( mpToken->GetOpCode() == ocPercentSign )
{ // this operator _follows_ its operand
if (mbComputeII)
{
FormulaToken** pArg = pCode - 1;
HandleIIOpCode(mpToken.get(), &pArg, 1);
}
PutCode( mpToken );
NextToken();
}
}
void FormulaCompiler::PowLine()
{
PostOpLine();
while (mpToken->GetOpCode() == ocPow)
{
FormulaTokenRef p = mpToken;
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
PostOpLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
}
void FormulaCompiler::MulDivLine()
{
PowLine();
while (mpToken->GetOpCode() == ocMul || mpToken->GetOpCode() == ocDiv)
{
FormulaTokenRef p = mpToken;
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
PowLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
}
void FormulaCompiler::AddSubLine()
{
MulDivLine();
while (mpToken->GetOpCode() == ocAdd || mpToken->GetOpCode() == ocSub)
{
FormulaTokenRef p = mpToken;
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
MulDivLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
}
void FormulaCompiler::ConcatLine()
{
AddSubLine();
while (mpToken->GetOpCode() == ocAmpersand)
{
FormulaTokenRef p = mpToken;
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
AddSubLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
}
void FormulaCompiler::CompareLine()
{
ConcatLine();
while (mpToken->GetOpCode() >= ocEqual && mpToken->GetOpCode() <= ocGreaterEqual)
{
FormulaTokenRef p = mpToken;
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
ConcatLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
}
OpCode FormulaCompiler::Expression()
{
static const short nRecursionMax = 42;
FormulaCompilerRecursionGuard aRecursionGuard( nRecursion );
if ( nRecursion > nRecursionMax )
{
SetError( FormulaError::StackOverflow );
return ocStop; //! generate token instead?
}
CompareLine();
while (mpToken->GetOpCode() == ocAnd || mpToken->GetOpCode() == ocOr)
{
FormulaTokenRef p = mpToken;
mpToken->SetByte( 2 ); // 2 parameters!
FormulaToken** pArgArray[2];
if (mbComputeII)
pArgArray[0] = pCode - 1; // Add first argument
NextToken();
CompareLine();
if (mbComputeII)
{
pArgArray[1] = pCode - 1; // Add second argument
HandleIIOpCode(p.get(), pArgArray, 2);
}
PutCode(p);
}
return mpToken->GetOpCode();
}
void FormulaCompiler::SetError( FormulaError /*nError*/ )
{
}
FormulaTokenRef FormulaCompiler::ExtendRangeReference( FormulaToken & /*rTok1*/, FormulaToken & /*rTok2*/ )
{
return FormulaTokenRef();
}
bool FormulaCompiler::MergeRangeReference( FormulaToken * * const pCode1, FormulaToken * const * const pCode2 )
{
if (!isAdjacentRpnEnd( pc, pCode, pCode1, pCode2))
return false;
FormulaToken *p1 = *pCode1, *p2 = *pCode2;
FormulaTokenRef p = ExtendRangeReference( *p1, *p2);
if (!p)
return false;
p->IncRef();
p1->DecRef();
p2->DecRef();
*pCode1 = p.get();
--pCode;
--pc;
return true;
}
bool FormulaCompiler::CompileTokenArray()
{
glSubTotal = false;
bCorrected = false;
needsRPNTokenCheck = false;
if (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError)
{
if ( bAutoCorrect )
{
aCorrectedFormula.clear();
aCorrectedSymbol.clear();
}
pArr->DelRPN();
maArrIterator.Reset();
pStack = nullptr;
FormulaToken* pDataArray[ FORMULA_MAXTOKENS + 1 ];
// Code in some places refers to the last token as 'pCode - 1', which may
// point before the first element if the expression is bad. So insert a dummy
// node in that place which will make that token be nullptr.
pDataArray[ 0 ] = nullptr;
FormulaToken** pData = pDataArray + 1;
pCode = pData;
bool bWasForced = pArr->IsRecalcModeForced();
if ( bWasForced )
{
if ( bAutoCorrect )
aCorrectedFormula = "=";
}
pArr->ClearRecalcMode();
maArrIterator.Reset();
eLastOp = ocOpen;
pc = 0;
NextToken();
OpCode eOp = Expression();
// Some trailing garbage that doesn't form an expression?
if (eOp != ocStop)
SetError( FormulaError::OperatorExpected);
PostProcessCode();
FormulaError nErrorBeforePop = pArr->GetCodeError();
while( pStack )
PopTokenArray();
if( pc )
{
pArr->CreateNewRPNArrayFromData( pData, pc );
if( needsRPNTokenCheck )
pArr->CheckAllRPNTokens();
}
// once an error, always an error
if( pArr->GetCodeError() == FormulaError::NONE && nErrorBeforePop != FormulaError::NONE )
pArr->SetCodeError( nErrorBeforePop);
if (pArr->GetCodeError() != FormulaError::NONE && mbStopOnError)
{
pArr->DelRPN();
maArrIterator.Reset();
pArr->SetHyperLink( false);
}
if ( bWasForced )
pArr->SetRecalcModeForced();
}
if( nNumFmt == SvNumFormatType::UNDEFINED )
nNumFmt = SvNumFormatType::NUMBER;
return glSubTotal;
}
void FormulaCompiler::PopTokenArray()
{
if( pStack )
{
FormulaArrayStack* p = pStack;
pStack = p->pNext;
// obtain special RecalcMode from SharedFormula
if ( pArr->IsRecalcModeAlways() )
p->pArr->SetExclusiveRecalcModeAlways();
else if ( !pArr->IsRecalcModeNormal() && p->pArr->IsRecalcModeNormal() )
p->pArr->SetMaskedRecalcMode( pArr->GetRecalcMode() );
p->pArr->SetCombinedBitsRecalcMode( pArr->GetRecalcMode() );
if ( pArr->IsHyperLink() ) // fdo 87534
p->pArr->SetHyperLink( true );
if( p->bTemp )
delete pArr;
pArr = p->pArr;
maArrIterator = FormulaTokenArrayPlainIterator(*pArr);
maArrIterator.Jump(p->nIndex);
mpLastToken = p->mpLastToken;
delete p;
}
}
void FormulaCompiler::CreateStringFromTokenArray( OUString& rFormula )
{
OUStringBuffer aBuffer( pArr->GetLen() * 5 );
CreateStringFromTokenArray( aBuffer );
rFormula = aBuffer.makeStringAndClear();
}
void FormulaCompiler::CreateStringFromTokenArray( OUStringBuffer& rBuffer )
{
rBuffer.setLength(0);
if( !pArr->GetLen() )
return;
FormulaTokenArray* pSaveArr = pArr;
int nSaveIndex = maArrIterator.GetIndex();
bool bODFF = FormulaGrammar::isODFF( meGrammar);
if (bODFF || FormulaGrammar::isPODF( meGrammar) )
{
// Scan token array for missing args and re-write if present.
MissingConventionODF aConv( bODFF);
if (pArr->NeedsPodfRewrite( aConv))
{
pArr = pArr->RewriteMissing( aConv );
maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
}
}
else if ( FormulaGrammar::isOOXML( meGrammar ) )
{
// Scan token array for missing args and rewrite if present.
MissingConventionOOXML aConv;
if (pArr->NeedsOoxmlRewrite())
{
pArr = pArr->RewriteMissing( aConv );
maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
}
}
// At least one character per token, plus some are references, some are
// function names, some are numbers, ...
rBuffer.ensureCapacity( pArr->GetLen() * 5 );
if ( pArr->IsRecalcModeForced() )
rBuffer.append( '=');
const FormulaToken* t = maArrIterator.First();
while( t )
t = CreateStringFromToken( rBuffer, t, true );
if (pSaveArr != pArr)
{
delete pArr;
pArr = pSaveArr;
maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
maArrIterator.Jump(nSaveIndex);
}
}
const FormulaToken* FormulaCompiler::CreateStringFromToken( OUString& rFormula, const FormulaToken* pTokenP )
{
OUStringBuffer aBuffer;
const FormulaToken* p = CreateStringFromToken( aBuffer, pTokenP );
rFormula += aBuffer;
return p;
}
const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuffer, const FormulaToken* pTokenP,
bool bAllowArrAdvance )
{
bool bNext = true;
bool bSpaces = false;
const FormulaToken* t = pTokenP;
OpCode eOp = t->GetOpCode();
if( eOp >= ocAnd && eOp <= ocOr )
{
// AND, OR infix?
if ( bAllowArrAdvance )
t = maArrIterator.Next();
else
t = maArrIterator.PeekNext();
bNext = false;
bSpaces = ( !t || t->GetOpCode() != ocOpen );
}
if( bSpaces )
rBuffer.append( ' ');
if( eOp == ocSpaces )
{
bool bWriteSpaces = true;
if (mxSymbols->isODFF())
{
const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
bool bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
if (bIntersectionOp)
{
p = maArrIterator.PeekNextNoSpaces();
bIntersectionOp = (p && p->GetOpCode() == ocColRowName);
}
if (bIntersectionOp)
{
rBuffer.append( "!!");
bWriteSpaces = false;
}
}
else if (mxSymbols->isOOXML())
{
// ECMA-376-1:2016 18.17.2 Syntax states "that no space characters
// shall separate a function-name from the left parenthesis (()
// that follows it." and Excel even chokes on it.
const FormulaToken* p = maArrIterator.PeekPrevNoSpaces();
if (p && p->IsFunction())
{
p = maArrIterator.PeekNextNoSpaces();
if (p && p->GetOpCode() == ocOpen)
bWriteSpaces = false;
}
}
if (bWriteSpaces)
{
// most times it's just one blank
sal_uInt8 n = t->GetByte();
for ( sal_uInt8 j=0; j<n; ++j )
{
rBuffer.append( ' ');
}
}
}
else if( eOp >= ocInternalBegin && eOp <= ocInternalEnd )
rBuffer.appendAscii( pInternal[ eOp - ocInternalBegin ] );
else if (eOp == ocIntersect)
{
// Nasty, ugly, horrific, terrifying..
if (FormulaGrammar::isExcelSyntax( meGrammar))
rBuffer.append(' ');
else
rBuffer.append( mxSymbols->getSymbol( eOp));
}
else if( static_cast<sal_uInt16>(eOp) < mxSymbols->getSymbolCount()) // Keyword:
rBuffer.append( mxSymbols->getSymbol( eOp));
else
{
SAL_WARN( "formula.core","unknown OpCode");
rBuffer.append( GetNativeSymbol( ocErrName ));
}
if( bNext )
{
if (t->IsExternalRef())
{
CreateStringFromExternal( rBuffer, pTokenP);
}
else
{
switch( t->GetType() )
{
case svDouble:
AppendDouble( rBuffer, t->GetDouble() );
break;
case svString:
if( eOp == ocBad || eOp == ocStringXML )
rBuffer.append( t->GetString().getString());
else
AppendString( rBuffer, t->GetString().getString() );
break;
case svSingleRef:
CreateStringFromSingleRef( rBuffer, t);
break;
case svDoubleRef:
CreateStringFromDoubleRef( rBuffer, t);
break;
case svMatrix:
case svMatrixCell:
CreateStringFromMatrix( rBuffer, t );
break;
case svIndex:
CreateStringFromIndex( rBuffer, t );
if (t->GetOpCode() == ocTableRef && bAllowArrAdvance && NeedsTableRefTransformation())
{
// Suppress all TableRef related tokens, the resulting
// range was written by CreateStringFromIndex().
const FormulaToken* const p = maArrIterator.PeekNext();
if (p && p->GetOpCode() == ocTableRefOpen)
{
int nLevel = 0;
do
{
t = maArrIterator.Next();
if (!t)
break;
// Switch cases correspond with those in
// ScCompiler::HandleTableRef()
switch (t->GetOpCode())
{
case ocTableRefOpen:
++nLevel;
break;
case ocTableRefClose:
--nLevel;
break;
case ocTableRefItemAll:
case ocTableRefItemHeaders:
case ocTableRefItemData:
case ocTableRefItemTotals:
case ocTableRefItemThisRow:
case ocSep:
case ocPush:
case ocRange:
case ocSpaces:
break;
default:
nLevel = 0;
bNext = false;
}
} while (nLevel);
}
}
break;
case svExternal:
{
// mapped or translated name of AddIns
OUString aAddIn( t->GetExternal() );
bool bMapped = mxSymbols->isPODF(); // ODF 1.1 directly uses programmatical name
if (!bMapped && mxSymbols->hasExternals())
{
ExternalHashMap::const_iterator iLook = mxSymbols->getReverseExternalHashMap().find( aAddIn);
if (iLook != mxSymbols->getReverseExternalHashMap().end())
{
aAddIn = (*iLook).second;
bMapped = true;
}
}
if (!bMapped && !mxSymbols->isEnglish())
LocalizeString( aAddIn );
rBuffer.append( aAddIn);
}
break;
case svError:
AppendErrorConstant( rBuffer, t->GetError());
break;
case svByte:
case svJump:
case svFAP:
case svMissing:
case svSep:
break; // Opcodes
default:
SAL_WARN("formula.core", "FormulaCompiler::GetStringFromToken: unknown token type " << t->GetType());
} // of switch
}
}
if( bSpaces )
rBuffer.append( ' ');
if ( bAllowArrAdvance )
{
if( bNext )
t = maArrIterator.Next();
return t;
}
return pTokenP;
}
void FormulaCompiler::AppendDouble( OUStringBuffer& rBuffer, double fVal ) const
{
if ( mxSymbols->isEnglish() )
{
::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
rtl_math_StringFormat_Automatic,
rtl_math_DecimalPlaces_Max, '.', true );
}
else
{
SvtSysLocale aSysLocale;
::rtl::math::doubleToUStringBuffer( rBuffer, fVal,
rtl_math_StringFormat_Automatic,
rtl_math_DecimalPlaces_Max,
aSysLocale.GetLocaleDataPtr()->getNumDecimalSep()[0],
true );
}
}
void FormulaCompiler::AppendBoolean( OUStringBuffer& rBuffer, bool bVal ) const
{
rBuffer.append( mxSymbols->getSymbol( bVal ? ocTrue : ocFalse ) );
}
void FormulaCompiler::AppendString( OUStringBuffer& rBuffer, const OUString & rStr )
{
rBuffer.append( '"');
if ( lcl_UnicodeStrChr( rStr.getStr(), '"' ) == nullptr )
rBuffer.append( rStr );
else
{
OUString aStr = rStr.replaceAll( "\"", "\"\"" );
rBuffer.append(aStr);
}
rBuffer.append( '"');
}
bool FormulaCompiler::NeedsTableRefTransformation() const
{
// Currently only UI representations and OOXML export use Table structured
// references. Not defined in ODFF.
// Unnecessary to explicitly check for ODFF grammar as the ocTableRefOpen
// symbol is not defined there.
return mxSymbols->getSymbol( ocTableRefOpen).isEmpty() || FormulaGrammar::isPODF( meGrammar);
}
void FormulaCompiler::UpdateSeparatorsNative(
const OUString& rSep, const OUString& rArrayColSep, const OUString& rArrayRowSep )
{
NonConstOpCodeMapPtr xSymbolsNative;
lcl_fillNativeSymbols( xSymbolsNative);
xSymbolsNative->putOpCode( rSep, ocSep, nullptr);
xSymbolsNative->putOpCode( rArrayColSep, ocArrayColSep, nullptr);
xSymbolsNative->putOpCode( rArrayRowSep, ocArrayRowSep, nullptr);
}
void FormulaCompiler::ResetNativeSymbols()
{
NonConstOpCodeMapPtr xSymbolsNative;
lcl_fillNativeSymbols( xSymbolsNative, true);
lcl_fillNativeSymbols( xSymbolsNative);
}
void FormulaCompiler::SetNativeSymbols( const OpCodeMapPtr& xMap )
{
NonConstOpCodeMapPtr xSymbolsNative;
lcl_fillNativeSymbols( xSymbolsNative);
xSymbolsNative->copyFrom( *xMap );
}
OpCode FormulaCompiler::NextToken()
{
if( !GetToken() )
return ocStop;
OpCode eOp = mpToken->GetOpCode();
// There must be an operator before a push
if ( (eOp == ocPush || eOp == ocColRowNameAuto) &&
!( (eLastOp == ocOpen) || (eLastOp == ocSep) ||
(SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)) )
SetError( FormulaError::OperatorExpected);
// Operator and Plus => operator
if (eOp == ocAdd && (eLastOp == ocOpen || eLastOp == ocSep ||
(SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
eOp = NextToken();
else
{
// Before an operator there must not be another operator, with the
// exception of AND and OR.
if ( eOp != ocAnd && eOp != ocOr &&
(SC_OPCODE_START_BIN_OP <= eOp && eOp < SC_OPCODE_STOP_BIN_OP )
&& (eLastOp == ocOpen || eLastOp == ocSep ||
(SC_OPCODE_START_BIN_OP <= eLastOp && eLastOp < SC_OPCODE_STOP_UN_OP)))
{
SetError( FormulaError::VariableExpected);
if ( bAutoCorrect && !pStack )
{
if ( eOp == eLastOp || eLastOp == ocOpen )
{ // throw away duplicated operator
aCorrectedSymbol.clear();
bCorrected = true;
}
else
{
sal_Int32 nPos = aCorrectedFormula.getLength();
if ( nPos )
{
nPos--;
sal_Unicode c = aCorrectedFormula[ nPos ];
switch ( eOp )
{ // swap operators
case ocGreater:
if ( c == mxSymbols->getSymbolChar( ocEqual) )
{ // >= instead of =>
aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
OUString( mxSymbols->getSymbolChar(ocGreater) ) );
aCorrectedSymbol = OUString(c);
bCorrected = true;
}
break;
case ocLess:
if ( c == mxSymbols->getSymbolChar( ocEqual) )
{ // <= instead of =<
aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
OUString( mxSymbols->getSymbolChar(ocLess) ) );
aCorrectedSymbol = OUString(c);
bCorrected = true;
}
else if ( c == mxSymbols->getSymbolChar( ocGreater) )
{ // <> instead of ><
aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
OUString( mxSymbols->getSymbolChar(ocLess) ) );
aCorrectedSymbol = OUString(c);
bCorrected = true;
}
break;
case ocMul:
if ( c == mxSymbols->getSymbolChar( ocSub) )
{ // *- instead of -*
aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
OUString( mxSymbols->getSymbolChar(ocMul) ) );
aCorrectedSymbol = OUString(c);
bCorrected = true;
}
break;
case ocDiv:
if ( c == mxSymbols->getSymbolChar( ocSub) )
{ // /- instead of -/
aCorrectedFormula = aCorrectedFormula.replaceAt( nPos, 1,
OUString( mxSymbols->getSymbolChar(ocDiv) ) );
aCorrectedSymbol = OUString(c);
bCorrected = true;
}
break;
default:
; // nothing
}
}
}
}
}
// Nasty, ugly, horrific, terrifying.. significant whitespace..
if (eOp == ocSpaces && FormulaGrammar::isExcelSyntax( meGrammar))
{
// Fake an intersection op as last op for the next round, but at
// least roughly check if it could make sense at all.
FormulaToken* pPrev = maArrIterator.PeekPrevNoSpaces();
if (pPrev && isPotentialRangeType( pPrev, false, false))
{
FormulaToken* pNext = maArrIterator.PeekNextNoSpaces();
if (pNext && isPotentialRangeType( pNext, false, true))
eLastOp = ocIntersect;
else
eLastOp = eOp;
}
else
eLastOp = eOp;
}
else
eLastOp = eOp;
}
return eOp;
}
void FormulaCompiler::PutCode( FormulaTokenRef& p )
{
if( pc >= FORMULA_MAXTOKENS - 1 )
{
if ( pc == FORMULA_MAXTOKENS - 1 )
{
p = new FormulaByteToken( ocStop );
p->IncRef();
*pCode++ = p.get();
++pc;
}
SetError( FormulaError::CodeOverflow);
return;
}
if (pArr->GetCodeError() != FormulaError::NONE && mbJumpCommandReorder)
return;
ForceArrayOperator( p);
p->IncRef();
*pCode++ = p.get();
pc++;
}
bool FormulaCompiler::HandleExternalReference( const FormulaToken& /*_aToken*/)
{
return true;
}
bool FormulaCompiler::HandleRange()
{
return true;
}
bool FormulaCompiler::HandleColRowName()
{
return true;
}
bool FormulaCompiler::HandleDbData()
{
return true;
}
bool FormulaCompiler::HandleTableRef()
{
return true;
}
void FormulaCompiler::CreateStringFromSingleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
void FormulaCompiler::CreateStringFromDoubleRef( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
void FormulaCompiler::CreateStringFromIndex( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
void FormulaCompiler::CreateStringFromMatrix( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
void FormulaCompiler::CreateStringFromExternal( OUStringBuffer& /*rBuffer*/, const FormulaToken* /*pToken*/) const
{
}
void FormulaCompiler::LocalizeString( OUString& /*rName*/ ) const
{
}
formula::ParamClass FormulaCompiler::GetForceArrayParameter( const FormulaToken* /*pToken*/, sal_uInt16 /*nParam*/ ) const
{
return ParamClass::Unknown;
}
void FormulaCompiler::ForceArrayOperator( FormulaTokenRef const & rCurr )
{
if (!pCurrentFactorToken || (pCurrentFactorToken.get() == rCurr.get()))
return;
if (!(rCurr->GetOpCode() != ocPush && (rCurr->GetType() == svByte || rCurr->GetType() == svJump)))
return;
// Inherited parameter class.
formula::ParamClass eType = pCurrentFactorToken->GetInForceArray();
if (eType == formula::ParamClass::ForceArray)
{
rCurr->SetInForceArray( eType);
return;
}
else if (eType == formula::ParamClass::ReferenceOrForceArray)
{
// Inherit further only if the return class of the nested function is
// not Reference. Else flag as suppressed.
if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
rCurr->SetInForceArray( eType);
else
rCurr->SetInForceArray( formula::ParamClass::SuppressedReferenceOrForceArray);
return;
}
if (nCurrentFactorParam > 0)
{
// Actual current parameter's class.
eType = GetForceArrayParameter( pCurrentFactorToken.get(), static_cast<sal_uInt16>(nCurrentFactorParam - 1));
if (eType == ParamClass::ForceArray)
rCurr->SetInForceArray( eType);
else if (eType == ParamClass::ReferenceOrForceArray)
{
if (GetForceArrayParameter( rCurr.get(), SAL_MAX_UINT16) != ParamClass::Reference)
rCurr->SetInForceArray( eType);
else
rCurr->SetInForceArray( formula::ParamClass::SuppressedReferenceOrForceArray);
}
}
}
void FormulaCompiler::CheckSetForceArrayParameter( FormulaTokenRef const & rCurr, sal_uInt8 nParam )
{
if (!pCurrentFactorToken)
return;
nCurrentFactorParam = nParam + 1;
ForceArrayOperator( rCurr);
}
void FormulaCompiler::PushTokenArray( FormulaTokenArray* pa, bool bTemp )
{
if ( bAutoCorrect && !pStack )
{ // don't merge stacked subroutine code into entered formula
aCorrectedFormula += aCorrectedSymbol;
aCorrectedSymbol.clear();
}
FormulaArrayStack* p = new FormulaArrayStack;
p->pNext = pStack;
p->pArr = pArr;
p->nIndex = maArrIterator.GetIndex();
p->mpLastToken = mpLastToken;
p->bTemp = bTemp;
pStack = p;
pArr = pa;
maArrIterator = FormulaTokenArrayPlainIterator( *pArr );
}
} // namespace formula
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression 'aVec.size() < nCount' is always true.
↑ V501 There are identical sub-expressions 'mpTable[ocArrayColSep] != mpTable[eOp]' to the left and to the right of the '&&' operator.
↑ V560 A part of conditional expression is always false: (eOp == ocTableRef).
↑ V560 A part of conditional expression is always false: (eOp == ocDBArea).