namespace Iceshrimp.Parsing; public static class SearchQueryParser { public static List Parse(ReadOnlySpan input) { var results = new List(); input = input.Trim(); if (input.Length == 0) return []; int pos = 0; while (pos < input.Length) { var oldPos = pos; var res = ParseToken(input, ref pos); if (res == null) return results; if (pos <= oldPos) throw new Exception("Infinite loop detected!"); results.Add(res); } return results; } private static ISearchQueryFilter? ParseToken(ReadOnlySpan input, ref int pos) { while (input[pos] == ' ') { pos++; if (pos >= input.Length) return null; } var negated = false; if (input[pos] == '-' && input.Length > pos + 1) { negated = true; pos++; } if (input[pos] == '"' && input.Length > pos + 2) { var closingQuote = pos + 1 + input[(pos + 1)..].IndexOf('"'); if (closingQuote != -1) { var literalRes = new WordFilter(negated, input[++pos..closingQuote].ToString()); pos = closingQuote + 1; return literalRes; } } if (input[pos] == '(' && input.Length > pos + 2) { var closingParen = pos + 1 + input[(pos + 1)..].IndexOf(')'); if (closingParen != -1) { var items = input[++pos..closingParen].ToString().Split(" OR ").Select(p => p.Trim()).ToArray(); var literalRes = new MultiWordFilter(negated, items); if (items.Length > 0) { pos = closingParen + 1; return literalRes; } } } var end = input[pos..].IndexOf(' '); if (end == -1) end = input.Length; else end += pos; var splitIdx = input[pos..end].IndexOf(':'); var keyRange = splitIdx < 1 ? ..0 : pos..(pos + splitIdx); var key = splitIdx < 1 ? ReadOnlySpan.Empty : input[keyRange]; var value = splitIdx < 1 ? input : input[(keyRange.End.Value + 1)..end]; ISearchQueryFilter res = key switch { "cw" => new CwFilter(negated, value.ToString()), "from" or "author" or "by" or "user" => new FromFilter(negated, value.ToString()), "mention" or "mentions" or "mentioning" => new MentionFilter(negated, value.ToString()), "reply" or "replying" or "to" => new ReplyFilter(negated, value.ToString()), "instance" or "domain" or "host" => new InstanceFilter(negated, value.ToString()), "filter" when MiscFilter.TryParse(negated, value, out var parsed) => parsed, "in" when InFilter.TryParse(negated, value, out var parsed) => parsed, "visibility" when VisibilityFilter.TryParse(negated, value, out var parsed) => parsed, "has" or "attachment" or "attached" when AttachmentFilter.TryParse(negated, value, out var parsed) => parsed, "case" when CaseFilter.TryParse(value, out var parsed) => parsed, "match" when MatchFilter.TryParse(value, out var parsed) => parsed, "after" or "since" when DateOnly.TryParse(value, out var date) => new AfterFilter(date), "before" or "until" when DateOnly.TryParse(value, out var date) => new BeforeFilter(date), _ => new WordFilter(negated, input[pos..end].ToString()) }; pos = end; return res; } }