using Jtr.Parsing.Json; using Jtr.Parsing.Json.Syntax; using Jtr.Parsing; using Jtr.Parsing.Extensions; namespace Jtr.Tools; public static partial class JsonPath { public static partial class Interpreter { private static readonly VisitorContainer Visitors = new(); static Interpreter() { Visitors.Register(WildCard); Visitors.Register(PropertyAccessor); Visitors.Register(Property); Visitors.Register(ArrayIndex); Visitors.Register(ArrayIndexList); Visitors.Register(ObjectIndex); Visitors.Register(ObjectIndexList); } public static IJsonSyntax Evaluate(string input, string path) { var root = JsonParser.Parse("", input); if (root is not JsonArraySyntax && root is not JsonObjectSyntax) throw Report.SyntaxException(root.Token, "Expected a JSON array or object."); var expressions = Parser.Parse(path); var context = new Context { Target = root }; foreach (var expr in expressions) Visitors[expr.GetType()](expr, context); return context.Target; } private static void Evaluate(IJsonPathSyntax node, Context context) => Visitors[node.GetType()](node, context); private static void WildCard(IJsonPathSyntax visitee, Context context) { switch (context.Target) { case JsonObjectSyntax @object: var elements = @object.Properties.Select(p => p.Value).ToArray(); var target = new JsonArraySyntax(@object.Token, elements); context.Target = target; break; case JsonArraySyntax array: context.Target = array; break; default: var syntax = visitee.As(); throw Report.SyntaxException(syntax.Token, "Invalid target for '*'."); } } private static void PropertyAccessor(IJsonPathSyntax visitee, Context context) { var accessor = visitee.As(); Evaluate(accessor.Getter, context); } private static void Property(IJsonPathSyntax visitee, Context context) { var property = visitee.As(); switch (context.Target) { case JsonObjectSyntax @object: foreach (var objectProperty in @object.Properties) { if (objectProperty.Key.Lexeme != property.Token.Lexeme) continue; context.Target = objectProperty.Value; return; } context.Target = new JsonNullSyntax(); break; default: context.Target = new JsonNullSyntax(); break; } } private static void ArrayIndex(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonArraySyntax array) throw Report.SyntaxException(context.Target.Token, "Integer indexes are only allowed on arrays."); var index = visitee.As(); var value = index.IndexAsInt; if (value >= 0 && value < array.Elements.Length) context.Target = array.Elements[value]; else throw Report.SyntaxException(index.Token, "Index out of range."); } private static void ArrayIndexList(IJsonPathSyntax visitee, Context context) { var indices = visitee.As(); if (context.Target is not JsonArraySyntax array) throw Report.SyntaxException(indices.Token, "Integer indices are only allowed on arrays."); var list = new List(); for (var i = 0; i < indices.Indices.Length; i++) { var index = indices.ValueAt(i); if (index >= 0 && index < array.Elements.Length) list.Add(array.Elements.ElementAt(index)); } context.Target = new JsonArraySyntax(array.Token, list.ToArray()); } private static void ObjectIndex(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonObjectSyntax @object) throw Report.SyntaxException(context.Target.Token, "String indices are only allowed on objects."); var index = visitee.As(); foreach (var property in @object.Properties) { if (property.Key.Lexeme != index.Index.Lexeme) continue; context.Target = property.Value; return; } context.Target = new JsonNullSyntax(@object.Token); } private static void ObjectIndexList(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonObjectSyntax @object) throw Report.SyntaxException(context.Target.Token, "Index strings are only allowed on objects."); var indices = visitee.As(); var elements = new List(); foreach (var index in indices.Indexes) { foreach (var property in @object.Properties) { if (property.Key.Lexeme != index.Lexeme) continue; elements.Add(property.Value); break; } } context.Target = new JsonArraySyntax(@object.Token, elements.ToArray()); } } }