a fork of iceshrimp.net but a tweaked frontend to my personal liking. waow
fediverse
social-media
social
iceshrimp
fedi
1namespace Iceshrimp.Parsing;
2
3public static class SearchQueryParser
4{
5 public static List<ISearchQueryFilter> Parse(ReadOnlySpan<char> input)
6 {
7 var results = new List<ISearchQueryFilter>();
8
9 input = input.Trim();
10 if (input.Length == 0) return [];
11
12 int pos = 0;
13 while (pos < input.Length)
14 {
15 var oldPos = pos;
16 var res = ParseToken(input, ref pos);
17 if (res == null) return results;
18 if (pos <= oldPos) throw new Exception("Infinite loop detected!");
19 results.Add(res);
20 }
21
22 return results;
23 }
24
25 private static ISearchQueryFilter? ParseToken(ReadOnlySpan<char> input, ref int pos)
26 {
27 while (input[pos] == ' ')
28 {
29 pos++;
30 if (pos >= input.Length) return null;
31 }
32
33 var negated = false;
34 if (input[pos] == '-' && input.Length > pos + 1)
35 {
36 negated = true;
37 pos++;
38 }
39
40 if (input[pos] == '"' && input.Length > pos + 2)
41 {
42 var closingQuote = pos + 1 + input[(pos + 1)..].IndexOf('"');
43 if (closingQuote != -1)
44 {
45 var literalRes = new WordFilter(negated, input[++pos..closingQuote].ToString());
46 pos = closingQuote + 1;
47 return literalRes;
48 }
49 }
50
51 if (input[pos] == '(' && input.Length > pos + 2)
52 {
53 var closingParen = pos + 1 + input[(pos + 1)..].IndexOf(')');
54 if (closingParen != -1)
55 {
56 var items = input[++pos..closingParen].ToString().Split(" OR ").Select(p => p.Trim()).ToArray();
57 var literalRes = new MultiWordFilter(negated, items);
58 if (items.Length > 0)
59 {
60 pos = closingParen + 1;
61 return literalRes;
62 }
63 }
64 }
65
66 var end = input[pos..].IndexOf(' ');
67 if (end == -1)
68 end = input.Length;
69 else
70 end += pos;
71
72 var splitIdx = input[pos..end].IndexOf(':');
73 var keyRange = splitIdx < 1 ? ..0 : pos..(pos + splitIdx);
74 var key = splitIdx < 1 ? ReadOnlySpan<char>.Empty : input[keyRange];
75 var value = splitIdx < 1 ? input : input[(keyRange.End.Value + 1)..end];
76
77 ISearchQueryFilter res = key switch
78 {
79 "cw" => new CwFilter(negated, value.ToString()),
80 "from" or "author" or "by" or "user" => new FromFilter(negated, value.ToString()),
81 "mention" or "mentions" or "mentioning" => new MentionFilter(negated, value.ToString()),
82 "reply" or "replying" or "to" => new ReplyFilter(negated, value.ToString()),
83 "instance" or "domain" or "host" => new InstanceFilter(negated, value.ToString()),
84
85 "filter" when MiscFilter.TryParse(negated, value, out var parsed) => parsed,
86 "in" when InFilter.TryParse(negated, value, out var parsed) => parsed,
87 "visibility" when VisibilityFilter.TryParse(negated, value, out var parsed) => parsed,
88 "has" or "attachment" or "attached" when AttachmentFilter.TryParse(negated, value, out var parsed)
89 => parsed,
90
91 "case" when CaseFilter.TryParse(value, out var parsed) => parsed,
92 "match" when MatchFilter.TryParse(value, out var parsed) => parsed,
93
94 "after" or "since" when DateOnly.TryParse(value, out var date) => new AfterFilter(date),
95 "before" or "until" when DateOnly.TryParse(value, out var date) => new BeforeFilter(date),
96
97 _ => new WordFilter(negated, input[pos..end].ToString())
98 };
99
100 pos = end;
101 return res;
102 }
103}