tdf#137543 - Add new LET function to Calc

Add new LET function to Calc which assigns names to calculation results.

TODO: oasis proposal

Change-Id: Ia0d56a30751a44a72e364a28b64fd8f617e997dc
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168349
Tested-by: Gabor Kelemen <gabor.kelemen.extern@allotropia.de>
Tested-by: Jenkins
Reviewed-by: Balazs Varga <balazs.varga.extern@allotropia.de>
This commit is contained in:
Balazs Varga 2024-05-30 18:43:09 +02:00
parent 200f74b800
commit 521a56d8d1
26 changed files with 4641 additions and 10 deletions

View file

@ -282,6 +282,8 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_ODFF[] =
{ "COM.MICROSOFT.SORT" , SC_OPCODE_SORT },
{ "COM.MICROSOFT.SORTBY" , SC_OPCODE_SORTBY },
{ "COM.MICROSOFT.UNIQUE" , SC_OPCODE_UNIQUE },
{ "COM.MICROSOFT.LET" , SC_OPCODE_LET },
{ "_xlpm." , SC_OPCODE_STRINGNAME },
{ "ORG.OPENOFFICE.MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
{ "INDEX" , SC_OPCODE_INDEX },
@ -737,6 +739,8 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_OOXML[] =
{ "_xlfn._xlws.SORT" , SC_OPCODE_SORT },
{ "_xlfn.SORTBY" , SC_OPCODE_SORTBY },
{ "_xlfn.UNIQUE" , SC_OPCODE_UNIQUE },
{ "_xlfn.LET" , SC_OPCODE_LET },
{ "_xlpm." , SC_OPCODE_STRINGNAME },
{ "_xlfn.ORG.OPENOFFICE.MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
{ "INDEX" , SC_OPCODE_INDEX },
@ -1195,6 +1199,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_PODF[] =
{ "SORT" , SC_OPCODE_SORT },
{ "SORTBY" , SC_OPCODE_SORTBY },
{ "UNIQUE" , SC_OPCODE_UNIQUE },
{ "LET" , SC_OPCODE_LET },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
{ "INDEX" , SC_OPCODE_INDEX },
@ -1654,6 +1659,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH_API[] =
{ "SORT" , SC_OPCODE_SORT },
{ "SORTBY" , SC_OPCODE_SORTBY },
{ "UNIQUE" , SC_OPCODE_UNIQUE },
{ "LET" , SC_OPCODE_LET },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ "OFFSET" , SC_OPCODE_OFFSET },
{ "INDEX" , SC_OPCODE_INDEX }, // ?? first character = I ??
@ -2111,6 +2117,7 @@ const std::pair<const char *, int> RID_STRLIST_FUNCTION_NAMES_ENGLISH[] =
{ "SORT" , SC_OPCODE_SORT },
{ "SORTBY" , SC_OPCODE_SORTBY },
{ "UNIQUE" , SC_OPCODE_UNIQUE },
{ "LET" , SC_OPCODE_LET },
{ "MULTIRANGE" , SC_OPCODE_MULTI_AREA },
{ "OFFSET" , SC_OPCODE_OFFSET },
{ "INDEX" , SC_OPCODE_INDEX },
@ -2549,6 +2556,7 @@ const std::pair<TranslateId, int> RID_STRLIST_FUNCTION_NAMES[] =
{ NC_("RID_STRLIST_FUNCTION_NAMES", "SORT") , SC_OPCODE_SORT },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "SORTBY") , SC_OPCODE_SORTBY },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "UNIQUE") , SC_OPCODE_UNIQUE },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "LET") , SC_OPCODE_LET },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "MULTIRANGE") , SC_OPCODE_MULTI_AREA }, // legacy for range list (union)
{ NC_("RID_STRLIST_FUNCTION_NAMES", "OFFSET") , SC_OPCODE_OFFSET },
{ NC_("RID_STRLIST_FUNCTION_NAMES", "INDEX") , SC_OPCODE_INDEX }, // ?? first character = I ??

View file

