diff --git a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs index 82ea69a..c02ad73 100644 --- a/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs +++ b/DiscordChatExporter.Core/Exporting/Filtering/Parsing/FilterGrammar.cs @@ -15,16 +15,17 @@ internal static class FilterGrammar from close in Character.EqualTo(open) select value; - private static readonly TextParser FreeCharacter = - Character.Matching(c => - !char.IsWhiteSpace(c) && - // Avoid all special tokens used by the grammar - c is not ('(' or ')' or '"' or '\'' or '-' or '|' or '&'), - "any character except whitespace or `(`, `)`, `\"`, `'`, `-`, `|`, `&`" - ); - private static readonly TextParser UnquotedString = - Parse.OneOf(EscapedCharacter, FreeCharacter).AtLeastOnce().Text(); + Parse.OneOf( + EscapedCharacter, + Character.Matching( + c => + !char.IsWhiteSpace(c) && + // Avoid all special tokens used by the grammar + c is not ('(' or ')' or '"' or '\'' or '-' or '~' or '|' or '&'), + "any character except whitespace or `(`, `)`, `\"`, `'`, `-`, `|`, `&`" + ) + ).AtLeastOnce().Text(); private static readonly TextParser String = Parse.OneOf(QuotedString, UnquotedString).Named("text string"); @@ -64,19 +65,7 @@ internal static class FilterGrammar .Select(k => (MessageFilter) new HasMessageFilter(k)) .Named("has:"); - private static readonly TextParser NegatedFilter = Character - .EqualTo('-') - .IgnoreThen(Parse.Ref(() => StandaloneFilter!)) - .Select(f => (MessageFilter) new NegatedMessageFilter(f)); - - private static readonly TextParser GroupedFilter = - from open in Character.EqualTo('(') - from content in Parse.Ref(() => BinaryExpressionFilter!).Token() - from close in Character.EqualTo(')') - select content; - - private static readonly TextParser StandaloneFilter = Parse.OneOf( - GroupedFilter, + private static readonly TextParser PrimitiveFilter = Parse.OneOf( FromFilter, MentionsFilter, ReactionFilter, @@ -84,19 +73,33 @@ internal static class FilterGrammar ContainsFilter ); - private static readonly TextParser UnaryExpressionFilter = Parse.OneOf( - NegatedFilter, - StandaloneFilter - ); + private static readonly TextParser GroupedFilter = + from open in Character.EqualTo('(') + from content in Parse.Ref(() => ChainedFilter!).Token() + from close in Character.EqualTo(')') + select content; - private static readonly TextParser BinaryExpressionFilter = Parse.Chain( + private static readonly TextParser NegatedFilter = Character + // Dash is annoying to use from CLI due to conflicts with options, so we provide tilde as an alias + .In('-', '~') + .IgnoreThen(Parse.OneOf(GroupedFilter, PrimitiveFilter)) + .Select(f => (MessageFilter) new NegatedMessageFilter(f)); + + private static readonly TextParser ChainedFilter = Parse.Chain( + // Operator Parse.OneOf( // Explicit operator Character.In('|', '&').Token().Try(), // Implicit operator (resolves to 'and') Character.WhiteSpace.AtLeastOnce().IgnoreThen(Parse.Return(' ')) ), - UnaryExpressionFilter, + // Operand + Parse.OneOf( + NegatedFilter, + GroupedFilter, + PrimitiveFilter + ), + // Reducer (op, left, right) => op switch { '|' => new BinaryExpressionMessageFilter(left, right, BinaryExpressionKind.Or), @@ -105,5 +108,5 @@ internal static class FilterGrammar ); public static readonly TextParser Filter = - BinaryExpressionFilter.Token().AtEnd(); + ChainedFilter.Token().AtEnd(); } \ No newline at end of file