diff --git a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs index 986e0d724..412e5c1e0 100644 --- a/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs +++ b/Projects/UOContent.Tests/Tests/Engines/AdvancedSearch/AdvancedSearchUtilitiesTests.cs @@ -39,4 +39,19 @@ public class AdvancedSearchUtilitiesTests { Assert.True(AdvancedSearchUtilities.CompareValues(typeof(Layer), (byte)Layer.OneHanded, "onehanded", "=")); } + + [Theory] + // leaf value is "T"/"F"; evalLeaf returns leaf=="T" + [InlineData("T", true)] + [InlineData("F", false)] + [InlineData("F@F|T", true)] // (F&&F)||T = T (buggy code gave F&&(F||T)=F) + [InlineData("T|F@F", true)] // T||(F&&F) = T (buggy code gave (T||F)&&F=F) + [InlineData("T@F", false)] + [InlineData("T@T", true)] + [InlineData("F|F", false)] + public void EvaluateBoolean_Precedence(string expr, bool expected) + { + var result = AdvancedSearchUtilities.EvaluateBoolean(expr, leaf => leaf == "T"); + Assert.Equal(expected, result); + } } diff --git a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs index 57ec309f1..660df1058 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchThreadWorker.cs @@ -317,24 +317,8 @@ public class AdvancedSearchThreadWorker } } - private static bool EvaluateRecursive(IEntity entity, ReadOnlySpan span) - { - var atIndex = span.IndexOf('@'); - var orIndex = span.IndexOf('|'); - - if (atIndex == -1 && orIndex == -1) - { - return EvaluateSingleExpression(entity, span); - } - - var result = atIndex != -1; - var splitIndex = result ? atIndex : orIndex; - - var left = EvaluateRecursive(entity, span.Slice(0, splitIndex)); - var right = EvaluateRecursive(entity, span.Slice(splitIndex + 1)); - - return result ? left && right : left || right; - } + private static bool EvaluateRecursive(IEntity entity, ReadOnlySpan span) => + AdvancedSearchUtilities.EvaluateBoolean(span, leaf => EvaluateSingleExpression(entity, 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 9532fe20a..16355a249 100644 --- a/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs +++ b/Projects/UOContent/Engines/Advanced Search/AdvancedSearchUtilities.cs @@ -360,6 +360,24 @@ public static class AdvancedSearchUtilities return false; } + // OR ('|') binds looser than AND ('@'); split on the outermost OR first, then AND. + internal static bool EvaluateBoolean(ReadOnlySpan expr, Func evalLeaf) + { + var orIndex = expr.IndexOf('|'); + if (orIndex != -1) + { + return EvaluateBoolean(expr[..orIndex], evalLeaf) || EvaluateBoolean(expr[(orIndex + 1)..], evalLeaf); + } + + var andIndex = expr.IndexOf('@'); + if (andIndex != -1) + { + return EvaluateBoolean(expr[..andIndex], evalLeaf) && EvaluateBoolean(expr[(andIndex + 1)..], evalLeaf); + } + + return evalLeaf(expr.Trim().ToString()); + } + private static int GetEnumSize(Type enumType) => Type.GetTypeCode(Enum.GetUnderlyingType(enumType)) switch {