From 147345b2919e4e15db5ffabc62bef80e3a5290ab Mon Sep 17 00:00:00 2001 From: Matt Edwards Date: Wed, 12 Jun 2024 09:29:56 -0400 Subject: [PATCH] Refactor namespace --- .../Element/ElementTypeDescriptor.cs} | 25 +++-- .../Element/ElementValueAccessor.cs} | 5 +- .../Element/FilterElementHelper.cs | 3 +- .../Functions}/CountElementFunction.cs | 7 +- .../Functions}/FilterElementFunction.cs | 5 +- .../Functions}/LengthElementFunction.cs | 7 +- .../Functions}/MatchElementFunction.cs | 7 +- .../Functions}/SearchElementFunction.cs | 7 +- .../Functions}/ValueElementFunction.cs | 7 +- .../Descriptors/ITypeDescriptor.cs | 18 ++++ .../IValueAccessor.cs} | 4 +- .../Node/FilterNodeHelper.cs | 2 +- .../Node/Functions}/CountNodeFunction.cs | 7 +- .../Node/Functions}/FilterNodeFunction.cs | 5 +- .../Node/Functions}/LengthNodeFunction.cs | 7 +- .../Node/Functions}/MatchNodeFunction.cs | 7 +- .../Node/Functions}/SearchElementFunction.cs | 7 +- .../Node/Functions}/ValueElementFunction.cs | 7 +- .../Node/NodeTypeDescriptor.cs} | 27 +++-- .../Node/NodeValueAccessor.cs} | 5 +- .../Evaluators/IJsonPathFilterEvaluator.cs | 7 -- .../Evaluators/IJsonTypeDescriptor.cs | 12 --- .../Evaluators/JsonPathEvaluatorException.cs | 20 ---- .../Parser/FilterExpressionFunction.cs | 17 --- .../Evaluators/Parser/FilterFunction.cs | 79 -------------- .../Evaluators/Parser/FunctionCreator.cs | 6 -- .../FilterEvaluator.cs} | 11 +- .../Filters/FilterEvaluatorException.cs | 20 ++++ src/Hyperbee.Json/Filters/IFilterEvaluator.cs | 7 ++ .../Filters/Parser/FilterExtensionFunction.cs | 27 +++++ .../Filters/Parser/FilterFunction.cs | 100 ++++++++++++++++++ .../Parser/FilterTokenizerRegex.cs | 2 +- .../Parser/FilterTruthyExpression.cs | 2 +- .../Filters/Parser/FunctionCreator.cs | 6 ++ .../Parser/JsonPathExpression.cs | 3 +- .../Parser/LiteralFunction.cs | 4 +- .../Parser/ParenFunction.cs | 4 +- .../Parser/ParseExpressionContext.cs | 3 +- src/Hyperbee.Json/JsonPath.cs | 43 ++++---- src/Hyperbee.Json/JsonTypeRegistry.cs | 18 ++-- src/Hyperbee.Json/Tokenizer/JsonPathToken.cs | 3 + .../Hyperbee.Json.Benchmark.csproj | 1 + .../JsonPathExpressionParser.cs | 11 +- .../JsonPathParseAndSelect.cs | 22 +++- .../JsonPathSelectEvaluator.cs | 1 - .../Evaluators/JsonPathExpressionTests.cs | 14 +-- 46 files changed, 349 insertions(+), 263 deletions(-) rename src/Hyperbee.Json/{Evaluators/Parser/Element/JsonElementTypeDescriptor.cs => Descriptors/Element/ElementTypeDescriptor.cs} (64%) rename src/Hyperbee.Json/{Evaluators/Parser/Element/JsonElementValueAccessor.cs => Descriptors/Element/ElementValueAccessor.cs} (96%) rename src/Hyperbee.Json/{Evaluators/Parser => Descriptors}/Element/FilterElementHelper.cs (97%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/CountElementFunction.cs (81%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/FilterElementFunction.cs (63%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/LengthElementFunction.cs (78%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/MatchElementFunction.cs (79%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/SearchElementFunction.cs (79%) rename src/Hyperbee.Json/{Evaluators/Parser/Element => Descriptors/Element/Functions}/ValueElementFunction.cs (63%) create mode 100644 src/Hyperbee.Json/Descriptors/ITypeDescriptor.cs rename src/Hyperbee.Json/{Evaluators/IJsonValueAccessor.cs => Descriptors/IValueAccessor.cs} (81%) rename src/Hyperbee.Json/{Evaluators/Parser => Descriptors}/Node/FilterNodeHelper.cs (97%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/CountNodeFunction.cs (81%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/FilterNodeFunction.cs (63%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/LengthNodeFunction.cs (79%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/MatchNodeFunction.cs (79%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/SearchElementFunction.cs (79%) rename src/Hyperbee.Json/{Evaluators/Parser/Node => Descriptors/Node/Functions}/ValueElementFunction.cs (64%) rename src/Hyperbee.Json/{Evaluators/Parser/Node/JsonNodeTypeDescriptor.cs => Descriptors/Node/NodeTypeDescriptor.cs} (64%) rename src/Hyperbee.Json/{Evaluators/Parser/Node/JsonNodeValueAccessor.cs => Descriptors/Node/NodeValueAccessor.cs} (96%) delete mode 100644 src/Hyperbee.Json/Evaluators/IJsonPathFilterEvaluator.cs delete mode 100644 src/Hyperbee.Json/Evaluators/IJsonTypeDescriptor.cs delete mode 100644 src/Hyperbee.Json/Evaluators/JsonPathEvaluatorException.cs delete mode 100644 src/Hyperbee.Json/Evaluators/Parser/FilterExpressionFunction.cs delete mode 100644 src/Hyperbee.Json/Evaluators/Parser/FilterFunction.cs delete mode 100644 src/Hyperbee.Json/Evaluators/Parser/FunctionCreator.cs rename src/Hyperbee.Json/{Evaluators/JsonPathFilterEvaluator.cs => Filters/FilterEvaluator.cs} (70%) create mode 100644 src/Hyperbee.Json/Filters/FilterEvaluatorException.cs create mode 100644 src/Hyperbee.Json/Filters/IFilterEvaluator.cs create mode 100644 src/Hyperbee.Json/Filters/Parser/FilterExtensionFunction.cs create mode 100644 src/Hyperbee.Json/Filters/Parser/FilterFunction.cs rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/FilterTokenizerRegex.cs (92%) rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/FilterTruthyExpression.cs (95%) create mode 100644 src/Hyperbee.Json/Filters/Parser/FunctionCreator.cs rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/JsonPathExpression.cs (99%) rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/LiteralFunction.cs (90%) rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/ParenFunction.cs (57%) rename src/Hyperbee.Json/{Evaluators => Filters}/Parser/ParseExpressionContext.cs (68%) diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementTypeDescriptor.cs b/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs similarity index 64% rename from src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementTypeDescriptor.cs rename to src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs index 21f5c1aa..0dcca040 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementTypeDescriptor.cs +++ b/src/Hyperbee.Json/Descriptors/Element/ElementTypeDescriptor.cs @@ -1,19 +1,30 @@ -namespace Hyperbee.Json.Evaluators.Parser.Element; +using System.Text.Json; +using Hyperbee.Json.Descriptors.Element.Functions; +using Hyperbee.Json.Filters; +using Hyperbee.Json.Filters.Parser; -public class JsonElementTypeDescriptor : IJsonTypeDescriptor +namespace Hyperbee.Json.Descriptors.Element; + +public class ElementTypeDescriptor : ITypeDescriptor { + private FilterEvaluator _evaluator; + private ElementValueAccessor _accessor; public Dictionary Functions { get; init; } - public IJsonValueAccessor GetAccessor() => - new JsonElementValueAccessor() as IJsonValueAccessor; + public IValueAccessor Accessor + { + get => _accessor ??= new ElementValueAccessor(); + } - public IJsonPathFilterEvaluator GetFilterEvaluator() => - new JsonPathFilterEvaluator( this ); + public IFilterEvaluator FilterEvaluator + { + get => _evaluator ??= new FilterEvaluator( this ); + } public FilterFunction GetFilterFunction( ParseExpressionContext context ) => new FilterElementFunction( context ); - public JsonElementTypeDescriptor() + public ElementTypeDescriptor() { Functions = new Dictionary( [ diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementValueAccessor.cs b/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs similarity index 96% rename from src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementValueAccessor.cs rename to src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs index b303b9c7..f41a4dcc 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/JsonElementValueAccessor.cs +++ b/src/Hyperbee.Json/Descriptors/Element/ElementValueAccessor.cs @@ -1,10 +1,11 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text.Json; +using Hyperbee.Json.Filters; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element; -public class JsonElementValueAccessor : IJsonValueAccessor +public class ElementValueAccessor : IValueAccessor { public IEnumerable<(JsonElement, string)> EnumerateChildValues( JsonElement value ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementHelper.cs b/src/Hyperbee.Json/Descriptors/Element/FilterElementHelper.cs similarity index 97% rename from src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementHelper.cs rename to src/Hyperbee.Json/Descriptors/Element/FilterElementHelper.cs index 6084b7c0..078792df 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementHelper.cs +++ b/src/Hyperbee.Json/Descriptors/Element/FilterElementHelper.cs @@ -1,13 +1,12 @@ using System.Reflection; using System.Text.Json; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element; public static class FilterElementHelper { public static readonly MethodInfo SelectFirstElementValueMethod; public static readonly MethodInfo SelectFirstMethod; - public static readonly MethodInfo SelectElementsMethod; static FilterElementHelper() diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/CountElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs similarity index 81% rename from src/Hyperbee.Json/Evaluators/Parser/Element/CountElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs index 10c2dd3c..461ed5c4 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/CountElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/CountElementFunction.cs @@ -1,11 +1,12 @@ using System.Linq.Expressions; using System.Reflection; using System.Text.Json; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; public class CountElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : - FilterExpressionFunction( methodName, arguments, context ) + FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "count"; @@ -22,7 +23,7 @@ static CountElementFunction() .MakeGenericMethod( typeof( JsonElement ) ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/FilterElementFunction.cs similarity index 63% rename from src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/FilterElementFunction.cs index 1218d8d7..c8c93c51 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/FilterElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/FilterElementFunction.cs @@ -1,10 +1,11 @@ using System.Linq.Expressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; public class FilterElementFunction( ParseExpressionContext context ) : FilterFunction { - protected override Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + protected override Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) { var queryExp = Expression.Constant( item.ToString() ); diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/LengthElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs similarity index 78% rename from src/Hyperbee.Json/Evaluators/Parser/Element/LengthElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs index bc6f8aef..7b320373 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/LengthElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/LengthElementFunction.cs @@ -1,10 +1,11 @@ using System.Linq.Expressions; using System.Reflection; using System.Text.Json; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; -public class LengthElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class LengthElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "length"; @@ -15,7 +16,7 @@ static LengthElementFunction() LengthMethod = typeof( LengthElementFunction ).GetMethod( nameof( Length ), [typeof( JsonElement )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/MatchElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/MatchElementFunction.cs similarity index 79% rename from src/Hyperbee.Json/Evaluators/Parser/Element/MatchElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/MatchElementFunction.cs index 810cae6f..0300885b 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/MatchElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/MatchElementFunction.cs @@ -2,10 +2,11 @@ using System.Reflection; using System.Text.Json; using System.Text.RegularExpressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; -public class MatchElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class MatchElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "match"; @@ -16,7 +17,7 @@ static MatchElementFunction() MatchMethod = typeof( MatchElementFunction ).GetMethod( nameof( Match ), [typeof( JsonElement ), typeof( string )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 2 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/SearchElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/SearchElementFunction.cs similarity index 79% rename from src/Hyperbee.Json/Evaluators/Parser/Element/SearchElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/SearchElementFunction.cs index 2993e9a0..16d3b35c 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/SearchElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/SearchElementFunction.cs @@ -2,10 +2,11 @@ using System.Reflection; using System.Text.Json; using System.Text.RegularExpressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; -public class SearchElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class SearchElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "search"; @@ -16,7 +17,7 @@ static SearchElementFunction() SearchMethod = typeof( SearchElementFunction ).GetMethod( nameof( Search ), [typeof( JsonElement ), typeof( string )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 2 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Element/ValueElementFunction.cs b/src/Hyperbee.Json/Descriptors/Element/Functions/ValueElementFunction.cs similarity index 63% rename from src/Hyperbee.Json/Evaluators/Parser/Element/ValueElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Element/Functions/ValueElementFunction.cs index c9533927..9c7078cb 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Element/ValueElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Element/Functions/ValueElementFunction.cs @@ -1,12 +1,13 @@ using System.Linq.Expressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Element; +namespace Hyperbee.Json.Descriptors.Element.Functions; -public class ValueElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class ValueElementFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "value"; - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Descriptors/ITypeDescriptor.cs b/src/Hyperbee.Json/Descriptors/ITypeDescriptor.cs new file mode 100644 index 00000000..868594ca --- /dev/null +++ b/src/Hyperbee.Json/Descriptors/ITypeDescriptor.cs @@ -0,0 +1,18 @@ +using Hyperbee.Json.Filters; +using Hyperbee.Json.Filters.Parser; + +namespace Hyperbee.Json.Descriptors; + + +public interface IJsonTypeDescriptor +{ + public Dictionary Functions { get; } + + public FilterFunction GetFilterFunction( ParseExpressionContext context ); +} + +public interface ITypeDescriptor : IJsonTypeDescriptor +{ + public IValueAccessor Accessor { get; } + public IFilterEvaluator FilterEvaluator { get; } +} diff --git a/src/Hyperbee.Json/Evaluators/IJsonValueAccessor.cs b/src/Hyperbee.Json/Descriptors/IValueAccessor.cs similarity index 81% rename from src/Hyperbee.Json/Evaluators/IJsonValueAccessor.cs rename to src/Hyperbee.Json/Descriptors/IValueAccessor.cs index 626c2112..c4fdb648 100644 --- a/src/Hyperbee.Json/Evaluators/IJsonValueAccessor.cs +++ b/src/Hyperbee.Json/Descriptors/IValueAccessor.cs @@ -1,6 +1,6 @@ -namespace Hyperbee.Json.Evaluators; +namespace Hyperbee.Json.Descriptors; -public interface IJsonValueAccessor +public interface IValueAccessor { IEnumerable<(TElement, string)> EnumerateChildValues( TElement value ); TElement GetElementAt( TElement value, int index ); diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeHelper.cs b/src/Hyperbee.Json/Descriptors/Node/FilterNodeHelper.cs similarity index 97% rename from src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeHelper.cs rename to src/Hyperbee.Json/Descriptors/Node/FilterNodeHelper.cs index 474e1a29..132d3432 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeHelper.cs +++ b/src/Hyperbee.Json/Descriptors/Node/FilterNodeHelper.cs @@ -3,7 +3,7 @@ using System.Text.Json.Nodes; using Hyperbee.Json.Extensions; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node; public static class FilterNodeHelper { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/CountNodeFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/CountNodeFunction.cs similarity index 81% rename from src/Hyperbee.Json/Evaluators/Parser/Node/CountNodeFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/CountNodeFunction.cs index 111af350..6aa6f345 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/CountNodeFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/CountNodeFunction.cs @@ -1,11 +1,12 @@ using System.Linq.Expressions; using System.Reflection; using System.Text.Json.Nodes; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; public class CountNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : - FilterExpressionFunction( methodName, arguments, context ) + FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "count"; @@ -22,7 +23,7 @@ static CountNodeFunction() .MakeGenericMethod( typeof( JsonNode ) ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/FilterNodeFunction.cs similarity index 63% rename from src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/FilterNodeFunction.cs index ca2a3b72..9b8ed8bb 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/FilterNodeFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/FilterNodeFunction.cs @@ -1,10 +1,11 @@ using System.Linq.Expressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; public class FilterNodeFunction( ParseExpressionContext context ) : FilterFunction { - protected override Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + protected override Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) { var queryExp = Expression.Constant( item.ToString() ); diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/LengthNodeFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/LengthNodeFunction.cs similarity index 79% rename from src/Hyperbee.Json/Evaluators/Parser/Node/LengthNodeFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/LengthNodeFunction.cs index 27860b4b..9b313967 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/LengthNodeFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/LengthNodeFunction.cs @@ -2,10 +2,11 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Nodes; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; -public class LengthNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class LengthNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "length"; @@ -16,7 +17,7 @@ static LengthNodeFunction() LengthMethod = typeof( LengthNodeFunction ).GetMethod( nameof( Length ), [typeof( JsonNode )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/MatchNodeFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/MatchNodeFunction.cs similarity index 79% rename from src/Hyperbee.Json/Evaluators/Parser/Node/MatchNodeFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/MatchNodeFunction.cs index a73fb332..56c86ab7 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/MatchNodeFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/MatchNodeFunction.cs @@ -2,10 +2,11 @@ using System.Reflection; using System.Text.Json.Nodes; using System.Text.RegularExpressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; -public class MatchNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class MatchNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "match"; @@ -16,7 +17,7 @@ static MatchNodeFunction() MatchMethod = typeof( MatchNodeFunction ).GetMethod( nameof( Match ), [typeof( JsonNode ), typeof( string )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 2 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/SearchElementFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/SearchElementFunction.cs similarity index 79% rename from src/Hyperbee.Json/Evaluators/Parser/Node/SearchElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/SearchElementFunction.cs index 304a13e8..c6322712 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/SearchElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/SearchElementFunction.cs @@ -2,10 +2,11 @@ using System.Reflection; using System.Text.Json.Nodes; using System.Text.RegularExpressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; -public class SearchNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class SearchNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "search"; @@ -16,7 +17,7 @@ static SearchNodeFunction() SearchMethod = typeof( SearchNodeFunction ).GetMethod( nameof( Search ), [typeof( JsonNode ), typeof( string )] ); } - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 2 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/ValueElementFunction.cs b/src/Hyperbee.Json/Descriptors/Node/Functions/ValueElementFunction.cs similarity index 64% rename from src/Hyperbee.Json/Evaluators/Parser/Node/ValueElementFunction.cs rename to src/Hyperbee.Json/Descriptors/Node/Functions/ValueElementFunction.cs index aa30a88e..d34d0b32 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/ValueElementFunction.cs +++ b/src/Hyperbee.Json/Descriptors/Node/Functions/ValueElementFunction.cs @@ -1,12 +1,13 @@ using System.Linq.Expressions; +using Hyperbee.Json.Filters.Parser; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node.Functions; -public class ValueNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExpressionFunction( methodName, arguments, context ) +public class ValueNodeFunction( string methodName, IList arguments, ParseExpressionContext context ) : FilterExtensionFunction( methodName, arguments, context ) { public const string Name = "value"; - public override Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ) + public override Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ) { if ( arguments.Count != 1 ) { diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeTypeDescriptor.cs b/src/Hyperbee.Json/Descriptors/Node/NodeTypeDescriptor.cs similarity index 64% rename from src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeTypeDescriptor.cs rename to src/Hyperbee.Json/Descriptors/Node/NodeTypeDescriptor.cs index f2b5b6d2..42003a59 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeTypeDescriptor.cs +++ b/src/Hyperbee.Json/Descriptors/Node/NodeTypeDescriptor.cs @@ -1,19 +1,30 @@ -namespace Hyperbee.Json.Evaluators.Parser.Node; +using System.Text.Json.Nodes; +using Hyperbee.Json.Descriptors.Node.Functions; +using Hyperbee.Json.Filters; +using Hyperbee.Json.Filters.Parser; -public class JsonNodeTypeDescriptor : IJsonTypeDescriptor +namespace Hyperbee.Json.Descriptors.Node; + +public class NodeTypeDescriptor : ITypeDescriptor { - public Dictionary Functions { get; } + private FilterEvaluator _evaluator; + private NodeValueAccessor _accessor; + public Dictionary Functions { get; init; } - public IJsonValueAccessor GetAccessor() => - new JsonNodeValueAccessor() as IJsonValueAccessor; + public IValueAccessor Accessor + { + get => _accessor ??= new NodeValueAccessor(); + } - public IJsonPathFilterEvaluator GetFilterEvaluator() => - new JsonPathFilterEvaluator( this ); + public IFilterEvaluator FilterEvaluator + { + get => _evaluator ??= new FilterEvaluator( this ); + } public FilterFunction GetFilterFunction( ParseExpressionContext context ) => new FilterNodeFunction( context ); - public JsonNodeTypeDescriptor() + public NodeTypeDescriptor() { Functions = new Dictionary( [ diff --git a/src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeValueAccessor.cs b/src/Hyperbee.Json/Descriptors/Node/NodeValueAccessor.cs similarity index 96% rename from src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeValueAccessor.cs rename to src/Hyperbee.Json/Descriptors/Node/NodeValueAccessor.cs index 875b56f6..ac9b8863 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/Node/JsonNodeValueAccessor.cs +++ b/src/Hyperbee.Json/Descriptors/Node/NodeValueAccessor.cs @@ -1,10 +1,11 @@ using System.Globalization; using System.Runtime.CompilerServices; using System.Text.Json.Nodes; +using Hyperbee.Json.Filters; -namespace Hyperbee.Json.Evaluators.Parser.Node; +namespace Hyperbee.Json.Descriptors.Node; -internal class JsonNodeValueAccessor : IJsonValueAccessor +internal class NodeValueAccessor : IValueAccessor { public IEnumerable<(JsonNode, string)> EnumerateChildValues( JsonNode value ) { diff --git a/src/Hyperbee.Json/Evaluators/IJsonPathFilterEvaluator.cs b/src/Hyperbee.Json/Evaluators/IJsonPathFilterEvaluator.cs deleted file mode 100644 index bfd3c7f0..00000000 --- a/src/Hyperbee.Json/Evaluators/IJsonPathFilterEvaluator.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Hyperbee.Json.Evaluators; - -public interface IJsonPathFilterEvaluator -{ - public object Evaluate( string filter, TType current, TType root ); -} diff --git a/src/Hyperbee.Json/Evaluators/IJsonTypeDescriptor.cs b/src/Hyperbee.Json/Evaluators/IJsonTypeDescriptor.cs deleted file mode 100644 index 783f0c7d..00000000 --- a/src/Hyperbee.Json/Evaluators/IJsonTypeDescriptor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Hyperbee.Json.Evaluators.Parser; - -namespace Hyperbee.Json.Evaluators; - -public interface IJsonTypeDescriptor -{ - public Dictionary Functions { get; } - - public IJsonValueAccessor GetAccessor(); - public IJsonPathFilterEvaluator GetFilterEvaluator(); - public FilterFunction GetFilterFunction( ParseExpressionContext context ); -} diff --git a/src/Hyperbee.Json/Evaluators/JsonPathEvaluatorException.cs b/src/Hyperbee.Json/Evaluators/JsonPathEvaluatorException.cs deleted file mode 100644 index 8c2f781f..00000000 --- a/src/Hyperbee.Json/Evaluators/JsonPathEvaluatorException.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Hyperbee.Json.Evaluators; - -[Serializable] -public class JsonPathEvaluatorException : Exception -{ - public JsonPathEvaluatorException() - : base( "JsonPath evaluator exception." ) - { - } - - public JsonPathEvaluatorException( string message ) - : base( message ) - { - } - - public JsonPathEvaluatorException( string message, Exception innerException ) - : base( message, innerException ) - { - } -} diff --git a/src/Hyperbee.Json/Evaluators/Parser/FilterExpressionFunction.cs b/src/Hyperbee.Json/Evaluators/Parser/FilterExpressionFunction.cs deleted file mode 100644 index 7865c5fe..00000000 --- a/src/Hyperbee.Json/Evaluators/Parser/FilterExpressionFunction.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Json.Evaluators.Parser; - -public abstract class FilterExpressionFunction( - string methodName, - IList arguments, - ParseExpressionContext context -) : FilterFunction -{ - public abstract Expression GetExpression( string methodName, IList arguments, ParseExpressionContext context ); - - protected override Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) - { - return GetExpression( methodName, arguments, context ); - } -} diff --git a/src/Hyperbee.Json/Evaluators/Parser/FilterFunction.cs b/src/Hyperbee.Json/Evaluators/Parser/FilterFunction.cs deleted file mode 100644 index 72ae6d8a..00000000 --- a/src/Hyperbee.Json/Evaluators/Parser/FilterFunction.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Linq.Expressions; -using static Hyperbee.Json.Evaluators.Parser.JsonPathExpression; - -namespace Hyperbee.Json.Evaluators.Parser; - -public class FilterFunction -{ - private readonly FilterFunction _implementation; - - public FilterFunction() - { - _implementation = this; - } - - internal FilterFunction( ReadOnlySpan item, FilterTokenType? type, ParseExpressionContext context ) - { - if ( item.Length == 0 && type == FilterTokenType.OpenParen ) - { - // There is no function, just an expression in parentheses. - _implementation = new ParenFunction( context ); - return; - } - - switch ( item[0] ) - { - case '@': - _implementation = context.Descriptor.GetFilterFunction( context ); - return; - case '$': - // Current becomes root - _implementation = context.Descriptor.GetFilterFunction( context with { Current = context.Root } ); - return; - } - - if ( TryGetExpressionFunction( item, context, out _implementation ) ) - { - // Methods based on spec - return; - } - - // Function not found, will try to parse this as a literal value. - var literalFunction = new LiteralFunction(); - _implementation = literalFunction; - } - - public Expression GetExpression( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) - { - return _implementation.Evaluate( data, item, ref start, ref from ); - } - - protected virtual Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) - { - // The real implementation will be in the derived classes. - return Expression.Throw( Expression.Constant( new NotImplementedException() ) ); - } - - private static bool TryGetExpressionFunction( ReadOnlySpan item, ParseExpressionContext context, out FilterFunction function ) - { - var match = FilterTokenizerRegex.RegexFunction().Match( item.ToString() ); - - if ( match.Groups.Count != 3 ) - { - function = null; - return false; - } - - var method = match.Groups[1].Value; - var arguments = match.Groups[2].Value.Split( ',', options: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ); - - if ( context.Descriptor.Functions.TryGetValue( method.ToLowerInvariant(), out var creator ) ) - { - function = creator( method, arguments, context ); - return true; - } - - function = null; - return false; - } -} diff --git a/src/Hyperbee.Json/Evaluators/Parser/FunctionCreator.cs b/src/Hyperbee.Json/Evaluators/Parser/FunctionCreator.cs deleted file mode 100644 index 0cc8b740..00000000 --- a/src/Hyperbee.Json/Evaluators/Parser/FunctionCreator.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Hyperbee.Json.Evaluators.Parser; - -public delegate FilterExpressionFunction FunctionCreator( - string methodName, - IList arguments, - ParseExpressionContext context = null ); diff --git a/src/Hyperbee.Json/Evaluators/JsonPathFilterEvaluator.cs b/src/Hyperbee.Json/Filters/FilterEvaluator.cs similarity index 70% rename from src/Hyperbee.Json/Evaluators/JsonPathFilterEvaluator.cs rename to src/Hyperbee.Json/Filters/FilterEvaluator.cs index 207dedc3..5b3416c5 100644 --- a/src/Hyperbee.Json/Evaluators/JsonPathFilterEvaluator.cs +++ b/src/Hyperbee.Json/Filters/FilterEvaluator.cs @@ -1,10 +1,11 @@ using System.Collections.Concurrent; -using Hyperbee.Json.Evaluators.Parser; +using Hyperbee.Json.Descriptors; +using Hyperbee.Json.Filters.Parser; using Microsoft.CSharp.RuntimeBinder; -namespace Hyperbee.Json.Evaluators; +namespace Hyperbee.Json.Filters; -public sealed class JsonPathFilterEvaluator : IJsonPathFilterEvaluator +public sealed class FilterEvaluator : IFilterEvaluator { private readonly IJsonTypeDescriptor _typeDescriptor; @@ -12,7 +13,7 @@ public sealed class JsonPathFilterEvaluator : IJsonPathFilterEvaluator> Compiled = new(); - public JsonPathFilterEvaluator( IJsonTypeDescriptor typeDescriptor ) + public FilterEvaluator( ITypeDescriptor typeDescriptor ) { _typeDescriptor = typeDescriptor; } @@ -31,7 +32,7 @@ public object Evaluate( string filter, TType current, TType root ) } catch ( Exception ex ) { - throw new JsonPathEvaluatorException( "Error compiling JsonPath expression.", ex ); + throw new FilterEvaluatorException( "Error compiling JsonPath expression.", ex ); } } } diff --git a/src/Hyperbee.Json/Filters/FilterEvaluatorException.cs b/src/Hyperbee.Json/Filters/FilterEvaluatorException.cs new file mode 100644 index 00000000..7ba399e2 --- /dev/null +++ b/src/Hyperbee.Json/Filters/FilterEvaluatorException.cs @@ -0,0 +1,20 @@ +namespace Hyperbee.Json.Filters; + +[Serializable] +public class FilterEvaluatorException : Exception +{ + public FilterEvaluatorException() + : base( "JsonPath filter evaluator exception." ) + { + } + + public FilterEvaluatorException( string message ) + : base( message ) + { + } + + public FilterEvaluatorException( string message, Exception innerException ) + : base( message, innerException ) + { + } +} diff --git a/src/Hyperbee.Json/Filters/IFilterEvaluator.cs b/src/Hyperbee.Json/Filters/IFilterEvaluator.cs new file mode 100644 index 00000000..b96bc9c0 --- /dev/null +++ b/src/Hyperbee.Json/Filters/IFilterEvaluator.cs @@ -0,0 +1,7 @@ + +namespace Hyperbee.Json.Filters; + +public interface IFilterEvaluator +{ + public object Evaluate( string filter, TType current, TType root ); +} diff --git a/src/Hyperbee.Json/Filters/Parser/FilterExtensionFunction.cs b/src/Hyperbee.Json/Filters/Parser/FilterExtensionFunction.cs new file mode 100644 index 00000000..b21e899e --- /dev/null +++ b/src/Hyperbee.Json/Filters/Parser/FilterExtensionFunction.cs @@ -0,0 +1,27 @@ +using System.Linq.Expressions; + +namespace Hyperbee.Json.Filters.Parser; + +public abstract class FilterExtensionFunction : FilterFunction +{ + private readonly string _methodName; + private readonly IList _arguments; + private readonly ParseExpressionContext _context; + + protected FilterExtensionFunction( string methodName, + IList arguments, + ParseExpressionContext context ) + { + _methodName = methodName; + _arguments = arguments; + _context = context; + } + + public abstract Expression GetExtensionExpression( string methodName, IList arguments, ParseExpressionContext context ); + + protected override Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + { + // Convert to extension function shape + return GetExtensionExpression( _methodName, _arguments, _context ); + } +} diff --git a/src/Hyperbee.Json/Filters/Parser/FilterFunction.cs b/src/Hyperbee.Json/Filters/Parser/FilterFunction.cs new file mode 100644 index 00000000..566a714a --- /dev/null +++ b/src/Hyperbee.Json/Filters/Parser/FilterFunction.cs @@ -0,0 +1,100 @@ +using System.Linq.Expressions; +using static Hyperbee.Json.Filters.Parser.JsonPathExpression; + +namespace Hyperbee.Json.Filters.Parser; + +public class FilterFunction +{ + private readonly FilterFunction _implementation; + + public FilterFunction() + { + _implementation = this; + } + + internal FilterFunction( ReadOnlySpan item, FilterTokenType? type, ParseExpressionContext context ) + { + if ( TryGetParenFunction( item, type, context, out _implementation ) ) + { + return; + } + + if ( TryGetFilterFunction( item, context, out _implementation ) ) + { + return; + } + + if ( TryGetExtensionFunction( item, context, out _implementation ) ) + { + return; + } + + // No functions not found, try to parse this as a literal value. + var literalFunction = new LiteralFunction(); + _implementation = literalFunction; + } + + public Expression GetExpression( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + { + return _implementation.GetExpressionImpl( data, item, ref start, ref from ); + } + + protected virtual Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + { + // The real implementation will be in the derived classes. + return Expression.Throw( Expression.Constant( new NotImplementedException() ) ); + } + + private static bool TryGetParenFunction( ReadOnlySpan item, FilterTokenType? type, ParseExpressionContext context, out FilterFunction function ) + { + function = null; + + if ( item.Length != 0 || type != FilterTokenType.OpenParen ) + { + return false; + } + + function = new ParenFunction( context ); + return true; + } + + private static bool TryGetFilterFunction( ReadOnlySpan item, ParseExpressionContext context, out FilterFunction function ) + { + switch ( item[0] ) + { + case '@': + function = context.Descriptor.GetFilterFunction( context ); + return true; + case '$': + // Current becomes root + function = context.Descriptor.GetFilterFunction( context with { Current = context.Root } ); + return true; + } + + function = null; + return false; + } + private static bool TryGetExtensionFunction( ReadOnlySpan item, ParseExpressionContext context, out FilterFunction function ) + { + var match = FilterTokenizerRegex.RegexFunction().Match( item.ToString() ); + + if ( match.Groups.Count != 3 ) + { + function = null; + return false; + } + + var method = match.Groups[1].Value; + var arguments = match.Groups[2].Value.Split( ',', options: StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries ); + + if ( context.Descriptor.Functions.TryGetValue( method.ToLowerInvariant(), out var creator ) ) + { + function = creator( method, arguments, context ); + return true; + } + + function = null; + return false; + } + +} diff --git a/src/Hyperbee.Json/Evaluators/Parser/FilterTokenizerRegex.cs b/src/Hyperbee.Json/Filters/Parser/FilterTokenizerRegex.cs similarity index 92% rename from src/Hyperbee.Json/Evaluators/Parser/FilterTokenizerRegex.cs rename to src/Hyperbee.Json/Filters/Parser/FilterTokenizerRegex.cs index 506d6772..66395c48 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/FilterTokenizerRegex.cs +++ b/src/Hyperbee.Json/Filters/Parser/FilterTokenizerRegex.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; internal static partial class FilterTokenizerRegex { diff --git a/src/Hyperbee.Json/Evaluators/Parser/FilterTruthyExpression.cs b/src/Hyperbee.Json/Filters/Parser/FilterTruthyExpression.cs similarity index 95% rename from src/Hyperbee.Json/Evaluators/Parser/FilterTruthyExpression.cs rename to src/Hyperbee.Json/Filters/Parser/FilterTruthyExpression.cs index 020b1f7e..96ea6ee7 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/FilterTruthyExpression.cs +++ b/src/Hyperbee.Json/Filters/Parser/FilterTruthyExpression.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; public static class FilterTruthyExpression { diff --git a/src/Hyperbee.Json/Filters/Parser/FunctionCreator.cs b/src/Hyperbee.Json/Filters/Parser/FunctionCreator.cs new file mode 100644 index 00000000..805a7443 --- /dev/null +++ b/src/Hyperbee.Json/Filters/Parser/FunctionCreator.cs @@ -0,0 +1,6 @@ +namespace Hyperbee.Json.Filters.Parser; + +public delegate FilterExtensionFunction FunctionCreator( + string methodName, + IList arguments, + ParseExpressionContext context = null ); diff --git a/src/Hyperbee.Json/Evaluators/Parser/JsonPathExpression.cs b/src/Hyperbee.Json/Filters/Parser/JsonPathExpression.cs similarity index 99% rename from src/Hyperbee.Json/Evaluators/Parser/JsonPathExpression.cs rename to src/Hyperbee.Json/Filters/Parser/JsonPathExpression.cs index 5a4908db..e07fb6c7 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/JsonPathExpression.cs +++ b/src/Hyperbee.Json/Filters/Parser/JsonPathExpression.cs @@ -1,7 +1,8 @@ using System.Linq.Expressions; using System.Reflection; +using Hyperbee.Json.Descriptors; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; // Based off Split-and-Merge Expression Parser // https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/october/csharp-a-split-and-merge-expression-parser-in-csharp // Handles `filter-selector` in the https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base/blob/main/sourcecode/abnf/jsonpath-collected.abnf#L69 diff --git a/src/Hyperbee.Json/Evaluators/Parser/LiteralFunction.cs b/src/Hyperbee.Json/Filters/Parser/LiteralFunction.cs similarity index 90% rename from src/Hyperbee.Json/Evaluators/Parser/LiteralFunction.cs rename to src/Hyperbee.Json/Filters/Parser/LiteralFunction.cs index 3f496dd6..80ba2cd3 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/LiteralFunction.cs +++ b/src/Hyperbee.Json/Filters/Parser/LiteralFunction.cs @@ -1,11 +1,11 @@ using System.Linq.Expressions; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; public class LiteralFunction : FilterFunction { - protected override Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + protected override Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) { // strings double or single if ( FilterTokenizerRegex.RegexQuotedDouble().IsMatch( item ) ) diff --git a/src/Hyperbee.Json/Evaluators/Parser/ParenFunction.cs b/src/Hyperbee.Json/Filters/Parser/ParenFunction.cs similarity index 57% rename from src/Hyperbee.Json/Evaluators/Parser/ParenFunction.cs rename to src/Hyperbee.Json/Filters/Parser/ParenFunction.cs index a12f6974..dc261e35 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/ParenFunction.cs +++ b/src/Hyperbee.Json/Filters/Parser/ParenFunction.cs @@ -1,10 +1,10 @@ using System.Linq.Expressions; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; internal class ParenFunction( ParseExpressionContext context ) : FilterFunction { - protected override Expression Evaluate( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) + protected override Expression GetExpressionImpl( ReadOnlySpan data, ReadOnlySpan item, ref int start, ref int from ) { return JsonPathExpression.Parse( data, ref start, ref from, JsonPathExpression.EndArg, context ); } diff --git a/src/Hyperbee.Json/Evaluators/Parser/ParseExpressionContext.cs b/src/Hyperbee.Json/Filters/Parser/ParseExpressionContext.cs similarity index 68% rename from src/Hyperbee.Json/Evaluators/Parser/ParseExpressionContext.cs rename to src/Hyperbee.Json/Filters/Parser/ParseExpressionContext.cs index c262978d..de743c6e 100644 --- a/src/Hyperbee.Json/Evaluators/Parser/ParseExpressionContext.cs +++ b/src/Hyperbee.Json/Filters/Parser/ParseExpressionContext.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; +using Hyperbee.Json.Descriptors; -namespace Hyperbee.Json.Evaluators.Parser; +namespace Hyperbee.Json.Filters.Parser; public record ParseExpressionContext( Expression Current, diff --git a/src/Hyperbee.Json/JsonPath.cs b/src/Hyperbee.Json/JsonPath.cs index 6d228082..4c177aca 100644 --- a/src/Hyperbee.Json/JsonPath.cs +++ b/src/Hyperbee.Json/JsonPath.cs @@ -34,7 +34,7 @@ using System.Collections.Immutable; using System.Globalization; -using Hyperbee.Json.Evaluators; +using Hyperbee.Json.Descriptors; using Hyperbee.Json.Memory; using Hyperbee.Json.Tokenizer; @@ -45,11 +45,7 @@ namespace Hyperbee.Json; public sealed class JsonPath { - private static readonly IJsonTypeDescriptor Descriptor = JsonTypeRegistry.GetDescriptor(); - - private static readonly IJsonValueAccessor Accessor = Descriptor.GetAccessor(); - - private static readonly IJsonPathFilterEvaluator FilterEvaluator = Descriptor.GetFilterEvaluator(); + private static readonly ITypeDescriptor Descriptor = JsonTypeRegistry.GetDescriptor(); public IEnumerable Select( in TElement value, string query ) { @@ -89,6 +85,9 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg { var stack = new Stack( 4 ); + var filterEvaluator = Descriptor.FilterEvaluator; + var accessor = Descriptor.Accessor; + do { // deconstruct the next args node @@ -108,14 +107,14 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg // make sure we have a complex value - if ( !Accessor.IsObjectOrArray( current ) ) + if ( !accessor.IsObjectOrArray( current ) ) throw new InvalidOperationException( "Object or Array expected." ); // try to access object or array using KEY value if ( token.Singular ) { - if ( Accessor.TryGetChildValue( current, selector, out var childValue ) ) + if ( accessor.TryGetChildValue( current, selector, out var childValue ) ) Push( stack, childValue, tokens ); continue; @@ -125,7 +124,7 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg if ( selector == "*" ) { - foreach ( var (_, childKey) in Accessor.EnumerateChildValues( current ) ) + foreach ( var (_, childKey) in accessor.EnumerateChildValues( current ) ) { Push( stack, current, tokens.Push( new( childKey, SelectorKind.UnspecifiedSingular ) ) ); // (Dot | Index) } @@ -137,10 +136,10 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg if ( selector == ".." ) { - foreach ( var (childValue, _) in Accessor.EnumerateChildValues( current ) ) + foreach ( var (childValue, _) in accessor.EnumerateChildValues( current ) ) { - if ( Accessor.IsObjectOrArray( childValue ) ) - Push( stack, childValue, tokens.Push( new( "..", SelectorKind.UnspecifiedGroup ) ) ); // Descendant + if ( accessor.IsObjectOrArray( childValue ) ) + Push( stack, childValue, tokens.Push( JsonPathToken.DescendToken ) ); // Descendant } Push( stack, current, tokens ); @@ -157,7 +156,7 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg if ( childSelector.Length > 2 && childSelector[0] == '(' && childSelector[^1] == ')' ) { - if ( FilterEvaluator.Evaluate( childSelector, current, root ) is not string evalSelector ) + if ( filterEvaluator.Evaluate( childSelector, current, root ) is not string evalSelector ) continue; var selectorKind = evalSelector != "*" && evalSelector != ".." && !JsonPathRegex.RegexSlice().IsMatch( evalSelector ) // (Dot | Index) | Wildcard, Descendant, Slice @@ -172,9 +171,9 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg if ( childSelector.Length > 3 && childSelector[0] == '?' && childSelector[1] == '(' && childSelector[^1] == ')' ) { - foreach ( var (childValue, childKey) in Accessor.EnumerateChildValues( current ) ) + foreach ( var (childValue, childKey) in accessor.EnumerateChildValues( current ) ) { - var filter = FilterEvaluator.Evaluate( JsonPathRegex.RegexPathFilter().Replace( childSelector, "$1" ), childValue, root ); + var filter = filterEvaluator.Evaluate( JsonPathRegex.RegexPathFilter().Replace( childSelector, "$1" ), childValue, root ); // treat the filter result as truthy if the evaluator returned a non-convertible object instance. if ( filter is not null and not IConvertible || Convert.ToBoolean( filter, CultureInfo.InvariantCulture ) ) @@ -186,12 +185,12 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg // [name1,name2,...] or [#,#,...] or [start:end:step] - if ( Accessor.IsArray( current, out var length ) ) + if ( accessor.IsArray( current, out var length ) ) { if ( JsonPathRegex.RegexNumber().IsMatch( childSelector ) ) { // [#,#,...] - Push( stack, Accessor.GetElementAt( current, int.Parse( childSelector ) ), tokens ); + Push( stack, accessor.GetElementAt( current, int.Parse( childSelector ) ), tokens ); continue; } @@ -199,26 +198,26 @@ private static IEnumerable EnumerateMatches( TElement root, ElementArg if ( JsonPathRegex.RegexSlice().IsMatch( childSelector ) ) { foreach ( var index in EnumerateSlice( current, childSelector ) ) - Push( stack, Accessor.GetElementAt( current, index ), tokens ); + Push( stack, accessor.GetElementAt( current, index ), tokens ); continue; } // [name1,name2,...] foreach ( var index in EnumerateArrayIndices( length ) ) - Push( stack, Accessor.GetElementAt( current, index ), tokens.Push( new( childSelector, SelectorKind.UnspecifiedSingular ) ) ); // Name + Push( stack, accessor.GetElementAt( current, index ), tokens.Push( new( childSelector, SelectorKind.UnspecifiedSingular ) ) ); // Name continue; } // [name1,name2,...] - if ( Accessor.IsObject( current ) ) + if ( accessor.IsObject( current ) ) { if ( JsonPathRegex.RegexSlice().IsMatch( childSelector ) || JsonPathRegex.RegexNumber().IsMatch( childSelector ) ) continue; // [name1,name2,...] - if ( Accessor.TryGetChildValue( current, childSelector, out var childValue ) ) + if ( accessor.TryGetChildValue( current, childSelector, out var childValue ) ) Push( stack, childValue, tokens ); } } @@ -238,7 +237,7 @@ private static IEnumerable EnumerateArrayIndices( int length ) private static IEnumerable EnumerateSlice( TElement value, string sliceExpr ) { - if ( !Accessor.IsArray( value, out var length ) ) + if ( !Descriptor.Accessor.IsArray( value, out var length ) ) yield break; var (lower, upper, step) = SliceSyntaxHelper.ParseExpression( sliceExpr, length, reverse: true ); diff --git a/src/Hyperbee.Json/JsonTypeRegistry.cs b/src/Hyperbee.Json/JsonTypeRegistry.cs index e0ec2c18..b1c34417 100644 --- a/src/Hyperbee.Json/JsonTypeRegistry.cs +++ b/src/Hyperbee.Json/JsonTypeRegistry.cs @@ -1,8 +1,6 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using Hyperbee.Json.Evaluators; -using Hyperbee.Json.Evaluators.Parser.Element; -using Hyperbee.Json.Evaluators.Parser.Node; +using Hyperbee.Json.Descriptors; +using Hyperbee.Json.Descriptors.Element; +using Hyperbee.Json.Descriptors.Node; namespace Hyperbee.Json; @@ -12,20 +10,20 @@ public class JsonTypeRegistry static JsonTypeRegistry() { - Register( new JsonElementTypeDescriptor() ); - Register( new JsonNodeTypeDescriptor() ); + Register( new ElementTypeDescriptor() ); + Register( new NodeTypeDescriptor() ); } - public static void Register( IJsonTypeDescriptor descriptor ) + public static void Register( ITypeDescriptor descriptor ) { Descriptors[typeof( TElement )] = descriptor; } - public static IJsonTypeDescriptor GetDescriptor() + public static ITypeDescriptor GetDescriptor() { if ( Descriptors.TryGetValue( typeof( TElement ), out var descriptor ) ) { - return descriptor; + return descriptor as ITypeDescriptor; } throw new InvalidOperationException( $"No JSON descriptors registered for type {typeof( TElement )}." ); diff --git a/src/Hyperbee.Json/Tokenizer/JsonPathToken.cs b/src/Hyperbee.Json/Tokenizer/JsonPathToken.cs index 1b65c31e..669144aa 100644 --- a/src/Hyperbee.Json/Tokenizer/JsonPathToken.cs +++ b/src/Hyperbee.Json/Tokenizer/JsonPathToken.cs @@ -15,8 +15,11 @@ internal record SelectorDescriptor [DebuggerDisplay( "Singular = {Singular}, SelectorCount = {Selectors.Length}" )] internal record JsonPathToken { + public static JsonPathToken DescendToken = new( "..", SelectorKind.UnspecifiedGroup ); + public SelectorDescriptor[] Selectors { get; init; } + // TODO: Check if we can set in ctor public string FirstSelector => Selectors[0].Value; public bool Singular diff --git a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj index 9c8b65e1..7a800ec0 100644 --- a/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj +++ b/test/Hyperbee.Json.Benchmark/Hyperbee.Json.Benchmark.csproj @@ -15,6 +15,7 @@ + diff --git a/test/Hyperbee.Json.Benchmark/JsonPathExpressionParser.cs b/test/Hyperbee.Json.Benchmark/JsonPathExpressionParser.cs index 30b4e103..a0f297a1 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathExpressionParser.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathExpressionParser.cs @@ -2,9 +2,9 @@ using System.Text.Json; using System.Text.Json.Nodes; using BenchmarkDotNet.Attributes; -using Hyperbee.Json.Evaluators.Parser; -using Hyperbee.Json.Evaluators.Parser.Element; -using Hyperbee.Json.Evaluators.Parser.Node; +using Hyperbee.Json.Descriptors.Element; +using Hyperbee.Json.Descriptors.Node; +using Hyperbee.Json.Filters.Parser; namespace Hyperbee.Json.Benchmark; @@ -16,19 +16,18 @@ public class JsonPathExpressionParser [Params( "(\"world\" == 'world') && (true || false)" )] public string Filter; - [GlobalSetup] public void Setup() { _nodeExpressionContext = new ParseExpressionContext( Expression.Parameter( typeof( JsonNode ) ), Expression.Parameter( typeof( JsonNode ) ), - new JsonNodeTypeDescriptor() ); + new NodeTypeDescriptor() ); _elementExpressionContext = new ParseExpressionContext( Expression.Parameter( typeof( JsonElement ) ), Expression.Parameter( typeof( JsonElement ) ), - new JsonElementTypeDescriptor() ); + new ElementTypeDescriptor() ); } [Benchmark] diff --git a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelect.cs b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelect.cs index 8934095f..09e02830 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelect.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathParseAndSelect.cs @@ -4,6 +4,7 @@ using Hyperbee.Json.Extensions; using Newtonsoft.Json.Linq; using JsonEverything = Json.Path; +using JsonCons.JsonPath; namespace Hyperbee.Json.Benchmark; @@ -62,30 +63,41 @@ public class JsonPathParseAndSelect public void JsonPath_Hyperbee_JsonElement() { var element = JsonDocument.Parse( Document ).RootElement; - var _ = element.Select( Filter ).ToArray(); + if ( element.Select( Filter ).ToArray().Length <= 0 ) + throw new InvalidDataException( "Failed Test" ); } [Benchmark] public void JsonPath_Hyperbee_JsonNode() { var node = JsonNode.Parse( Document )!; - var _ = node.Select( Filter ).ToArray(); + if ( node.Select( Filter ).ToArray().Length <= 0 ) + throw new InvalidDataException( "Failed Test" ); } [Benchmark] public void JsonPath_Newtonsoft_JObject() { var jObject = JObject.Parse( Document ); - var _ = jObject.SelectTokens( Filter ).ToArray(); + if ( jObject.SelectTokens( Filter ).ToArray().Length <= 0 ) + throw new InvalidDataException( "Failed Test" ); } [Benchmark] public void JsonPath_JsonEverything_JsonNode() { - var path = JsonEverything.JsonPath.Parse( Filter ); var node = JsonNode.Parse( Document )!; - var _ = path.Evaluate( node ).Matches!.ToArray(); + if ( path.Evaluate( node ).Matches!.ToArray().Length <= 0 ) + throw new InvalidDataException( "Failed Test" ); } + [Benchmark] + public void JsonPath_JsonCons_JsonNode() + { + var path = JsonSelector.Parse( Filter )!; + var element = JsonDocument.Parse( Document ).RootElement; + if ( path.Select( element ).ToArray().Length <= 0 ) + throw new InvalidDataException( "Failed Test" ); + } } diff --git a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs index 6be899f0..9f67d9dd 100644 --- a/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs +++ b/test/Hyperbee.Json.Benchmark/JsonPathSelectEvaluator.cs @@ -1,7 +1,6 @@ using System.Text.Json; using System.Text.Json.Nodes; using BenchmarkDotNet.Attributes; -using Hyperbee.Json.Evaluators; using Hyperbee.Json.Extensions; using Newtonsoft.Json.Linq; diff --git a/test/Hyperbee.Json.Tests/Evaluators/JsonPathExpressionTests.cs b/test/Hyperbee.Json.Tests/Evaluators/JsonPathExpressionTests.cs index f48a4b35..1132b2d4 100644 --- a/test/Hyperbee.Json.Tests/Evaluators/JsonPathExpressionTests.cs +++ b/test/Hyperbee.Json.Tests/Evaluators/JsonPathExpressionTests.cs @@ -3,10 +3,10 @@ using System.Linq.Expressions; using System.Text.Json; using System.Text.Json.Nodes; -using Hyperbee.Json.Evaluators.Parser; -using Hyperbee.Json.Evaluators.Parser.Element; -using Hyperbee.Json.Evaluators.Parser.Node; +using Hyperbee.Json.Descriptors.Element; +using Hyperbee.Json.Descriptors.Node; using Hyperbee.Json.Extensions; +using Hyperbee.Json.Filters.Parser; using Hyperbee.Json.Tests.TestSupport; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -156,11 +156,11 @@ private static (Expression, ParameterExpression) GetExpression( string filter, T ? JsonPathExpression.Parse( filter, new ParseExpressionContext( param, param, - new JsonElementTypeDescriptor() ) ) + new ElementTypeDescriptor() ) ) : JsonPathExpression.Parse( filter, new ParseExpressionContext( param, param, - new JsonNodeTypeDescriptor() ) ); + new NodeTypeDescriptor() ) ); return (expression, param); } @@ -194,7 +194,7 @@ private static bool CompileAndExecute( string filter, Type sourceType ) if ( sourceType == typeof( JsonElement ) ) { var source = GetDocument(); - var func = JsonPathExpression.Compile( filter, new JsonElementTypeDescriptor() ); + var func = JsonPathExpression.Compile( filter, new ElementTypeDescriptor() ); return func( source.RootElement, source.RootElement ); } @@ -202,7 +202,7 @@ private static bool CompileAndExecute( string filter, Type sourceType ) { // arrange var source = GetDocument(); - var func = JsonPathExpression.Compile( filter, new JsonNodeTypeDescriptor() ); + var func = JsonPathExpression.Compile( filter, new NodeTypeDescriptor() ); // act return func( source, source );