diff --git a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs index 030f3f573..22923e53c 100644 --- a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs @@ -51,7 +51,8 @@ public class AdvancedSearchUtilitiesTests [InlineData("F|F", false)] public void EvaluateBoolean_Precedence(string expr, bool expected) { - var result = AdvancedSearchUtilities.EvaluateBoolean(expr, leaf => leaf == "T"); + // State is unused here; the leaf evaluator just checks the span equals "T". + var result = AdvancedSearchUtilities.EvaluateBoolean(expr, 0, static (_, leaf) => leaf.SequenceEqual("T")); Assert.Equal(expected, result); } diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs index a350cbdcd..a4a26c244 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs @@ -349,7 +349,7 @@ public class AdvancedSearchThreadWorker } private static bool EvaluateRecursive(IEntity entity, ReadOnlySpan span) => - AdvancedSearchUtilities.EvaluateBoolean(span, leaf => EvaluateSingleExpression(entity, leaf)); + AdvancedSearchUtilities.EvaluateBoolean(span, entity, static (e, leaf) => EvaluateSingleExpression(e, leaf)); private static bool EvaluateSingleExpression(IEntity entity, ReadOnlySpan expression) { diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs index adc793241..b1735a67e 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs @@ -375,22 +375,27 @@ public static class AdvancedSearchUtilities return false; } + // Evaluates one trimmed leaf atom against caller-supplied state. A custom delegate is required + // because ReadOnlySpan cannot be a Func<> type argument; passing state avoids a per-call + // capturing closure, so the recursion allocates neither a string nor a closure. + internal delegate bool LeafEvaluator(TState state, ReadOnlySpan leaf); + // OR ('|') binds looser than AND ('@'); split on the outermost OR first, then AND. - internal static bool EvaluateBoolean(ReadOnlySpan expr, Func evalLeaf) + internal static bool EvaluateBoolean(ReadOnlySpan expr, TState state, LeafEvaluator evalLeaf) { var orIndex = expr.IndexOf('|'); if (orIndex != -1) { - return EvaluateBoolean(expr[..orIndex], evalLeaf) || EvaluateBoolean(expr[(orIndex + 1)..], evalLeaf); + return EvaluateBoolean(expr[..orIndex], state, evalLeaf) || EvaluateBoolean(expr[(orIndex + 1)..], state, evalLeaf); } var andIndex = expr.IndexOf('@'); if (andIndex != -1) { - return EvaluateBoolean(expr[..andIndex], evalLeaf) && EvaluateBoolean(expr[(andIndex + 1)..], evalLeaf); + return EvaluateBoolean(expr[..andIndex], state, evalLeaf) && EvaluateBoolean(expr[(andIndex + 1)..], state, evalLeaf); } - return evalLeaf(expr.Trim().ToString()); + return evalLeaf(state, expr.Trim()); } private static int GetEnumSize(Type enumType) =>