1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 22:33:05 +08:00

Constrain operator parsing better

This commit is contained in:
Bartłomiej Dach 2021-03-02 19:56:36 +01:00
parent 26736d990f
commit e46543a4a9
3 changed files with 99 additions and 58 deletions

View File

@ -194,5 +194,14 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(1, filterCriteria.SearchTerms.Length); Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm); Assert.AreEqual("double\"quote", filterCriteria.Artist.SearchTerm);
} }
[Test]
public void TestOperatorParsing()
{
const string query = "artist=><something";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("><something", filterCriteria.Artist.SearchTerm);
}
} }
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Screens.Select.Filter
{
/// <summary>
/// Defines logical operators that can be used in the song select search box keyword filters.
/// </summary>
public enum Operator
{
Less,
LessOrEqual,
Equal,
GreaterOrEqual,
Greater
}
}

View File

@ -5,13 +5,14 @@ using System;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
internal static class FilterQueryParser internal static class FilterQueryParser
{ {
private static readonly Regex query_syntax_regex = new Regex( private static readonly Regex query_syntax_regex = new Regex(
@"\b(?<key>\w+)(?<op>[=:><]+)(?<value>("".*"")|(\S*))", @"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*"")|(\S*))",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static void ApplyQueries(FilterCriteria criteria, string query) internal static void ApplyQueries(FilterCriteria criteria, string query)
@ -19,7 +20,7 @@ namespace osu.Game.Screens.Select
foreach (Match match in query_syntax_regex.Matches(query)) foreach (Match match in query_syntax_regex.Matches(query))
{ {
var key = match.Groups["key"].Value.ToLower(); var key = match.Groups["key"].Value.ToLower();
var op = match.Groups["op"].Value; var op = parseOperator(match.Groups["op"].Value);
var value = match.Groups["value"].Value; var value = match.Groups["value"].Value;
if (tryParseKeywordCriteria(criteria, key, value, op)) if (tryParseKeywordCriteria(criteria, key, value, op))
@ -29,57 +30,72 @@ namespace osu.Game.Screens.Select
criteria.SearchText = query; criteria.SearchText = query;
} }
private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, string op) private static bool tryParseKeywordCriteria(FilterCriteria criteria, string key, string value, Operator op)
{ {
switch (key) switch (key)
{ {
case "stars" when parseFloatWithPoint(value, out var stars): case "stars" when parseFloatWithPoint(value, out var stars):
updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2); return updateCriteriaRange(ref criteria.StarDifficulty, op, stars, 0.01f / 2);
break;
case "ar" when parseFloatWithPoint(value, out var ar): case "ar" when parseFloatWithPoint(value, out var ar):
updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2); return updateCriteriaRange(ref criteria.ApproachRate, op, ar, 0.1f / 2);
break;
case "dr" when parseFloatWithPoint(value, out var dr): case "dr" when parseFloatWithPoint(value, out var dr):
case "hp" when parseFloatWithPoint(value, out dr): case "hp" when parseFloatWithPoint(value, out dr):
updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); return updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2);
break;
case "cs" when parseFloatWithPoint(value, out var cs): case "cs" when parseFloatWithPoint(value, out var cs):
updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2); return updateCriteriaRange(ref criteria.CircleSize, op, cs, 0.1f / 2);
break;
case "bpm" when parseDoubleWithPoint(value, out var bpm): case "bpm" when parseDoubleWithPoint(value, out var bpm):
updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2); return updateCriteriaRange(ref criteria.BPM, op, bpm, 0.01d / 2);
break;
case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length): case "length" when parseDoubleWithPoint(value.TrimEnd('m', 's', 'h'), out var length):
var scale = getLengthScale(value); var scale = getLengthScale(value);
updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0); return updateCriteriaRange(ref criteria.Length, op, length * scale, scale / 2.0);
break;
case "divisor" when parseInt(value, out var divisor): case "divisor" when parseInt(value, out var divisor):
updateCriteriaRange(ref criteria.BeatDivisor, op, divisor); return updateCriteriaRange(ref criteria.BeatDivisor, op, divisor);
break;
case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue): case "status" when Enum.TryParse<BeatmapSetOnlineStatus>(value, true, out var statusValue):
updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue); return updateCriteriaRange(ref criteria.OnlineStatus, op, statusValue);
break;
case "creator": case "creator":
updateCriteriaText(ref criteria.Creator, op, value); return updateCriteriaText(ref criteria.Creator, op, value);
break;
case "artist": case "artist":
updateCriteriaText(ref criteria.Artist, op, value); return updateCriteriaText(ref criteria.Artist, op, value);
break;
default: default:
return false; return false;
} }
}
return true; private static Operator parseOperator(string value)
{
switch (value)
{
case "=":
case ":":
return Operator.Equal;
case "<":
return Operator.Less;
case "<=":
case "<:":
return Operator.LessOrEqual;
case ">":
return Operator.Greater;
case ">=":
case ">:":
return Operator.GreaterOrEqual;
default:
throw new ArgumentOutOfRangeException(nameof(value), $"Unsupported operator {value}");
}
} }
private static int getLengthScale(string value) => private static int getLengthScale(string value) =>
@ -97,120 +113,119 @@ namespace osu.Game.Screens.Select
private static bool parseInt(string value, out int result) => private static bool parseInt(string value, out int result) =>
int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result); int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out result);
private static void updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, string op, string value) private static bool updateCriteriaText(ref FilterCriteria.OptionalTextFilter textFilter, Operator op, string value)
{ {
switch (op) switch (op)
{ {
case "=": case Operator.Equal:
case ":":
textFilter.SearchTerm = value.Trim('"'); textFilter.SearchTerm = value.Trim('"');
break; return true;
default:
return false;
} }
} }
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, string op, float value, float tolerance = 0.05f) private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange<float> range, Operator op, float value, float tolerance = 0.05f)
{ {
switch (op) switch (op)
{ {
default: default:
return; return false;
case "=": case Operator.Equal:
case ":":
range.Min = value - tolerance; range.Min = value - tolerance;
range.Max = value + tolerance; range.Max = value + tolerance;
break; break;
case ">": case Operator.Greater:
range.Min = value + tolerance; range.Min = value + tolerance;
break; break;
case ">=": case Operator.GreaterOrEqual:
case ">:":
range.Min = value - tolerance; range.Min = value - tolerance;
break; break;
case "<": case Operator.Less:
range.Max = value - tolerance; range.Max = value - tolerance;
break; break;
case "<=": case Operator.LessOrEqual:
case "<:":
range.Max = value + tolerance; range.Max = value + tolerance;
break; break;
} }
return true;
} }
private static void updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, string op, double value, double tolerance = 0.05) private static bool updateCriteriaRange(ref FilterCriteria.OptionalRange<double> range, Operator op, double value, double tolerance = 0.05)
{ {
switch (op) switch (op)
{ {
default: default:
return; return false;
case "=": case Operator.Equal:
case ":":
range.Min = value - tolerance; range.Min = value - tolerance;
range.Max = value + tolerance; range.Max = value + tolerance;
break; break;
case ">": case Operator.Greater:
range.Min = value + tolerance; range.Min = value + tolerance;
break; break;
case ">=": case Operator.GreaterOrEqual:
case ">:":
range.Min = value - tolerance; range.Min = value - tolerance;
break; break;
case "<": case Operator.Less:
range.Max = value - tolerance; range.Max = value - tolerance;
break; break;
case "<=": case Operator.LessOrEqual:
case "<:":
range.Max = value + tolerance; range.Max = value + tolerance;
break; break;
} }
return true;
} }
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value) private static bool updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, Operator op, T value)
where T : struct where T : struct
{ {
switch (op) switch (op)
{ {
default: default:
return; return false;
case "=": case Operator.Equal:
case ":":
range.IsLowerInclusive = range.IsUpperInclusive = true; range.IsLowerInclusive = range.IsUpperInclusive = true;
range.Min = value; range.Min = value;
range.Max = value; range.Max = value;
break; break;
case ">": case Operator.Greater:
range.IsLowerInclusive = false; range.IsLowerInclusive = false;
range.Min = value; range.Min = value;
break; break;
case ">=": case Operator.GreaterOrEqual:
case ">:":
range.IsLowerInclusive = true; range.IsLowerInclusive = true;
range.Min = value; range.Min = value;
break; break;
case "<": case Operator.Less:
range.IsUpperInclusive = false; range.IsUpperInclusive = false;
range.Max = value; range.Max = value;
break; break;
case "<=": case Operator.LessOrEqual:
case "<:":
range.IsUpperInclusive = true; range.IsUpperInclusive = true;
range.Max = value; range.Max = value;
break; break;
} }
return true;
} }
} }
} }