167 lines
6.1 KiB
C#
167 lines
6.1 KiB
C#
using DevDisciples.Json.Parser;
|
|
using DevDisciples.Json.Parser.Syntax;
|
|
using DevDisciples.Parsing;
|
|
using DevDisciples.Parsing.Extensions;
|
|
|
|
namespace DevDisciples.Json.Tools;
|
|
|
|
public static partial class JsonPath
|
|
{
|
|
public static partial class Interpreter
|
|
{
|
|
private static readonly VisitorContainer<IJsonPathSyntax, Context> Visitors = new();
|
|
|
|
static Interpreter()
|
|
{
|
|
Visitors.Register<WildCardSyntax>(WildCardExpression);
|
|
Visitors.Register<PropertyAccessorSyntax>(PropertyAccessorExpression);
|
|
Visitors.Register<PropertySyntax>(PropertyExpression);
|
|
Visitors.Register<ArrayIndexSyntax>(ArrayIndexExpression);
|
|
Visitors.Register<ArrayIndexListSyntax>(ArrayIndexListExpression);
|
|
Visitors.Register<ObjectIndexSyntax>(ObjectIndexExpression);
|
|
Visitors.Register<ObjectIndexListSyntax>(ObjectIndexListExpression);
|
|
}
|
|
|
|
public static IJsonSyntax Evaluate(string source, string path)
|
|
{
|
|
var root = JsonParser.Parse("<json>", source);
|
|
|
|
if (root is not JsonArraySyntax && root is not JsonObjectSyntax)
|
|
throw Report.Error(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 WildCardExpression(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<WildCardSyntax>();
|
|
throw Report.Error(syntax.Token, "Invalid target for '*'.");
|
|
}
|
|
}
|
|
|
|
private static void PropertyAccessorExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
var accessor = visitee.As<PropertyAccessorSyntax>();
|
|
Evaluate(accessor.Getter, context);
|
|
}
|
|
|
|
private static void PropertyExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
var property = visitee.As<PropertySyntax>();
|
|
|
|
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 ArrayIndexExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
if (context.Target is not JsonArraySyntax array)
|
|
throw Report.Error(context.Target.Token, "Integer indexes are only allowed on arrays.");
|
|
|
|
var index = visitee.As<ArrayIndexSyntax>();
|
|
|
|
var value = index.IndexAsInt;
|
|
|
|
if (value >= 0 && value < array.Elements.Length)
|
|
context.Target = array.Elements[value];
|
|
|
|
else throw Report.Error(index.Token, "Index out of range.");
|
|
}
|
|
|
|
private static void ArrayIndexListExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
var indices = visitee.As<ArrayIndexListSyntax>();
|
|
|
|
if (context.Target is not JsonArraySyntax array)
|
|
throw Report.Error(indices.Token, "Integer indices are only allowed on arrays.");
|
|
|
|
var list = new List<IJsonSyntax>();
|
|
|
|
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 ObjectIndexExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
if (context.Target is not JsonObjectSyntax @object)
|
|
throw Report.Error(context.Target.Token, "String indices are only allowed on objects.");
|
|
|
|
var index = visitee.As<ObjectIndexSyntax>();
|
|
|
|
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 ObjectIndexListExpression(IJsonPathSyntax visitee, Context context)
|
|
{
|
|
if (context.Target is not JsonObjectSyntax @object)
|
|
throw Report.Error(context.Target.Token, "Index strings are only allowed on objects.");
|
|
|
|
var indices = visitee.As<ObjectIndexListSyntax>();
|
|
|
|
var elements = new List<IJsonSyntax>();
|
|
|
|
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());
|
|
}
|
|
}
|
|
} |