diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormula.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormula.cs index 47bd17ceb..04f5b3d04 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormula.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormula.cs @@ -34,6 +34,7 @@ internal enum FormulaFlags : short { IsDynamic = 1, IsAlwaysDynamic = 2, + IsLambda = 4, } internal class RpnFormula { @@ -52,6 +53,9 @@ internal class RpnFormula internal int _arrayIndex = -1; internal FormulaFlags _flags = 0; internal FunctionExpression _currentFunction = null; + // saves the initial formula stack count when executing a lambda expression + // to avoid popping formulas pushed before the lambda expression was invoked. + internal int _lambdaFormulaStackCount = 0; private VariableStorageManager _variableStorage; public bool CanBeDynamicArray @@ -62,6 +66,14 @@ public bool CanBeDynamicArray } } + public bool IsLambda + { + get + { + return (_flags & FormulaFlags.IsLambda) == FormulaFlags.IsLambda; + } + } + public bool IgnoreCaching { get; set; diff --git a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs index 3bc997da6..ccb716a7c 100644 --- a/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs +++ b/src/EPPlus/FormulaParsing/DependencyChain/RpnFormulaExecution.cs @@ -516,7 +516,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d cr = f._expressionStack.Pop().Compile(); } - if (cr != null && (writeToCell || depChain._formulaStack.Count > 0)) // If calculating single cell via the FormulaParser.Parse method we should not write to the cells + if (cr != null && f.IsLambda == false && (writeToCell || depChain._formulaStack.Count > 0)) // If calculating single cell via the FormulaParser.Parse method we should not write to the cells { SetValueToWorkbook(depChain, f, rd, cr, options, ref depChainPos); } @@ -526,7 +526,7 @@ private static CompileResult CalculateFormulaChain(RpnOptimizedDependencyChain d depChain._parsingContext.Parser.Logger.Log($"Set value in Cell\t{f.GetAddress()}\t{cr.ResultValue}\t{cr.DataType}"); } - if (depChain._formulaStack.Count > 0) + if (depChain._formulaStack.Count > f._lambdaFormulaStackCount) { f = depChain._formulaStack.Pop(); if (f._formulaEnumerator == null) diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Hstack.cs b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Hstack.cs index 8d40e5995..a83b743d6 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Hstack.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Hstack.cs @@ -25,28 +25,16 @@ namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup EPPlusVersion = "7", Description = "Combines arrays horizontally into a single array.", SupportsArrays = true)] - internal class Hstack : ExcelFunction + internal class Hstack : StackFunctionBase { - public override string NamespacePrefix => "_xlfn."; - - public override int ArgumentMinLength => 1; public override bool ExecutesLambda => true; public override CompileResult Execute(IList arguments, ParsingContext context) { - var ranges = new List(); - foreach (var arg in arguments) + var ranges = GetRanges(arguments, out ExcelErrorValue err); + if (err != null) { - if (!arg.IsExcelRange) - { - var rng = new InMemoryRange(1, 1); - rng.SetValue(0, 0, arg.Value); - ranges.Add(rng); - } - else - { - ranges.Add(arg.ValueAsRangeInfo); - } + return CreateDynamicArrayResult(err, DataType.ExcelError); } var nRows = ranges.Max(x => x.Size.NumberOfRows); var nCols = Convert.ToInt16(ranges.Sum(x => x.Size.NumberOfCols)); diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/StackFunctionBase.cs b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/StackFunctionBase.cs new file mode 100644 index 000000000..7d7e5b407 --- /dev/null +++ b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/StackFunctionBase.cs @@ -0,0 +1,54 @@ +/************************************************************************************************* + Required Notice: Copyright (C) EPPlus Software AB. + This software is licensed under PolyForm Noncommercial License 1.0.0 + and may only be used for noncommercial purposes + https://polyformproject.org/licenses/noncommercial/1.0.0/ + + A commercial license to use this software can be purchased at https://epplussoftware.com + ************************************************************************************************* + Date Author Change + ************************************************************************************************* + 22/3/2025 EPPlus Software AB EPPlus v8 + *************************************************************************************************/ +using OfficeOpenXml.FormulaParsing.FormulaExpressions; +using OfficeOpenXml.FormulaParsing.Ranges; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup +{ + internal abstract class StackFunctionBase : ExcelFunction + { + public override string NamespacePrefix => "_xlfn."; + + public override int ArgumentMinLength => 1; + + protected List GetRanges(IEnumerable arguments, out ExcelErrorValue err) + { + err = default; + var ranges = new List(); + foreach (var arg in arguments) + { + if (arg.Value is not IRangeInfo) + { + var rng = new InMemoryRange(1, 1); + rng.SetValue(0, 0, arg.Value); + ranges.Add(rng); + } + else + { + var r = arg.ValueAsRangeInfo; + if (r == null) + { + err = ErrorValues.ValueError; + break; + } + ranges.Add(r); + } + } + return ranges; + } + } +} diff --git a/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Vstack.cs b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Vstack.cs index d776eb6c2..8d52e98de 100644 --- a/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Vstack.cs +++ b/src/EPPlus/FormulaParsing/Excel/Functions/RefAndLookup/Vstack.cs @@ -13,10 +13,8 @@ Date Author Change using OfficeOpenXml.FormulaParsing.Excel.Functions.Metadata; using OfficeOpenXml.FormulaParsing.FormulaExpressions; using OfficeOpenXml.FormulaParsing.Ranges; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup { @@ -25,31 +23,14 @@ namespace OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup EPPlusVersion = "7", Description = "Combines arrays vertically into a single array.", SupportsArrays = true)] - internal class Vstack : ExcelFunction + internal class Vstack : StackFunctionBase { - public override string NamespacePrefix => "_xlfn."; - - public override int ArgumentMinLength => 1; public override CompileResult Execute(IList arguments, ParsingContext context) { - var ranges = new List(); - foreach(var arg in arguments) + var ranges = GetRanges(arguments, out ExcelErrorValue err); + if (err != null) { - if(!arg.IsExcelRange) - { - var rng = new InMemoryRange(1, 1); - rng.SetValue(0, 0, arg.Value); - ranges.Add(rng); - } - else - { - var r = arg.ValueAsRangeInfo; - if(r==null) - { - return CreateDynamicArrayResult(ErrorValues.ValueError, DataType.ExcelError); - } - ranges.Add(r); - } + return CreateDynamicArrayResult(err, DataType.ExcelError); } var nRows = ranges.Sum(x => x.Size.NumberOfRows); var nCols = ranges.Max(x => x.Size.NumberOfCols); diff --git a/src/EPPlus/FormulaParsing/FormulaExpressions/FunctionExpression.cs b/src/EPPlus/FormulaParsing/FormulaExpressions/FunctionExpression.cs index 03fb45959..989b41a73 100644 --- a/src/EPPlus/FormulaParsing/FormulaExpressions/FunctionExpression.cs +++ b/src/EPPlus/FormulaParsing/FormulaExpressions/FunctionExpression.cs @@ -229,6 +229,13 @@ internal string GetExpressionKey(RpnFormula f) key.Append(f._tokens[i].Value); } } + else if(e.ExpressionType==ExpressionType.Variable || + e.ExpressionType==ExpressionType.LambdaVariableDeclaration || + e.ExpressionType == ExpressionType.LambdaCalculation || + e.ExpressionType == ExpressionType.LambdaInvoke) + { + return null; + } else { var fa = e.GetAddress(); diff --git a/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaCalculator.cs b/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaCalculator.cs index a9eee9267..214f62a68 100644 --- a/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaCalculator.cs +++ b/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaCalculator.cs @@ -202,12 +202,14 @@ public CompileResult Execute(ParsingContext ctx) formula.IgnoreCaching = true; formula.ExpressionStack = _formula.ExpressionStack; formula.FunctionStack = _formula.FunctionStack; + formula._flags = _formula._flags | FormulaFlags.IsLambda; + formula._lambdaFormulaStackCount = ctx.DependencyChain._formulaStack.Count; var rpnTokens = new RpnTokens { Tokens = _currentTokens, Scope = _scope }; formula.SetTokens(rpnTokens, ctx, _scope); // SetTokens clears the variable storage... ctx.VariableStorage.Push(_scope); var chain = ctx.DependencyChain; - var compileResult = RpnFormulaExecution.ExecutePartialFormula(chain, formula, ctx.CalcOption, false); + var compileResult = RpnFormulaExecution.ExecutePartialFormula(chain, formula, ctx.CalcOption, true); return CompileResultFactory.CreateDynamicArrayResult(compileResult.Result, compileResult.Address, CompileResultType.DynamicArray_AlwaysSetCellAsDynamic); } diff --git a/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaEtaExpression.cs b/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaEtaExpression.cs index 9c15d96b1..6fa3c69df 100644 --- a/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaEtaExpression.cs +++ b/src/EPPlus/FormulaParsing/FormulaExpressions/LambdaEtaExpression.cs @@ -83,8 +83,10 @@ public override CompileResult Compile() ExpressionStack = _rpnFormula.ExpressionStack, FunctionStack = _rpnFormula.FunctionStack }; + rpnFormula._flags = _rpnFormula._flags | FormulaFlags.IsLambda; + rpnFormula._lambdaFormulaStackCount = Context.DependencyChain._formulaStack.Count; rpnFormula.SetTokens(rpnTokens, Context, _scope); - var result = RpnFormulaExecution.ExecutePartialFormula(Context.DependencyChain, rpnFormula, Context.CalcOption, false); + var result = RpnFormulaExecution.ExecutePartialFormula(Context.DependencyChain, rpnFormula, Context.CalcOption, true); if(result.DataType != DataType.LambdaCalculation) { return CompileResult.GetErrorResult(eErrorType.Value);