From 1cffceff9d419636b2af0225ff60da83105a5ea4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Aug 2026 18:27:49 +0000 Subject: [PATCH] SF: treat non-finite literals as numeric when executing SF formulas - whenever a string is handled by the executor it is checked if it is a non-finite literal, then it returns it as non-finite number instead - add docu - add tests - add test that empty text waves are not converted to empty numeric waves due to the new guard introduced in SFE_ConvertNonFiniteElements --- Packages/MIES/MIES_SweepFormula_Executor.ipf | 15 +- Packages/doc/SweepFormula.rst | 1 + Packages/tests/Basic/UTF_SweepFormula.ipf | 140 +++++++++++++++++-- 3 files changed, 141 insertions(+), 15 deletions(-) diff --git a/Packages/MIES/MIES_SweepFormula_Executor.ipf b/Packages/MIES/MIES_SweepFormula_Executor.ipf index f100ed5859..0938e01fde 100644 --- a/Packages/MIES/MIES_SweepFormula_Executor.ipf +++ b/Packages/MIES/MIES_SweepFormula_Executor.ipf @@ -696,10 +696,23 @@ static Function/WAVE SFE_FormulaExecutorStringOrVariable(STRUCT SF_ExecutionData endif Make/FREE/T outT = {str} + WAVE/Z converted = SFE_ConvertNonFiniteElements(outT) + if(WaveExists(converted)) + return SFH_GetOutputForExecutorSingle(converted, exd.graph, "ExecutorNumberReturn") + endif + return SFH_GetOutputForExecutorSingle(outT, exd.graph, "ExecutorStringReturn") End -static Function/WAVE SFE_ConvertNonFiniteElements(WAVE/T subArray) +/// @brief Convert a text wave that consists solely of the non-finite literals inf/-inf/NaN/-NaN into a numeric wave +/// +/// @return numeric wave with the converted values or a null wave reference if the input is empty or at least one +/// element is not a non-finite literal +Function/WAVE SFE_ConvertNonFiniteElements(WAVE/T subArray) + + if(numpnts(subArray) == 0) + return $"" + endif Make/FREE/D/N=(DimSize(subArray, ROWS), DimSize(subArray, COLS), DimSize(subArray, LAYERS), DimSize(subArray, CHUNKS)) convert MultiThread convert[][][][] = SFE_ConvertNonFiniteElementsImpl(subArray[p][q][r][s]) diff --git a/Packages/doc/SweepFormula.rst b/Packages/doc/SweepFormula.rst index ef4f58f5d5..c327364c36 100644 --- a/Packages/doc/SweepFormula.rst +++ b/Packages/doc/SweepFormula.rst @@ -198,6 +198,7 @@ The array evaluation supports numeric and text data. The interpretation of the J text data is preferred. This means that `["NaN"]` returns a one element text wave `{"NaN"}`, whereas `[1, "NaN"]` returns a two element numeric wave `{1, NaN}`. If one element can not be parsed as string then it is assumed that the array contains numeric data. +The literals `inf`, `-inf`, `NaN` and `-NaN` are evaluated as non-finite numbers when appearing in a formula. The JSON null element is only allowed for the topmost array as the parser inserts it for operation with no argument like e.g. `select()`. For sub arrays null elements `[null]` are invalid and result in an error. diff --git a/Packages/tests/Basic/UTF_SweepFormula.ipf b/Packages/tests/Basic/UTF_SweepFormula.ipf index 6245536d71..46b36a42cc 100644 --- a/Packages/tests/Basic/UTF_SweepFormula.ipf +++ b/Packages/tests/Basic/UTF_SweepFormula.ipf @@ -108,22 +108,71 @@ static Function TestNonFiniteValues() TestOperationMinMaxHelper(win, "\"inf\"", "inf", +Inf) TestOperationMinMaxHelper(win, "\"-inf\"", "-inf", -Inf) TestOperationMinMaxHelper(win, "\"NaN\"", "NaN", NaN) + TestOperationMinMaxHelper(win, "\"-NaN\"", "-NaN", NaN) + + WAVE data = SFE_ExecuteFormula("inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("-inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {-Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("-NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) End -// Fails with Abort -// UTF_TD_GENERATOR DataGenerators#NonFiniteValues -//static Function TestNonFiniteValuesPrimitiveOperations([variable var]) -// -// string win, device, str -// -// [win, device] = CreateEmptyUnlockedDataBrowserWindow() -// -// str = "\"" + num2str(var) + "\"" -// TestOperationMinMaxHelper(win, "{\"+\":[1," + str + "]}", "1+" + str, 1 + var) -// TestOperationMinMaxHelper(win, "{\"*\":[1," + str + "]}", "1*" + str, 1 * var) -// TestOperationMinMaxHelper(win, "{\"-\":[1," + str + "]}", "1-" + str, 1 - var) -// TestOperationMinMaxHelper(win, "{\"/\":[1," + str + "]}", "1/" + str, 1 / var) -//End +static Function TestNonFiniteValuesPrimitiveOperations() + + string win + + win = GetDataBrowserWithData() + + // non-finite values entered as bare literals are kept as string by the parser (see #1863) + CheckEqualFormulas("{\"+\":[1,\"NaN\"]}", "1+NaN") + CheckEqualFormulas("{\"-\":[1,\"NaN\"]}", "1-NaN") + CheckEqualFormulas("{\"*\":[1,\"NaN\"]}", "1*NaN") + CheckEqualFormulas("{\"/\":[1,\"NaN\"]}", "1/NaN") + + // and are converted to numeric values when used in primitive operations + WAVE data = SFE_ExecuteFormula("1+NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1-NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1*NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1/NaN", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("1-inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {-Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1+inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1--inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1*inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("1/inf", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {0}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("NaN+1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("NaN-1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("NaN*1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("NaN/1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("inf-1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("inf+1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("inf--1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("inf*1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + WAVE data = SFE_ExecuteFormula("inf/1", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) +End static Function TestScientificNotation() @@ -3513,6 +3562,29 @@ static Function TestAxisPercentage() CHECK_EQUAL_VAR(offset, 0.95) End +/// Test op that returns an empty text wave, mimicking an operation result with no +/// entries (e.g. a lookup with zero matches), to cover +/// SFE_ConvertNonFiniteElements' numpnts(subArray) == 0 guard (see #1863) +static Function/WAVE TestNonFiniteEmptyTextOp(STRUCT SF_ExecutionData &exd) + + Make/FREE/T/N=0 output + + return SFH_GetOutputForExecutorSingle(output, exd.graph, SF_OP_TESTOP) +End + +static Function TestNonFiniteEmptyTextOperand() + + string win, device + + [win, device] = CreateEmptyUnlockedDataBrowserWindow() + win = CreateFakeSweepData(win, device, sweepNo = 0) + + SVAR funcName = $GetSFTestopName(win) + funcName = "UTF_SWEEPFORMULA#TestNonFiniteEmptyTextOp" + + ExecuteSweepFormulaCode(win, "testop() - 1", expectFailure = 1) +End + static Function TestSFHIsArray() Make/FREE numData @@ -3622,3 +3694,43 @@ static Function TestAssertDataStack([WAVE/T input]) CHECK_EQUAL_VAR(DimSize(assertDataStack, ROWS), 0) endtry End + +/// Test op that echoes its first argument resolved as numeric scalar via SFH_GetArgumentAsNumeric +static Function/WAVE TestNonFiniteScalarArgOp(STRUCT SF_ExecutionData &exd) + + variable result + string opShort = SF_OP_TESTOP + + result = SFH_GetArgumentAsNumeric(exd, SF_OP_TESTOP, 0) + + Make/FREE/D output = {result} + + return SFH_GetOutputForExecutorSingle(output, exd.graph, opShort) +End + +static Function TestNonFiniteScalarArgument() + + string win, device + + [win, device] = CreateEmptyUnlockedDataBrowserWindow() + win = CreateFakeSweepData(win, device, sweepNo = 0) + + SVAR funcName = $GetSFTestopName(win) + funcName = "UTF_SWEEPFORMULA#TestNonFiniteScalarArgOp" + + // bare non-finite literals are accepted wherever a numeric scalar argument is taken, see #1863 + WAVE data = SFE_ExecuteFormula("testop(inf)", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {Inf}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("testop(-inf)", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {-Inf}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("testop(NaN)", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + + WAVE data = SFE_ExecuteFormula("testop(-NaN)", win, singleResult = 1, useVariables = 0) + CHECK_EQUAL_WAVES(data, {NaN}, mode = WAVE_DATA) + + // textual arguments that are not non-finite literals are still rejected + ExecuteSweepFormulaCode(win, "testop(abc)", expectFailure = 1) +End