@ -626,6 +626,7 @@ uno::Sequence< sheet::FormulaOpCodeMapEntry > FormulaCompiler::OpCodeMap::create
SC_OPCODE_IF_ERROR,
SC_OPCODE_IF_NA,
SC_OPCODE_CHOOSE,
SC_OPCODE_LET,
SC_OPCODE_AND,
SC_OPCODE_OR
};
@ -1221,6 +1222,7 @@ bool FormulaCompiler::IsOpCodeJumpCommand( OpCode eOp )
case ocIfError:
case ocIfNA:
case ocChoose:
case ocLet:
return true;
default:
;
@ -1269,6 +1271,7 @@ bool FormulaCompiler::IsMatrixFunction( OpCode eOpCode )
case ocSortBy :
case ocRandArray :
case ocUnique :
case ocLet :
return true;
default:
{
@ -1565,6 +1568,11 @@ bool FormulaCompiler::GetToken()
case ocAggregate:
glSubTotal = true;
break;
case ocStringName:
if( HandleStringName())
return true;
else
return false;
case ocName:
if( HandleRange())
{
@ -1946,6 +1954,9 @@ void FormulaCompiler::Factor()
case ocChoose:
pFacToken->GetJump()[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
break;
case ocLet:
pFacToken->GetJump()[0] = SAL_MAX_UINT8 + 1;
break;
case ocIfError:
case ocIfNA:
pFacToken->GetJump()[ 0 ] = 2; // if, behind
@ -1978,6 +1989,9 @@ void FormulaCompiler::Factor()
case ocChoose:
nJumpMax = FORMULA_MAXJUMPCOUNT;
break;
case ocLet:
nJumpMax = SAL_MAX_UINT8;
break;
case ocIfError:
case ocIfNA:
nJumpMax = 2;
@ -1993,7 +2007,7 @@ void FormulaCompiler::Factor()
assert(!"FormulaCompiler::Factor: someone forgot to add a jump max case");
}
short nJumpCount = 0;
while ( (nJumpCount < (FORMULA_MAXJUMPCOUNT - 1)) && (eOp == ocSep)
while ( (nJumpCount < (SAL_MAX_UINT8 - 1)) && (eOp == ocSep)
&& (pArr->GetCodeError() == FormulaError::NONE || !mbStopOnError))
{
if ( ++nJumpCount <= nJumpMax )
@ -2022,6 +2036,9 @@ void FormulaCompiler::Factor()
case ocChoose:
bLimitOk = (nJumpCount < FORMULA_MAXJUMPCOUNT);
break;
case ocLet:
bLimitOk = (nJumpCount < SAL_MAX_UINT8);
break;
case ocIfError:
case ocIfNA:
bLimitOk = (nJumpCount <= 2);
@ -2598,7 +2615,7 @@ const FormulaToken* FormulaCompiler::CreateStringFromToken( OUStringBuffer& rBuf
break;
case svString:
if( eOp == ocBad || eOp == ocStringXML )
if( eOp == ocBad || eOp == ocStringXML || eOp == ocStringName )
rBuffer.append( t->GetString().getString());
else
AppendString( rBuffer, t->GetString().getString() );
@ -2920,6 +2937,11 @@ bool FormulaCompiler::HandleExternalReference( const FormulaToken& /*_aToken*/)
return true;
}
bool FormulaCompiler::HandleStringName()
{
return true;
}
bool FormulaCompiler::HandleRange()
{
return true;

View file

@ -435,6 +435,8 @@ bool FormulaTokenArray::AddFormulaToken(
AddBad( aStrVal );
else if ( eOpCode == ocStringXML )
AddStringXML( aStrVal );
else if ( eOpCode == ocStringName )
AddStringName( aStrVal );
else if ( eOpCode == ocExternal || eOpCode == ocMacro )
Add( new formula::FormulaExternalToken( eOpCode, aStrVal ) );
else if ( eOpCode == ocWhitespace )
@ -914,6 +916,10 @@ FormulaToken* FormulaTokenArray::AddStringXML( const OUString& rStr )
return Add( new FormulaStringOpToken( ocStringXML, svl::SharedString( rStr ) ) ); // string not interned
}
FormulaToken* FormulaTokenArray::AddStringName( const OUString& rStr )
{
return Add( new FormulaStringOpToken( ocStringName, svl::SharedString( rStr ) ) ); // string not interned
}
void FormulaTokenArray::AddRecalcMode( ScRecalcMode nBits )
{
@ -1576,12 +1582,15 @@ FormulaToken* FormulaTokenArray::AddOpCode( OpCode eOp )
case ocIfError:
case ocIfNA:
case ocChoose:
case ocLet:
{
short nJump[FORMULA_MAXJUMPCOUNT + 1];
short nJump[SAL_MAX_UINT8 + 1];
if ( eOp == ocIf )
nJump[ 0 ] = 3;
else if ( eOp == ocChoose )
nJump[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
else if ( eOp == ocLet )
nJump[0] = SAL_MAX_UINT8 + 1;
else
nJump[ 0 ] = 2;
pRet = new FormulaJumpToken( eOp, nJump );
@ -1660,6 +1669,20 @@ FormulaToken* FormulaTokenArrayPlainIterator::GetNextName()
return nullptr;
}
FormulaToken* FormulaTokenArrayPlainIterator::GetNextStringName()
{
if (mpFTA->GetArray())
{
while (mnIndex < mpFTA->GetLen())
{
FormulaToken* t = mpFTA->GetArray()[mnIndex++];
if (t->GetType() == svString && t->GetOpCode() == ocStringName)
return t;
}
}
return nullptr;
}
const FormulaToken* FormulaTokenIterator::Next()
{
const FormulaToken* t = GetNonEndOfPathToken( ++maStack.back().nPC );

View file

@ -27,6 +27,7 @@
#include <strings.hrc>
#include <bitmaps.hlst>
#include <core_resource.hxx>
#include <rtl/math.hxx>
namespace formula
{
@ -150,7 +151,10 @@ void ParaWin::UpdateArgDesc( sal_uInt16 nArg )
aArgName = pFuncDesc->getParameterName(nRealArg);
sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
if ( nArg >= nVarArgsStart )
aArgName += OUString::number( (nArg-nVarArgsStart)/2 + 1 );
{
sal_Int16 nShifted = pFuncDesc->getFunctionName().equalsIgnoreAsciiCase(u"LET") ? nPos / 2 : 0;
aArgName += OUString::number( (nArg-nVarArgsStart)/2 + 1 + nShifted );
}
aArgName += " " + ((nArg > (nFix+1) || pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired) ;
}
@ -209,8 +213,9 @@ void ParaWin::UpdateArgInput( sal_uInt16 nOffset, sal_uInt16 i )
sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
if ( nArg >= nVarArgsStart )
{
sal_Int16 nShifted = pFuncDesc->getFunctionName().equalsIgnoreAsciiCase(u"LET") ? nPos / 2 : 0;
OUString aArgName = pFuncDesc->getParameterName(nRealArg) +
OUString::number( (nArg-nVarArgsStart)/2 + 1 );
OUString::number( (nArg-nVarArgsStart)/2 + 1 + nShifted);
SetArgName( i, aArgName );
}
else

View file

@ -344,6 +344,7 @@ protected:
virtual void SetError(FormulaError nError);
virtual FormulaTokenRef ExtendRangeReference( FormulaToken & rTok1, FormulaToken & rTok2 );
virtual bool HandleExternalReference(const FormulaToken& _aToken);
virtual bool HandleStringName();
virtual bool HandleRange();
virtual bool HandleColRowName();
virtual bool HandleDbData();
@ -419,6 +420,12 @@ protected:
bool mbComputeII; // whether to attempt computing implicit intersection ranges while building the RPN array.
bool mbMatrixFlag; // whether the formula is a matrix formula (needed for II computation)
struct LambdaFunc
{
bool bInLambdaFunction = false;
short nBracketPos = 0;
} mLambda;
public:
enum InitSymbols
{

View file

@ -61,6 +61,8 @@
#define SC_OPCODE_TABLE_REF_ITEM_THIS_ROW 35
#define SC_OPCODE_STOP_DIV 36
#define SC_OPCODE_SKIP 37 /* used to skip raw tokens during string compilation */
#define SC_OPCODE_STRINGNAME 38 /* special OpCode for lambda function names */
#define SC_OPCODE_LET 39
/*** error constants #... ***/
#define SC_OPCODE_START_ERRORS 40

View file

@ -38,6 +38,7 @@ enum OpCode : sal_uInt16
ocIfError = SC_OPCODE_IF_ERROR,
ocIfNA = SC_OPCODE_IF_NA,
ocChoose = SC_OPCODE_CHOOSE,
ocLet = SC_OPCODE_LET,
// Parentheses and separators
ocOpen = SC_OPCODE_OPEN,
ocClose = SC_OPCODE_CLOSE,
@ -61,6 +62,7 @@ enum OpCode : sal_uInt16
ocTableRefItemTotals = SC_OPCODE_TABLE_REF_ITEM_TOTALS,
ocTableRefItemThisRow = SC_OPCODE_TABLE_REF_ITEM_THIS_ROW,
ocSkip = SC_OPCODE_SKIP,
ocStringName = SC_OPCODE_STRINGNAME,
// Access commands
ocDBArea = SC_OPCODE_DB_AREA,
ocTableRef = SC_OPCODE_TABLE_REF,
@ -553,6 +555,7 @@ inline std::string OpCodeEnumToString(OpCode eCode)
case ocMissing: return "Missing";
case ocBad: return "Bad";
case ocStringXML: return "StringXML";
case ocStringName: return "StringName";
case ocSpaces: return "Spaces";
case ocWhitespace: return "Whitespace";
case ocMatRef: return "MatRef";
@ -996,6 +999,7 @@ inline std::string OpCodeEnumToString(OpCode eCode)
case ocSort: return "Sort";
case ocSortBy: return "SortBy";
case ocUnique: return "Unique";
case ocLet: return "Let";
case ocTTT: return "TTT";
case ocDebugVar: return "DebugVar";
case ocDataToken1: return "DataToken1";

View file

@ -487,6 +487,7 @@ public:
FormulaToken* AddExternal( const OUString& rStr, OpCode eOp = ocExternal );
FormulaToken* AddBad( const OUString& rStr ); /// ocBad with OUString
FormulaToken* AddStringXML( const OUString& rStr ); /// ocStringXML with OUString, temporary during import
FormulaToken* AddStringName( const OUString& rStr ); /// ocStringName with OUString - Lambda functions
virtual FormulaToken* MergeArray( );
@ -578,6 +579,7 @@ public:
private:
SAL_DLLPRIVATE const FormulaToken* GetNonEndOfPathToken( short nIdx ) const;
SAL_DLLPRIVATE const FormulaToken* GetNonEndOfPathToken2( short nIdx ) const;
};
// For use in SAL_INFO, SAL_WARN etc
@ -639,6 +641,7 @@ public:
FormulaToken* Next();
FormulaToken* NextNoSpaces();
FormulaToken* GetNextName();
FormulaToken* GetNextStringName();
FormulaToken* GetNextReference();
FormulaToken* GetNextReferenceRPN();
FormulaToken* GetNextReferenceOrName();

View file

@ -70,6 +70,8 @@ https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocume
* Information Functions
* COUNTIF
* COUNTIFS
* Lambda Functions
* LET
* Lookup Functions
* HLOOKUP
* LOOKUP

View file

@ -155,6 +155,7 @@ public:
// since the reference count is cleared!
void SetOpCode( OpCode eCode );
void SetString( rtl_uString* pData, rtl_uString* pDataIgnoreCase );
void SetStringName( rtl_uString* pData, rtl_uString* pDataIgnoreCase );
void SetSingleReference( const ScSingleRefData& rRef );
void SetDoubleReference( const ScComplexRefData& rRef );
void SetDouble( double fVal );
@ -358,6 +359,7 @@ private:
bool ParsePredetectedErrRefReference( const OUString& rName, const OUString* pErrRef );
bool ParseMacro( const OUString& );
bool ParseNamedRange( const OUString&, bool onlyCheck = false );
bool ParseLambdaFuncName( const OUString&, bool bLambdaFunction = false );
bool ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange );
bool ParseDBRange( const OUString& );
bool ParseColRowName( const OUString& );
@ -516,6 +518,7 @@ private:
virtual void fillAddInToken(::std::vector< css::sheet::FormulaOpCodeMapEntry >& _rVec,bool _bIsEnglish) const override;
virtual bool HandleExternalReference(const formula::FormulaToken& _aToken) override;
virtual bool HandleStringName() override;
virtual bool HandleRange() override;
virtual bool HandleColRowName() override;
virtual bool HandleDbData() override;

View file

@ -601,5 +601,6 @@ inline constexpr OUString HID_FUNC_FILTER_MS = u"SC_HID_FUNC_FILTER_MS"_ustr;
inline constexpr OUString HID_FUNC_SORT_MS = u"SC_HID_FUNC_SORT_MS"_ustr;
inline constexpr OUString HID_FUNC_SORTBY_MS = u"SC_HID_FUNC_SORTBY_MS"_ustr;
inline constexpr OUString HID_FUNC_UNIQUE_MS = u"SC_HID_FUNC_UNIQUE_MS"_ustr;
inline constexpr OUString HID_FUNC_LET_MS = u"SC_HID_FUNC_LET_MS"_ustr;
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -4263,4 +4263,16 @@ const TranslateId SC_OPCODE_UNIQUE_ARY[] =
NC_("SC_OPCODE_UNIQUE", "Logical value that defines what values are considered unique: TRUE - returns values that occur only once. FALSE or omitted (default) - returns all distinct (different) values in the range or array.")
};
// -=*# Resource for function LET #*=-
const TranslateId SC_OPCODE_LET_ARY[] =
{
NC_("SC_OPCODE_LET", "The LET function assigns names to calculation results. This allows storing intermediate calculations, values, or defining names inside a formula. These names only apply within the scope of the LET function."),
NC_("SC_OPCODE_LET", "Name 1"),
NC_("SC_OPCODE_LET", "The first name to assign. Must start with a letter. Cannot be the output of a formula or conflict with range syntax."),
NC_("SC_OPCODE_LET", "Name value "),
NC_("SC_OPCODE_LET", "Name value 1, Name value 2,... The value or calculation to assign to Name."),
NC_("SC_OPCODE_LET", "Calculation or Name "),
NC_("SC_OPCODE_LET", "A calculation that uses all names within the LET function. This must be the last argument in the LET function. Or a second Name to assign to a second Name value. If a second Name is specified, Name value 2 and Calculation or Name 3 become required.")
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -77,7 +77,7 @@ public:
ScFunctionListObj::ScFunctionListObj()
: UnoApiTest(u"/sc/qa/extras/testdocuments"_ustr)
, XElementAccess(cppu::UnoType<uno::Sequence<beans::PropertyValue>>::get())
, XIndexAccess(403)
, XIndexAccess(404)
, XNameAccess("IF")
, XServiceInfo("stardiv.StarCalc.ScFunctionListObj", "com.sun.star.sheet.FunctionDescriptions")
{

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -1311,6 +1311,20 @@ CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf91332)
CPPUNIT_ASSERT_EQUAL(Color(0x90cf47), nColor);
}
CPPUNIT_TEST_FIXTURE(ScExportTest2, testTdf137543XLSX)
{
// LET function test
createScDoc("xlsx/tdf137543.xlsx");
save(u"Calc Office Open XML"_ustr);
xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr);
CPPUNIT_ASSERT(pSheet);
assertXPathContent(
pSheet, "/x:worksheet/x:sheetData/x:row/x:c/x:f"_ostr,
u"_xlfn.LET(_xlpm.first,15,_xlpm.second,10,SUM(_xlpm.first,_xlpm.second))"_ustr);
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View file

@ -2917,6 +2917,7 @@ CPPUNIT_TEST_FIXTURE(Test, testFunctionLists)
"HYPERLINK",
"INDEX",
"INDIRECT",
"LET",
"LOOKUP",
"MATCH",
"OFFSET",

View file

@ -197,12 +197,12 @@ OUString ScFuncDesc::GetParamList() const
aSig.append(maDefArgNames[nVarArgsStart]
+ "1" + sep
+ maDefArgNames[nVarArgsStart+1]
+ "1" + sep
+ (mxFuncName != "LET" ? "1" : "2") + sep
+ " "
+ maDefArgNames[nVarArgsStart]
+ "2" + sep
+ maDefArgNames[nVarArgsStart+1]
+ "2" + sep + " ... " );
+ (mxFuncName != "LET" ? "2" : "3") + sep + " ... " );
}
}
@ -394,6 +394,7 @@ ScFunctionList::ScFunctionList( bool bEnglishFunctionNames )
{ SC_OPCODE_IF_ERROR, ENTRY(SC_OPCODE_IF_ERROR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFERROR, 2, { 0, 0 }, 0 },
{ SC_OPCODE_IF_NA, ENTRY(SC_OPCODE_IF_NA_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_IFNA, 2, { 0, 0 }, 0 },
{ SC_OPCODE_CHOOSE, ENTRY(SC_OPCODE_CHOOSE_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_WAHL, VAR_ARGS+1, { 0, 0 }, 31 },
{ SC_OPCODE_LET, ENTRY(SC_OPCODE_LET_ARY), 0, ID_FUNCTION_GRP_TABLE, HID_FUNC_LET_MS, PAIRED_VAR_ARGS + 1, { 0, 0, 0 }, 0 },
{ SC_OPCODE_AND, ENTRY(SC_OPCODE_AND_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_UND, VAR_ARGS, { 0 }, 0 },
{ SC_OPCODE_OR, ENTRY(SC_OPCODE_OR_ARY), 0, ID_FUNCTION_GRP_LOGIC, HID_FUNC_ODER, VAR_ARGS, { 0 }, 0 },
{ SC_OPCODE_PI, ENTRY(SC_OPCODE_PI_ARY), 0, ID_FUNCTION_GRP_MATH, HID_FUNC_PI, 0, { }, 0 },

View file

@ -190,6 +190,7 @@ struct FormulaTokenRef_hash
{ return std::hash<const void *>()(static_cast<const void*>(p1)); }
};
typedef ::std::unordered_map< const formula::FormulaConstTokenRef, formula::FormulaConstTokenRef, FormulaTokenRef_hash> ScTokenMatrixMap;
typedef ::std::unordered_map< OUString, const formula::FormulaConstTokenRef> ScResultTokenMap;
class ScInterpreter
{
@ -246,6 +247,7 @@ private:
formula::FormulaConstTokenRef xResult;
ScJumpMatrix* pJumpMatrix; // currently active array condition, if any
ScTokenMatrixMap maTokenMatrixMap; // map FormulaToken* to formula::FormulaTokenRef if in array condition
ScResultTokenMap maResultTokenMap; // Result FormulaToken* to formula::FormulaTokenRef
ScFormulaCell* pMyFormulaCell; // the cell of this formula expression
const formula::FormulaToken* pCur; // current token
@ -513,6 +515,11 @@ private:
ScMatrixRef GetMatrix( short & rParam, size_t & rInRefList );
sc::RangeMatrix GetRangeMatrix();
// Get tokens at specific parameters for LET (lambda) function
void getTokensAtParameter( std::unique_ptr<ScTokenArray>& pTokens, short nPos );
static void replaceNamesToResult( const std::unordered_map<OUString, formula::FormulaToken*> nResultIndexes,
std::unique_ptr<ScTokenArray>& pTokens );
void ScTableOp(); // repeated operations
// common helper functions
@ -717,6 +724,7 @@ private:
void ScSort();
void ScSortBy();
void ScUnique();
void ScLet();
void ScSubTotal();
// If upon call rMissingField==true then the database field parameter may be

View file

@ -74,6 +74,8 @@
#include <scmatrix.hxx>
#include <tokenstringcontext.hxx>
#include <officecfg/Office/Common.hxx>
#include <sfx2/linkmgr.hxx>
#include <interpre.hxx>
using namespace formula;
using namespace ::com::sun::star;
@ -3651,6 +3653,20 @@ bool ScCompiler::ParseNamedRange( const OUString& rUpperName, bool onlyCheck )
return false;
}
bool ScCompiler::ParseLambdaFuncName( const OUString& aOrg, bool bLambdaFunction )
{
if (bLambdaFunction && !aOrg.isEmpty())
{
OUString aName = aOrg;
if (aOrg.startsWithIgnoreAsciiCase(u"_xlpm."))
aName = aName.copy(6);
svl::SharedString aSS = rDoc.GetSharedStringPool().intern(aName);
maRawToken.SetStringName(aSS.getData(), aSS.getDataIgnoreCase());
return true;
}
return false;
}
bool ScCompiler::ParseExternalNamedRange( const OUString& rSymbol, bool& rbInvalidExternalNameRange )
{
/* FIXME: This code currently (2008-12-02T15:41+0100 in CWS mooxlsc)
@ -4613,6 +4629,9 @@ Label_Rewind:
if (bMayBeFuncName && ParseOpCode2( aUpper ))
return true;
if (ParseLambdaFuncName(aOrg, mLambda.bInLambdaFunction))
return true;
} while (mbRewind);
// Last chance: it could be a broken invalidated reference that contains
@ -4743,6 +4762,12 @@ std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormul
{
case ocOpen:
{
if (eLastOp == ocLet)
{
mLambda.bInLambdaFunction = true;
mLambda.nBracketPos = nBrackets;
}
++nBrackets;
if (bUseFunctionStack)
{
@ -4765,7 +4790,14 @@ std::unique_ptr<ScTokenArray> ScCompiler::CompileString( const OUString& rFormul
}
}
else
{
nBrackets--;
if (mLambda.bInLambdaFunction && mLambda.nBracketPos == nBrackets)
{
mLambda.bInLambdaFunction = false;
mLambda.nBracketPos = nBrackets;
}
}
if (bUseFunctionStack && nFunction)
--nFunction;
}
@ -5033,6 +5065,14 @@ ScRangeData* ScCompiler::GetRangeData( const FormulaToken& rToken ) const
return rDoc.FindRangeNameBySheetAndIndex( rToken.GetSheet(), rToken.GetIndex());
}
bool ScCompiler::HandleStringName()
{
ScTokenArray* pNew = new ScTokenArray(rDoc);
pNew->AddString(mpToken->GetString());
PushTokenArray(pNew, true);
return GetToken();
}
bool ScCompiler::HandleRange()
{
ScTokenArray* pNew;

View file

@ -48,6 +48,7 @@
#include <document.hxx>
#include <dociter.hxx>
#include <docsh.hxx>
#include <sfx2/linkmgr.hxx>
#include <formulacell.hxx>
#include <scmatrix.hxx>
#include <docoptio.hxx>
@ -8917,6 +8918,152 @@ void ScInterpreter::ScUnique()
}
}
void ScInterpreter::getTokensAtParameter( std::unique_ptr<ScTokenArray>& pTokens, short nPos )
{
sal_uInt16 nOpen = 0;
sal_uInt16 nSepCount = 0;
formula::FormulaTokenArrayPlainIterator aIter(*pArr);
formula::FormulaToken* t = aIter.First();
for (t = aIter.NextNoSpaces(); t; t = aIter.NextNoSpaces())
{
OpCode aOpCode = t->GetOpCode();
formula::StackVar aIntType = t->GetType();
if ((aOpCode == ocOpen || aOpCode == ocArrayOpen || aOpCode == ocTableRefOpen) && aIntType == formula::StackVar::svSep)
nOpen++;
else if ((aOpCode == ocClose || aOpCode == ocArrayClose || aOpCode == ocTableRefClose) && aIntType == formula::StackVar::svSep)
nOpen--;
else if (aOpCode == ocSep && aIntType == formula::StackVar::svSep && nOpen == 1)
{
nSepCount++;
continue;
}
if (nSepCount == nPos && nOpen > 0)
{
pTokens->AddToken(*t->Clone());
}
}
}
void ScInterpreter::replaceNamesToResult( const std::unordered_map<OUString, formula::FormulaToken*> nResultIndexes,
std::unique_ptr<ScTokenArray>& pTokens )
{
formula::FormulaTokenArrayPlainIterator aIterResult(*pTokens);
for (FormulaToken* t = aIterResult.GetNextStringName(); t; t = aIterResult.GetNextStringName())
{
auto iRes = nResultIndexes.find(t->GetString().getString());
if (iRes != nResultIndexes.end())
pTokens->ReplaceToken(aIterResult.GetIndex() - 1, iRes->second->Clone(),
FormulaTokenArray::ReplaceMode::CODE_ONLY);
}
}
void ScInterpreter::ScLet()
{
const short* pJump = pCur->GetJump();
short nJumpCount = pJump[0];
short nOrgJumpCount = nJumpCount;
if (nJumpCount < 3 || (nJumpCount % 2 != 1))
{
PushError(FormulaError::ParameterExpected);
aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
return;
}
OUString aStrName;
std::unordered_map<OUString, formula::FormulaToken*> nResultIndexes;
formula::FormulaTokenArrayPlainIterator aIter(*pArr);
unique_ptr<ScTokenArray> pValueTokens(new ScTokenArray(mrDoc));
// name and function pairs parameter
while (nJumpCount > 1)
{
if (nJumpCount == nOrgJumpCount)
{
aStrName = GetString().getString();
}
else if ((nOrgJumpCount - nJumpCount + 1) % 2 == 1)
{
aIter.Jump(pJump[static_cast<short>(nOrgJumpCount - nJumpCount + 1)] - 1);
FormulaToken* t = aIter.NextRPN();
aStrName = t->GetString().getString();
}
else
{
PushError(FormulaError::ParameterExpected);
aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
return;
}
nJumpCount--;
// get value tokens
getTokensAtParameter(pValueTokens, nOrgJumpCount - nJumpCount);
nJumpCount--;
// replace names with result tokens
replaceNamesToResult(nResultIndexes, pValueTokens);
// calculate the inner results
ScCompiler aComp(mrDoc, aPos, *pValueTokens, mrDoc.GetGrammar(), false, false, &mrContext);
aComp.CompileTokenArray();
ScInterpreter aInt(mrDoc.GetFormulaCell(aPos), mrDoc, mrContext, aPos, *pValueTokens);
sfx2::LinkManager aNewLinkMgr(mrDoc.GetDocumentShell());
aInt.SetLinkManager(&aNewLinkMgr);
formula::StackVar aIntType = aInt.Interpret();
if (aIntType == formula::svMatrixCell)
{
ScConstMatrixRef xMat(aInt.GetResultToken()->GetMatrix());
if (!nResultIndexes.insert(std::make_pair(aStrName, new ScMatrixToken(xMat->Clone()))).second)
PushIllegalParameter();
}
else
{
FormulaConstTokenRef xTok(aInt.GetResultToken());
if (!nResultIndexes.insert(std::make_pair(aStrName, xTok->Clone())).second)
PushIllegalParameter();
}
pValueTokens->Clear();
}
// last parameter: calculation
getTokensAtParameter(pValueTokens, nOrgJumpCount - nJumpCount);
nJumpCount--;
// replace names with result tokens
replaceNamesToResult(nResultIndexes, pValueTokens);
// calculate the final result
ScCompiler aComp(mrDoc, aPos, *pValueTokens, mrDoc.GetGrammar(), false, false, &mrContext);
aComp.CompileTokenArray();
ScInterpreter aInt(mrDoc.GetFormulaCell(aPos), mrDoc, mrContext, aPos, *pValueTokens);
sfx2::LinkManager aNewLinkMgr(mrDoc.GetDocumentShell());
aInt.SetLinkManager(&aNewLinkMgr);
formula::StackVar aIntType = aInt.Interpret();
if (aIntType == formula::svMatrixCell)
{
ScConstMatrixRef xMat(aInt.GetResultToken()->GetMatrix());
PushTokenRef(new ScMatrixToken(xMat->Clone()));
}
else
{
formula::FormulaConstTokenRef xLambdaResult(aInt.GetResultToken());
if (xLambdaResult)
{
nGlobalError = xLambdaResult->GetError();
if (nGlobalError == FormulaError::NONE)
PushTokenRef(xLambdaResult);
else
PushError(nGlobalError);
}
}
pValueTokens.reset();
aCode.Jump(pJump[nOrgJumpCount], pJump[nOrgJumpCount]);
}
void ScInterpreter::ScSubTotal()
{
sal_uInt8 nParamCount = GetByte();

View file

@ -4162,6 +4162,7 @@ StackVar ScInterpreter::Interpret()
case ocSort : ScSort(); break;
case ocSortBy : ScSortBy(); break;
case ocUnique : ScUnique(); break;
case ocLet : ScLet(); break;
case ocTrue : ScTrue(); break;
case ocFalse : ScFalse(); break;
case ocGetActDate : ScGetActDate(); break;

View file

@ -57,6 +57,7 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] =
{ ocIfError, {{ Array, Reference }, 0, Value }},
{ ocIfNA, {{ Array, Reference }, 0, Value }},
{ ocChoose, {{ Array, Reference }, 1, Value }},
{ ocLet, {{ Value, ReferenceOrRefArray, ReferenceOrRefArray, }, 2, ForceArrayReturn } },
// Other specials.
{ ocArrayClose, {{ Bounds }, 0, Bounds }},
{ ocArrayColSep, {{ Bounds }, 0, Bounds }},
@ -76,6 +77,7 @@ const ScParameterClassification::RawData ScParameterClassification::pRawData[] =
{ ocSpaces, {{ Bounds }, 0, Bounds }},
{ ocStop, {{ Bounds }, 0, Bounds }},
{ ocStringXML, {{ Bounds }, 0, Bounds }},
{ ocStringName, {{ Bounds }, 0, Bounds }},
{ ocTableRef, {{ Bounds }, 0, Value }}, // or Reference?
{ ocTableRefClose, {{ Bounds }, 0, Bounds }},
{ ocTableRefItemAll, {{ Bounds }, 0, Bounds }},
@ -574,6 +576,7 @@ void ScParameterClassification::GenerateDocumentation()
case ocIfError:
case ocIfNA:
case ocChoose:
case ocLet:
aToken.SetByte(2);
break;
case ocPercentSign:

View file

@ -221,6 +221,10 @@ void ScRawToken::SetOpCode( OpCode e )
eType = svJump;
nJump[ 0 ] = FORMULA_MAXJUMPCOUNT + 1;
break;
case ocLet:
eType = svJump;
nJump[0] = SAL_MAX_UINT8;
break;
case ocMissing:
eType = svMissing;
break;
@ -256,6 +260,15 @@ void ScRawToken::SetString( rtl_uString* pData, rtl_uString* pDataIgnoreCase )
sharedstring.mpDataIgnoreCase = pDataIgnoreCase;
}
void ScRawToken::SetStringName( rtl_uString* pData, rtl_uString* pDataIgnoreCase )
{
eOp = ocStringName;
eType = svString;
sharedstring.mpData = pData;
sharedstring.mpDataIgnoreCase = pDataIgnoreCase;
}
void ScRawToken::SetSingleReference( const ScSingleRefData& rRef )
{
eOp = ocPush;
@ -1677,6 +1690,7 @@ void ScTokenArray::CheckToken( const FormulaToken& r )
case ocIfError:
case ocIfNA:
case ocChoose:
case ocLet:
// Jump commands are now supported.
break;
}
@ -5014,7 +5028,7 @@ void appendTokenByType( ScSheetLimits& rLimits, sc::TokenStringContext& rCxt, OU
case svString:
{
OUString aStr = rToken.GetString().getString();
if (eOp == ocBad || eOp == ocStringXML)
if (eOp == ocBad || eOp == ocStringXML || eOp == ocStringName)
{
rBuf.append(aStr);
return;

View file

@ -608,7 +608,8 @@ const XclFunctionInfo saFuncTable_2021[] =
EXC_FUNCENTRY_V_VR( ocSortBy, 2, 3, 0, "SORTBY" ),
EXC_FUNCENTRY_V_VR( ocMatSequence,1, 4, 0, "SEQUENCE" ),
EXC_FUNCENTRY_V_VR( ocRandArray, 0, 5, 0, "RANDARRAY" ),
EXC_FUNCENTRY_V_VR( ocUnique, 1, 3, 0, "UNIQUE" )
EXC_FUNCENTRY_V_VR( ocUnique, 1, 3, 0, "UNIQUE" ),
EXC_FUNCENTRY_V_VR( ocLet, 3, 3, 0, "SORTBY")
};

View file

@ -883,6 +883,7 @@ const FunctionData saFuncTable2021[] =
{ "COM.MICROSOFT.SEQUENCE", "SEQUENCE", NOID, NOID, 1, 4, A, { VO }, FuncFlags::MACROCALL_NEW },
{ "COM.MICROSOFT.RANDARRAY", "RANDARRAY", NOID, NOID, 0, 5, A, { VO }, FuncFlags::MACROCALL_NEW },
{ "COM.MICROSOFT.UNIQUE", "UNIQUE", NOID, NOID, 1, 3, A, { VO }, FuncFlags::MACROCALL_NEW },
{ "COM.MICROSOFT.LET", "LET", NOID, NOID, 3, MX, R, { VR, VR, VA }, FuncFlags::MACROCALL_NEW | FuncFlags::PARAMPAIRS },
};