- Refactored code
- Implemented basic JSON Path interpreter - Clean up
This commit is contained in:
parent
78ebafb409
commit
82a59eebd2
@ -1,4 +1,6 @@
|
|||||||
namespace DevDisciples.Json.Parser.Tests;
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Tests;
|
||||||
|
|
||||||
public class JsonParserTests
|
public class JsonParserTests
|
||||||
{
|
{
|
||||||
@ -12,7 +14,7 @@ public class JsonParserTests
|
|||||||
{
|
{
|
||||||
const string source = "null";
|
const string source = "null";
|
||||||
var node = JsonParser.Parse(nameof(Can_parse_null), source);
|
var node = JsonParser.Parse(nameof(Can_parse_null), source);
|
||||||
Assert.That(node is JsonNull);
|
Assert.That(node is JsonNullSyntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -25,8 +27,8 @@ public class JsonParserTests
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(trueNode is JsonBool { Value: true });
|
Assert.That(trueNode is JsonBoolSyntax { Value: true });
|
||||||
Assert.That(falseNode is JsonBool { Value: false });
|
Assert.That(falseNode is JsonBoolSyntax { Value: false });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,15 +37,15 @@ public class JsonParserTests
|
|||||||
{
|
{
|
||||||
const string source = "1";
|
const string source = "1";
|
||||||
var node = JsonParser.Parse(nameof(Can_parse_an_integer), source);
|
var node = JsonParser.Parse(nameof(Can_parse_an_integer), source);
|
||||||
Assert.That(node is JsonNumber { Value: 1 });
|
Assert.That(node is JsonNumberSyntax { Value: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Can_lex_a_decimal()
|
public void Can_parse_a_decimal()
|
||||||
{
|
{
|
||||||
const string source = "1.0";
|
const string source = "1.0";
|
||||||
var node = JsonParser.Parse(nameof(Can_lex_a_decimal), source);
|
var node = JsonParser.Parse(nameof(Can_parse_a_decimal), source);
|
||||||
Assert.That(node is JsonNumber { Value: 1.0 });
|
Assert.That(node is JsonNumberSyntax { Value: 1.0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -51,7 +53,7 @@ public class JsonParserTests
|
|||||||
{
|
{
|
||||||
const string source = "\"Hello world!\"";
|
const string source = "\"Hello world!\"";
|
||||||
var tokens = JsonParser.Parse(nameof(Can_parse_a_string), source);
|
var tokens = JsonParser.Parse(nameof(Can_parse_a_string), source);
|
||||||
Assert.That(tokens is JsonString { Value: "Hello world!" });
|
Assert.That(tokens is JsonStringSyntax { Value: "Hello world!" });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -59,7 +61,7 @@ public class JsonParserTests
|
|||||||
{
|
{
|
||||||
const string source = "[]";
|
const string source = "[]";
|
||||||
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_array), source);
|
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_array), source);
|
||||||
Assert.That(tokens is JsonArray);
|
Assert.That(tokens is JsonArraySyntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -68,9 +70,9 @@ public class JsonParserTests
|
|||||||
const string source = "[1]";
|
const string source = "[1]";
|
||||||
var tokens = JsonParser.Parse(nameof(Can_parse_a_single_item_array), source);
|
var tokens = JsonParser.Parse(nameof(Can_parse_a_single_item_array), source);
|
||||||
|
|
||||||
Assert.That(tokens is JsonArray
|
Assert.That(tokens is JsonArraySyntax
|
||||||
{
|
{
|
||||||
Elements: [JsonNumber { Value: 1 }]
|
Elements: [JsonNumberSyntax { Value: 1 }]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,15 +82,15 @@ public class JsonParserTests
|
|||||||
const string source = "[1,true,{},[],null]";
|
const string source = "[1,true,{},[],null]";
|
||||||
var node = JsonParser.Parse(nameof(Can_parse_an_array_with_multiple_items), source);
|
var node = JsonParser.Parse(nameof(Can_parse_an_array_with_multiple_items), source);
|
||||||
|
|
||||||
Assert.That(node is JsonArray
|
Assert.That(node is JsonArraySyntax
|
||||||
{
|
{
|
||||||
Elements:
|
Elements:
|
||||||
[
|
[
|
||||||
JsonNumber { Value: 1 },
|
JsonNumberSyntax { Value: 1 },
|
||||||
JsonBool { Value: true },
|
JsonBoolSyntax { Value: true },
|
||||||
JsonObject { Properties.Length: 0 },
|
JsonObjectSyntax { Properties.Length: 0 },
|
||||||
JsonArray { Elements.Length: 0 },
|
JsonArraySyntax { Elements.Length: 0 },
|
||||||
JsonNull
|
JsonNullSyntax
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -98,7 +100,7 @@ public class JsonParserTests
|
|||||||
{
|
{
|
||||||
const string source = "{}";
|
const string source = "{}";
|
||||||
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_object), source);
|
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_object), source);
|
||||||
Assert.That(tokens is JsonObject { Properties.Length: 0 });
|
Assert.That(tokens is JsonObjectSyntax { Properties.Length: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -107,10 +109,10 @@ public class JsonParserTests
|
|||||||
const string source = "{\"first_name\":\"John\"}";
|
const string source = "{\"first_name\":\"John\"}";
|
||||||
var node = JsonParser.Parse(nameof(Can_parse_an_object_with_one_entry), source);
|
var node = JsonParser.Parse(nameof(Can_parse_an_object_with_one_entry), source);
|
||||||
|
|
||||||
Assert.That(node is JsonObject { Properties.Length: 1 });
|
Assert.That(node is JsonObjectSyntax { Properties.Length: 1 });
|
||||||
var @object = (JsonObject)node;
|
var @object = (JsonObjectSyntax)node;
|
||||||
Assert.That(@object.Properties.Any(property => property.Key == "first_name"));
|
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name"));
|
||||||
Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" });
|
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -121,12 +123,12 @@ public class JsonParserTests
|
|||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(node is JsonObject { Properties.Length: 2 });
|
Assert.That(node is JsonObjectSyntax { Properties.Length: 2 });
|
||||||
var @object = (JsonObject)node;
|
var @object = (JsonObjectSyntax)node;
|
||||||
Assert.That(@object.Properties.Any(property => property.Key == "first_name"));
|
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name"));
|
||||||
Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" });
|
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" });
|
||||||
Assert.That(@object.Properties.Any(property => property.Key == "last_name"));
|
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "last_name"));
|
||||||
Assert.That(@object.Properties.First(property => property.Key == "last_name").Value is JsonString { Value: "Doe" });
|
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "last_name").Value is JsonStringSyntax { Value: "Doe" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,15 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly struct JsonArray : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
public ISyntaxNode[] Elements { get; }
|
|
||||||
|
|
||||||
public JsonArray(Lexer<JsonToken>.Token token, ISyntaxNode[] elements)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
Elements = elements;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly struct JsonBool : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
public bool Value => bool.TryParse(Token.Lexeme, out var val) && val;
|
|
||||||
|
|
||||||
public JsonBool(Lexer<JsonToken>.Token token)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly struct JsonNull : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
|
|
||||||
public JsonNull(Lexer<JsonToken>.Token token)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly struct JsonNumber : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default;
|
|
||||||
|
|
||||||
public JsonNumber(Lexer<JsonToken>.Token token)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly partial struct JsonObject
|
|
||||||
{
|
|
||||||
public readonly struct Property : ISyntaxNode
|
|
||||||
{
|
|
||||||
public string Key { get; }
|
|
||||||
public ISyntaxNode Value { get; }
|
|
||||||
|
|
||||||
public Property(string key, ISyntaxNode value)
|
|
||||||
{
|
|
||||||
Key = key;
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly partial struct JsonObject : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
public Property[] Properties { get; }
|
|
||||||
|
|
||||||
public JsonObject(Lexer<JsonToken>.Token token, Property[] properties)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
Properties = properties;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
using DevDisciples.Parsing;
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
namespace DevDisciples.Json.Parser;
|
||||||
|
|
||||||
public static partial class JsonParser
|
public static partial class JsonParser
|
||||||
{
|
{
|
||||||
public static ISyntaxNode Parse(string file, string source)
|
public static IJsonSyntax Parse(string file, string source)
|
||||||
{
|
{
|
||||||
var tokens = JsonLexer.Default.Lex(file, source).ToArray();
|
var tokens = JsonLexer.Default.Lex(file, source).ToArray();
|
||||||
var context = new Context(tokens);
|
var context = new Context(tokens);
|
||||||
@ -12,33 +13,33 @@ public static partial class JsonParser
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISyntaxNode Expression(ParserContext<JsonToken> ctx)
|
private static IJsonSyntax Expression(ParserContext<JsonToken> ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Match(JsonToken.LeftBracket))
|
if (ctx.Match(JsonToken.LeftBracket))
|
||||||
return Array(ctx);
|
return ArrayExpression(ctx);
|
||||||
|
|
||||||
if (ctx.Match(JsonToken.LeftBrace))
|
if (ctx.Match(JsonToken.LeftBrace))
|
||||||
return Object(ctx);
|
return ObjectExpression(ctx);
|
||||||
|
|
||||||
if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number))
|
if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number))
|
||||||
return Number(ctx);
|
return NumberExpression(ctx);
|
||||||
|
|
||||||
if (ctx.Match(JsonToken.String))
|
if (ctx.Match(JsonToken.String))
|
||||||
return String(ctx);
|
return StringExpression(ctx);
|
||||||
|
|
||||||
if (ctx.Match(JsonToken.Null))
|
if (ctx.Match(JsonToken.Null))
|
||||||
return Null(ctx);
|
return NullExpression(ctx);
|
||||||
|
|
||||||
if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False))
|
if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False))
|
||||||
return Bool(ctx);
|
return BoolExpression(ctx);
|
||||||
|
|
||||||
throw Report.Error(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'");
|
throw Report.Error(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISyntaxNode Array(ParserContext<JsonToken> ctx)
|
private static IJsonSyntax ArrayExpression(ParserContext<JsonToken> ctx)
|
||||||
{
|
{
|
||||||
var previous = ctx.Previous();
|
var previous = ctx.Previous();
|
||||||
List<ISyntaxNode>? elements = null;
|
List<IJsonSyntax>? elements = null;
|
||||||
|
|
||||||
if (!ctx.Check(JsonToken.RightBracket))
|
if (!ctx.Check(JsonToken.RightBracket))
|
||||||
{
|
{
|
||||||
@ -51,13 +52,13 @@ public static partial class JsonParser
|
|||||||
|
|
||||||
ctx.Consume(JsonToken.RightBracket, "Expected ']'");
|
ctx.Consume(JsonToken.RightBracket, "Expected ']'");
|
||||||
|
|
||||||
return new JsonArray(previous, elements?.ToArray() ?? System.Array.Empty<ISyntaxNode>());
|
return new JsonArraySyntax(previous, elements?.ToArray() ?? System.Array.Empty<IJsonSyntax>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISyntaxNode Object(ParserContext<JsonToken> ctx)
|
private static IJsonSyntax ObjectExpression(ParserContext<JsonToken> ctx)
|
||||||
{
|
{
|
||||||
var previous = ctx.Previous();
|
var previous = ctx.Previous();
|
||||||
Dictionary<string, ISyntaxNode>? properties = null;
|
Dictionary<Lexer<JsonToken>.Token, IJsonSyntax>? properties = null;
|
||||||
|
|
||||||
if (!ctx.Check(JsonToken.RightBrace))
|
if (!ctx.Check(JsonToken.RightBrace))
|
||||||
{
|
{
|
||||||
@ -66,32 +67,32 @@ public static partial class JsonParser
|
|||||||
var key = ctx.Consume(JsonToken.String, "Expected property name");
|
var key = ctx.Consume(JsonToken.String, "Expected property name");
|
||||||
ctx.Consume(JsonToken.Colon, "Expected ':' after property name");
|
ctx.Consume(JsonToken.Colon, "Expected ':' after property name");
|
||||||
properties ??= new();
|
properties ??= new();
|
||||||
properties[key.Lexeme] = Expression(ctx);
|
properties[key] = Expression(ctx);
|
||||||
} while (ctx.Match(JsonToken.Comma));
|
} while (ctx.Match(JsonToken.Comma));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Consume(JsonToken.RightBrace, "Expected '}'");
|
ctx.Consume(JsonToken.RightBrace, "Expected '}'");
|
||||||
|
|
||||||
var propertiesArray = properties?.Select(kv => new JsonObject.Property(kv.Key, kv.Value)).ToArray();
|
var propertiesArray = properties?.Select(kv => new JsonPropertySyntax(kv.Key, kv.Value)).ToArray();
|
||||||
|
|
||||||
return new JsonObject(previous, propertiesArray ?? System.Array.Empty<JsonObject.Property>());
|
return new JsonObjectSyntax(previous, propertiesArray ?? System.Array.Empty<JsonPropertySyntax>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISyntaxNode Number(ParserContext<JsonToken> ctx)
|
private static IJsonSyntax NumberExpression(ParserContext<JsonToken> ctx)
|
||||||
{
|
{
|
||||||
if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumber(ctx.Previous());
|
if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumberSyntax(ctx.Previous());
|
||||||
|
|
||||||
var minus = ctx.Previous();
|
var minus = ctx.Previous();
|
||||||
var number = ctx.Consume(JsonToken.Number, "Expected a number after '-'.");
|
var number = ctx.Consume(JsonToken.Number, "Expected a number after '-'.");
|
||||||
|
|
||||||
return new JsonNumber(
|
return new JsonNumberSyntax(
|
||||||
new Lexer<JsonToken>.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column)
|
new Lexer<JsonToken>.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ISyntaxNode String(ParserContext<JsonToken> ctx) => new JsonString(ctx.Previous());
|
private static IJsonSyntax StringExpression(ParserContext<JsonToken> ctx) => new JsonStringSyntax(ctx.Previous());
|
||||||
|
|
||||||
private static ISyntaxNode Null(ParserContext<JsonToken> ctx) => new JsonNull(ctx.Previous());
|
private static IJsonSyntax NullExpression(ParserContext<JsonToken> ctx) => new JsonNullSyntax(ctx.Previous());
|
||||||
|
|
||||||
private static ISyntaxNode Bool(ParserContext<JsonToken> ctx) => new JsonBool(ctx.Previous());
|
private static IJsonSyntax BoolExpression(ParserContext<JsonToken> ctx) => new JsonBoolSyntax(ctx.Previous());
|
||||||
}
|
}
|
@ -1,14 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Parser;
|
|
||||||
|
|
||||||
public readonly struct JsonString : ISyntaxNode
|
|
||||||
{
|
|
||||||
public Lexer<JsonToken>.Token Token { get; }
|
|
||||||
public string Value => Token.Lexeme;
|
|
||||||
|
|
||||||
public JsonString(Lexer<JsonToken>.Token token)
|
|
||||||
{
|
|
||||||
Token = token;
|
|
||||||
}
|
|
||||||
}
|
|
8
DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs
Normal file
8
DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public interface IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
}
|
15
DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs
Normal file
15
DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonArraySyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
public IJsonSyntax[] Elements { get; }
|
||||||
|
|
||||||
|
public JsonArraySyntax(Lexer<JsonToken>.Token token, IJsonSyntax[] elements)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Elements = elements;
|
||||||
|
}
|
||||||
|
}
|
14
DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonBoolSyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
public bool Value => bool.TryParse(Token.Lexeme, out var val) && val;
|
||||||
|
|
||||||
|
public JsonBoolSyntax(Lexer<JsonToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
13
DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs
Normal file
13
DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonNullSyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
|
||||||
|
public JsonNullSyntax(Lexer<JsonToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
14
DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonNumberSyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default;
|
||||||
|
|
||||||
|
public JsonNumberSyntax(Lexer<JsonToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
15
DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs
Normal file
15
DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly partial struct JsonObjectSyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
public JsonPropertySyntax[] Properties { get; }
|
||||||
|
|
||||||
|
public JsonObjectSyntax(Lexer<JsonToken>.Token token, JsonPropertySyntax[] properties)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Properties = properties;
|
||||||
|
}
|
||||||
|
}
|
16
DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs
Normal file
16
DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonPropertySyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token => Key;
|
||||||
|
public Lexer<JsonToken>.Token Key { get; }
|
||||||
|
public IJsonSyntax Value { get; }
|
||||||
|
|
||||||
|
public JsonPropertySyntax(Lexer<JsonToken>.Token key, IJsonSyntax value)
|
||||||
|
{
|
||||||
|
Key = key;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
14
DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
public readonly struct JsonStringSyntax : IJsonSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonToken>.Token Token { get; }
|
||||||
|
public string Value => Token.Lexeme;
|
||||||
|
|
||||||
|
public JsonStringSyntax(Lexer<JsonToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
@ -40,4 +40,11 @@ public class TransformController : ControllerBase
|
|||||||
|
|
||||||
return Ok(new { Result = result });
|
return Ok(new { Result = result });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("jsonpath")]
|
||||||
|
public IActionResult JsonPath([FromBody] JsonPathRequest request)
|
||||||
|
{
|
||||||
|
var result = Tools.JsonPath.Interpreter.Evaluate(request.Source, request.Path);
|
||||||
|
return Ok(new { Result = JsonFormatter.Format(result, new() { Beautify = true }) });
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
public class Json2CsharpRequest : TransformRequest
|
public class Json2CsharpRequest : TransformRequest
|
||||||
{
|
{
|
||||||
public string RootClassName { get; } = Json2CSharpTranslator.Context.DefaultRootClassName;
|
public string RootClassName { get; init; } = Json2CSharpTranslator.Context.DefaultRootClassName;
|
||||||
public string Namespace { get; } = Json2CSharpTranslator.Context.DefaultNamespace;
|
public string Namespace { get; init; } = Json2CSharpTranslator.Context.DefaultNamespace;
|
||||||
}
|
}
|
6
DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs
Normal file
6
DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace DevDisciples.Json.Tools.API.Requests;
|
||||||
|
|
||||||
|
public class JsonPathRequest : TransformRequest
|
||||||
|
{
|
||||||
|
public string Path { get; init; } = string.Empty;
|
||||||
|
}
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class PrettifyRequest : TransformRequest
|
public class PrettifyRequest : TransformRequest
|
||||||
{
|
{
|
||||||
public int IndentSize { get; } = JsonFormatter.Context.DefaultIndentSize;
|
public int IndentSize { get; init; } = JsonFormatter.Context.DefaultIndentSize;
|
||||||
}
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import {AppComponent} from "./app.component";
|
|
||||||
import {HomeComponent} from "./home/home.component";
|
import {HomeComponent} from "./home/home.component";
|
||||||
import {PrettifyComponent} from "./prettify/prettify.component";
|
import {PrettifyComponent} from "./prettify/prettify.component";
|
||||||
import {UglifyComponent} from "./uglify/uglify.component";
|
import {UglifyComponent} from "./uglify/uglify.component";
|
||||||
import {Json2CsharpComponent} from "./json2csharp/json2-csharp.component";
|
import {Json2CsharpComponent} from "./json2csharp/json2-csharp.component";
|
||||||
|
import {JsonPathComponent} from "./json-path/json-path.component";
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'home', component: HomeComponent },
|
{ path: 'home', component: HomeComponent },
|
||||||
{ path: 'prettify', component: PrettifyComponent },
|
{ path: 'prettify', component: PrettifyComponent },
|
||||||
{ path: 'uglify', component: UglifyComponent },
|
{ path: 'uglify', component: UglifyComponent },
|
||||||
{ path: 'json2csharp', component: Json2CsharpComponent },
|
{ path: 'json2csharp', component: Json2CsharpComponent },
|
||||||
|
{ path: 'jsonpath', component: JsonPathComponent },
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<h1>JSON to C#</h1>
|
||||||
|
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Path</mat-label>
|
||||||
|
<input matInput placeholder="$.*" [value]="$path.value" (input)="handlePathChange($event)">
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<app-input-output [input]="$input.value"
|
||||||
|
[inputOptions]="inputOptions"
|
||||||
|
(onInputChange)="handleInputChange($event)"
|
||||||
|
[output]="output"
|
||||||
|
[outputOptions]="outputOptions">
|
||||||
|
</app-input-output>
|
@ -0,0 +1,3 @@
|
|||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { JsonPathComponent } from './json-path.component';
|
||||||
|
|
||||||
|
describe('JsonPathComponent', () => {
|
||||||
|
let component: JsonPathComponent;
|
||||||
|
let fixture: ComponentFixture<JsonPathComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [JsonPathComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(JsonPathComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,69 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {InputOutputComponent} from "../input-output/input-output.component";
|
||||||
|
import {DebounceTime, GenerateDefaultJsonObjectString, MonacoJsonConfig, ReadOnlyMonacoCSharpConfig} from "../defaults";
|
||||||
|
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||||
|
import {JsonTransformService} from "../json-transform.service";
|
||||||
|
import {MatFormField} from "@angular/material/form-field";
|
||||||
|
import {MatInputModule} from "@angular/material/input";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-json-path',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
InputOutputComponent,
|
||||||
|
MatFormField,
|
||||||
|
MatInputModule
|
||||||
|
],
|
||||||
|
templateUrl: './json-path.component.html',
|
||||||
|
styleUrl: './json-path.component.scss'
|
||||||
|
})
|
||||||
|
export class JsonPathComponent implements OnInit {
|
||||||
|
$path: BehaviorSubject<string> = new BehaviorSubject<string>("$.*");
|
||||||
|
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||||
|
inputOptions = MonacoJsonConfig;
|
||||||
|
output: string = "";
|
||||||
|
outputOptions = ReadOnlyMonacoCSharpConfig;
|
||||||
|
|
||||||
|
constructor(private service: JsonTransformService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.$input
|
||||||
|
.pipe(debounceTime(DebounceTime))
|
||||||
|
.subscribe(input => this.update(input, this.$path.value));
|
||||||
|
|
||||||
|
this.$path
|
||||||
|
.pipe(debounceTime(DebounceTime))
|
||||||
|
.subscribe(path => this.update(this.$input.value, path));
|
||||||
|
|
||||||
|
this.update(this.$input.value, this.$path.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(input: string, path: string): void {
|
||||||
|
this.service
|
||||||
|
.jsonPath(input, path)
|
||||||
|
.subscribe({
|
||||||
|
next: response => {
|
||||||
|
console.log(response);
|
||||||
|
this.output = response.body.result;
|
||||||
|
},
|
||||||
|
error: response => {
|
||||||
|
console.log(response)
|
||||||
|
if (response.status === 499) {
|
||||||
|
this.output = response.error.detail;
|
||||||
|
console.log(response.error.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePathChange($event: any): void {
|
||||||
|
console.log($event);
|
||||||
|
this.$path.next($event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange($event: any): void {
|
||||||
|
console.log($event);
|
||||||
|
this.$input.next($event);
|
||||||
|
}
|
||||||
|
}
|
@ -22,4 +22,8 @@ export class JsonTransformService {
|
|||||||
public json2csharp(source: string): Observable<HttpResponse<any>> {
|
public json2csharp(source: string): Observable<HttpResponse<any>> {
|
||||||
return this.http.post(`${this.url}/api/transform/json2csharp`, {Source: source}, {observe: "response"});
|
return this.http.post(`${this.url}/api/transform/json2csharp`, {Source: source}, {observe: "response"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public jsonPath(source: string, path: string): Observable<HttpResponse<any>> {
|
||||||
|
return this.http.post(`${this.url}/api/transform/jsonpath`, {source: source, path: path}, {observe: "response"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<h1>JSON to C#</h1>
|
<h1>JSON to C#</h1>
|
||||||
|
|
||||||
<app-input-output [input]="input"
|
<app-input-output [input]="$input.value"
|
||||||
[inputOptions]="inputOptions"
|
[inputOptions]="inputOptions"
|
||||||
(onInputChange)="handleInputChange($event)"
|
(onInputChange)="handleInputChange($event)"
|
||||||
[output]="output"
|
[output]="output"
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
MonacoJsonConfig,
|
MonacoJsonConfig,
|
||||||
ReadOnlyMonacoCSharpConfig
|
ReadOnlyMonacoCSharpConfig
|
||||||
} from "../defaults";
|
} from "../defaults";
|
||||||
import {debounceTime, Subject} from "rxjs";
|
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-json2csharp',
|
selector: 'app-json2csharp',
|
||||||
@ -19,8 +19,7 @@ import {debounceTime, Subject} from "rxjs";
|
|||||||
styleUrl: './json2-csharp.component.scss'
|
styleUrl: './json2-csharp.component.scss'
|
||||||
})
|
})
|
||||||
export class Json2CsharpComponent implements OnInit {
|
export class Json2CsharpComponent implements OnInit {
|
||||||
input: string = GenerateDefaultJsonObjectString(2);
|
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||||
$input: Subject<string> = new Subject<string>();
|
|
||||||
inputOptions = MonacoJsonConfig;
|
inputOptions = MonacoJsonConfig;
|
||||||
output: string = "";
|
output: string = "";
|
||||||
outputOptions = ReadOnlyMonacoCSharpConfig;
|
outputOptions = ReadOnlyMonacoCSharpConfig;
|
||||||
@ -33,7 +32,7 @@ export class Json2CsharpComponent implements OnInit {
|
|||||||
.pipe(debounceTime(DebounceTime))
|
.pipe(debounceTime(DebounceTime))
|
||||||
.subscribe(input => this.update(input));
|
.subscribe(input => this.update(input));
|
||||||
|
|
||||||
this.update(this.input);
|
this.update(this.$input.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(input: string): void {
|
update(input: string): void {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<mat-list-item routerLink="/prettify">Prettify</mat-list-item>
|
<mat-list-item routerLink="/prettify">Prettify</mat-list-item>
|
||||||
<mat-list-item routerLink="/uglify">Uglify</mat-list-item>
|
<mat-list-item routerLink="/uglify">Uglify</mat-list-item>
|
||||||
<mat-list-item routerLink="/json2csharp">JSON to C#</mat-list-item>
|
<mat-list-item routerLink="/json2csharp">JSON to C#</mat-list-item>
|
||||||
|
<mat-list-item routerLink="/jsonpath">JSON Path</mat-list-item>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
|
|
||||||
|
@ -12,4 +12,5 @@ mat-sidenav {
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<h1>JSON Prettify</h1>
|
<h1>JSON Prettify</h1>
|
||||||
|
|
||||||
<app-input-output [input]="input"
|
<app-input-output [input]="$input.value"
|
||||||
[inputOptions]="inputOptions"
|
[inputOptions]="inputOptions"
|
||||||
(onInputChange)="handleInputChange($event)"
|
(onInputChange)="handleInputChange($event)"
|
||||||
[output]="output"
|
[output]="output"
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
MonacoJsonConfig,
|
MonacoJsonConfig,
|
||||||
ReadOnlyMonacoJsonConfig
|
ReadOnlyMonacoJsonConfig
|
||||||
} from "../defaults";
|
} from "../defaults";
|
||||||
import {debounceTime, Subject} from "rxjs";
|
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-prettify',
|
selector: 'app-prettify',
|
||||||
@ -23,12 +23,12 @@ import {debounceTime, Subject} from "rxjs";
|
|||||||
styleUrl: './prettify.component.scss'
|
styleUrl: './prettify.component.scss'
|
||||||
})
|
})
|
||||||
export class PrettifyComponent implements OnInit {
|
export class PrettifyComponent implements OnInit {
|
||||||
input: string = GenerateDefaultJsonObjectString();
|
// input: string = ;
|
||||||
$input: Subject<string> = new Subject<string>();
|
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString());
|
||||||
inputOptions = MonacoJsonConfig;
|
inputOptions = MonacoJsonConfig;
|
||||||
output: string = GenerateDefaultJsonObjectString(2);
|
output: string = GenerateDefaultJsonObjectString(2);
|
||||||
outputOptions = ReadOnlyMonacoJsonConfig;
|
outputOptions = ReadOnlyMonacoJsonConfig;
|
||||||
error: string = "";
|
// error: string = "";
|
||||||
|
|
||||||
constructor(private service: JsonTransformService) {
|
constructor(private service: JsonTransformService) {
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<h1>JSON Uglify</h1>
|
<h1>JSON Uglify</h1>
|
||||||
|
|
||||||
<app-input-output [input]="input"
|
<app-input-output [input]="$input.value"
|
||||||
[inputOptions]="inputOptions"
|
[inputOptions]="inputOptions"
|
||||||
(onInputChange)="handleInputChange($event)"
|
(onInputChange)="handleInputChange($event)"
|
||||||
[output]="output"
|
[output]="output"
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
MonacoJsonConfig,
|
MonacoJsonConfig,
|
||||||
ReadOnlyMonacoJsonConfig
|
ReadOnlyMonacoJsonConfig
|
||||||
} from "../defaults";
|
} from "../defaults";
|
||||||
import {debounceTime, Subject} from "rxjs";
|
import {debounceTime, BehaviorSubject} from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-uglify',
|
selector: 'app-uglify',
|
||||||
@ -19,8 +19,8 @@ import {debounceTime, Subject} from "rxjs";
|
|||||||
styleUrl: './uglify.component.scss'
|
styleUrl: './uglify.component.scss'
|
||||||
})
|
})
|
||||||
export class UglifyComponent implements OnInit {
|
export class UglifyComponent implements OnInit {
|
||||||
input: string = GenerateDefaultJsonObjectString(2);
|
// input: string = ;
|
||||||
$input: Subject<string> = new Subject<string>();
|
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||||
inputOptions = MonacoJsonConfig;
|
inputOptions = MonacoJsonConfig;
|
||||||
output: string = GenerateDefaultJsonObjectString();
|
output: string = GenerateDefaultJsonObjectString();
|
||||||
outputOptions = ReadOnlyMonacoJsonConfig;
|
outputOptions = ReadOnlyMonacoJsonConfig;
|
||||||
|
@ -11,6 +11,8 @@ public static partial class Json2CSharpTranslator
|
|||||||
|
|
||||||
public string RootClassName { get; init; } = DefaultRootClassName;
|
public string RootClassName { get; init; } = DefaultRootClassName;
|
||||||
public string Namespace { get; init; } = DefaultNamespace;
|
public string Namespace { get; init; } = DefaultNamespace;
|
||||||
|
public string CurrentName { get; set; } = string.Empty;
|
||||||
|
|
||||||
public List<ClassTranslation> Classes { get; } = new();
|
public List<ClassTranslation> Classes { get; } = new();
|
||||||
public readonly StringBuilder Builder = new();
|
public readonly StringBuilder Builder = new();
|
||||||
}
|
}
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
using DevDisciples.Json.Parser;
|
|
||||||
using DevDisciples.Parsing;
|
|
||||||
using Humanizer;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonArrayTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
var context = ContextFromArgs(args);
|
|
||||||
var name = NameFromArgs(args);
|
|
||||||
var array = (JsonArray)visitee;
|
|
||||||
var type = "object";
|
|
||||||
|
|
||||||
if (array.Elements.All(e => e is JsonObject))
|
|
||||||
{
|
|
||||||
context.Classes.Add(SquashObjects(name, array.Elements, args));
|
|
||||||
type = context.Classes.Last().Name;
|
|
||||||
}
|
|
||||||
else if (array.Elements.All(e => e is JsonString es && DateTime.TryParse(es.Value, out _)))
|
|
||||||
{
|
|
||||||
type = "DateTime";
|
|
||||||
}
|
|
||||||
else if (array.Elements.All(e => e is JsonString))
|
|
||||||
{
|
|
||||||
type = "string";
|
|
||||||
}
|
|
||||||
else if (array.Elements.All(e => e is JsonNumber))
|
|
||||||
{
|
|
||||||
type = array.Elements.Any(e => ((JsonNumber)e).Token.Lexeme.Contains('.')) ? "double" : "int";
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = $"List<{type}>",
|
|
||||||
Name = name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClassTranslation SquashObjects(string className, IEnumerable<ISyntaxNode> objects, object[] args)
|
|
||||||
{
|
|
||||||
var classes = objects
|
|
||||||
.Select(@object => JsonObjectTranslator.Translate(@object, args))
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
var squashed = new ClassTranslation
|
|
||||||
{
|
|
||||||
Name = className.Singularize(),
|
|
||||||
Properties = new()
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var @class in classes)
|
|
||||||
foreach (var prop in ((ClassTranslation)@class).Properties)
|
|
||||||
if (squashed.Properties.All(p => p.Name != prop.Name))
|
|
||||||
squashed.Properties.Add(prop);
|
|
||||||
|
|
||||||
return squashed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonBoolTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
return new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = "bool",
|
|
||||||
Name = NameFromArgs(args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonNullTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
return new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = "object",
|
|
||||||
Name = NameFromArgs(args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using DevDisciples.Json.Parser;
|
|
||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonNumberTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
return new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = ((JsonNumber)visitee).Token.Lexeme.Contains('.') ? "double" : "int",
|
|
||||||
Name = NameFromArgs(args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
using DevDisciples.Json.Parser;
|
|
||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonObjectTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
var context = ContextFromArgs(args);
|
|
||||||
var name = NameFromArgs(args);
|
|
||||||
var @class = new ClassTranslation { Name = name, Properties = new() };
|
|
||||||
var @object = (JsonObject)visitee;
|
|
||||||
|
|
||||||
context.Classes.Add(@class);
|
|
||||||
|
|
||||||
foreach (var prop in @object.Properties)
|
|
||||||
{
|
|
||||||
var visitor = Visitors[prop.Value.GetType()];
|
|
||||||
var translation = visitor(prop.Value, context, prop.Key);
|
|
||||||
|
|
||||||
switch (translation)
|
|
||||||
{
|
|
||||||
case ClassTranslation:
|
|
||||||
@class.Properties.Add(new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = translation.Name,
|
|
||||||
Name = translation.Name,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case PropertyTranslation property:
|
|
||||||
@class.Properties.Add(property);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return @class;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using DevDisciples.Json.Parser;
|
|
||||||
using DevDisciples.Parsing;
|
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
|
||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
|
||||||
{
|
|
||||||
public static class JsonStringTranslator
|
|
||||||
{
|
|
||||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
|
||||||
{
|
|
||||||
var @string = (JsonString)visitee;
|
|
||||||
var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string";
|
|
||||||
|
|
||||||
return new PropertyTranslation
|
|
||||||
{
|
|
||||||
Type = type,
|
|
||||||
Name = NameFromArgs(args),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
using DevDisciples.Json.Parser;
|
using DevDisciples.Json.Parser;
|
||||||
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
using DevDisciples.Parsing;
|
using DevDisciples.Parsing;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
|
||||||
@ -6,28 +7,28 @@ namespace DevDisciples.Json.Tools;
|
|||||||
|
|
||||||
public static partial class Json2CSharpTranslator
|
public static partial class Json2CSharpTranslator
|
||||||
{
|
{
|
||||||
public static readonly VisitorContainer<ISyntaxNode, ITranslation> Visitors;
|
private static readonly VisitorContainer<IJsonSyntax, Context, ITranslation> Visitors = new();
|
||||||
|
|
||||||
static Json2CSharpTranslator()
|
static Json2CSharpTranslator()
|
||||||
{
|
{
|
||||||
Visitors = new();
|
Visitors.Register<JsonObjectSyntax>(Object);
|
||||||
Visitors.Register<JsonObject>(JsonObjectTranslator.Translate);
|
Visitors.Register<JsonArraySyntax>(Array);
|
||||||
Visitors.Register<JsonArray>(JsonArrayTranslator.Translate);
|
Visitors.Register<JsonStringSyntax>(String);
|
||||||
Visitors.Register<JsonString>(JsonStringTranslator.Translate);
|
Visitors.Register<JsonNumberSyntax>(Number);
|
||||||
Visitors.Register<JsonNumber>(JsonNumberTranslator.Translate);
|
Visitors.Register<JsonBoolSyntax>(Bool);
|
||||||
Visitors.Register<JsonBool>(JsonBoolTranslator.Translate);
|
Visitors.Register<JsonNullSyntax>(Null);
|
||||||
Visitors.Register<JsonNull>(JsonNullTranslator.Translate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Translate(string source, Context? context = null)
|
public static string Translate(string source, Context? context = null)
|
||||||
{
|
{
|
||||||
if (JsonParser.Parse("<source>", source) is not JsonObject root)
|
if (JsonParser.Parse("<source>", source) is not JsonObjectSyntax root)
|
||||||
throw new ParsingException("Expected a JSON object.");
|
throw new ParsingException("Expected a JSON object.");
|
||||||
|
|
||||||
context ??= new();
|
context ??= new();
|
||||||
|
context.CurrentName = context.RootClassName;
|
||||||
|
|
||||||
var visitor = Visitors[typeof(JsonObject)];
|
var visitor = Visitors[typeof(JsonObjectSyntax)];
|
||||||
visitor(root, context, context.RootClassName);
|
visitor(root, context);
|
||||||
|
|
||||||
context.Builder.Append("//using System;\n");
|
context.Builder.Append("//using System;\n");
|
||||||
context.Builder.Append("//using System.Collections.Generic;\n");
|
context.Builder.Append("//using System.Collections.Generic;\n");
|
||||||
@ -38,7 +39,127 @@ public static partial class Json2CSharpTranslator
|
|||||||
return context.Builder.ToString();
|
return context.Builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context ContextFromArgs(object[] args) => (args[0] as Context)!;
|
private static ITranslation Object(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
var @object = (JsonObjectSyntax)visitee;
|
||||||
|
var @class = new ClassTranslation
|
||||||
|
{
|
||||||
|
Name = context.CurrentName,
|
||||||
|
Properties = new()
|
||||||
|
};
|
||||||
|
|
||||||
private static string NameFromArgs(object[] args) => ((string)args[1]).Camelize();
|
context.Classes.Add(@class);
|
||||||
|
|
||||||
|
foreach (var prop in @object.Properties)
|
||||||
|
{
|
||||||
|
context.CurrentName = prop.Key.Lexeme;
|
||||||
|
var visitor = Visitors[prop.Value.GetType()];
|
||||||
|
var translation = visitor(prop.Value, context);
|
||||||
|
|
||||||
|
switch (translation)
|
||||||
|
{
|
||||||
|
case ClassTranslation:
|
||||||
|
@class.Properties.Add(new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = translation.Name,
|
||||||
|
Name = translation.Name,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case PropertyTranslation property:
|
||||||
|
@class.Properties.Add(property);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @class;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITranslation Array(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
var array = (JsonArraySyntax)visitee;
|
||||||
|
var type = "object";
|
||||||
|
|
||||||
|
if (array.Elements.All(e => e is JsonObjectSyntax))
|
||||||
|
{
|
||||||
|
context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context));
|
||||||
|
type = context.Classes.Last().Name;
|
||||||
|
}
|
||||||
|
else if (array.Elements.All(e => e is JsonStringSyntax es && DateTime.TryParse(es.Value, out _)))
|
||||||
|
{
|
||||||
|
type = "DateTime";
|
||||||
|
}
|
||||||
|
else if (array.Elements.All(e => e is JsonStringSyntax))
|
||||||
|
{
|
||||||
|
type = "string";
|
||||||
|
}
|
||||||
|
else if (array.Elements.All(e => e is JsonNumberSyntax))
|
||||||
|
{
|
||||||
|
type = array.Elements.Any(e => ((JsonNumberSyntax)e).Token.Lexeme.Contains('.')) ? "double" : "int";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = $"List<{type}>",
|
||||||
|
Name = context.CurrentName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITranslation String(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
var @string = (JsonStringSyntax)visitee;
|
||||||
|
var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string";
|
||||||
|
|
||||||
|
return new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = type,
|
||||||
|
Name = context.CurrentName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITranslation Number(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
return new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = ((JsonNumberSyntax)visitee).Token.Lexeme.Contains('.') ? "double" : "int",
|
||||||
|
Name = context.CurrentName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITranslation Bool(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
return new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = "bool",
|
||||||
|
Name = context.CurrentName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ITranslation Null(IJsonSyntax visitee, Context context)
|
||||||
|
{
|
||||||
|
return new PropertyTranslation
|
||||||
|
{
|
||||||
|
Type = "object",
|
||||||
|
Name = context.CurrentName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClassTranslation SquashObjects(string className, IEnumerable<IJsonSyntax> objects, Context context)
|
||||||
|
{
|
||||||
|
var classes = objects
|
||||||
|
.Select(@object => Object(@object, context))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var squashed = new ClassTranslation
|
||||||
|
{
|
||||||
|
Name = className.Singularize(),
|
||||||
|
Properties = new()
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var @class in classes)
|
||||||
|
foreach (var prop in ((ClassTranslation)@class).Properties)
|
||||||
|
if (squashed.Properties.All(p => p.Name != prop.Name))
|
||||||
|
squashed.Properties.Add(prop);
|
||||||
|
|
||||||
|
return squashed;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,45 +1,39 @@
|
|||||||
using DevDisciples.Json.Parser;
|
using DevDisciples.Json.Parser;
|
||||||
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
using DevDisciples.Parsing;
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
namespace DevDisciples.Json.Tools;
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
public static partial class JsonFormatter
|
public static partial class JsonFormatter
|
||||||
{
|
{
|
||||||
public static VisitorContainer Visitors { get; }
|
private static VisitorContainer<IJsonSyntax, Context> Visitors { get; } = new();
|
||||||
|
|
||||||
static JsonFormatter()
|
static JsonFormatter()
|
||||||
{
|
{
|
||||||
Visitors = new();
|
Visitors.Register<JsonArraySyntax>(PrintArray);
|
||||||
Visitors.Register<JsonArray>(PrintArray);
|
Visitors.Register<JsonObjectSyntax>(PrintObject);
|
||||||
Visitors.Register<JsonObject>(PrintObject);
|
Visitors.Register<JsonStringSyntax>(PrintString);
|
||||||
Visitors.Register<JsonString>(PrintString);
|
Visitors.Register<JsonNumberSyntax>(PrintNumber);
|
||||||
Visitors.Register<JsonNumber>(PrintNumber);
|
Visitors.Register<JsonBoolSyntax>(PrintBool);
|
||||||
Visitors.Register<JsonBool>(PrintBool);
|
Visitors.Register<JsonNullSyntax>(PrintNull);
|
||||||
Visitors.Register<JsonNull>(PrintNull);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Format(string source, Context? context)
|
public static string Format(string source, Context? context)
|
||||||
{
|
{
|
||||||
var nodes = JsonParser.Parse("<source>", source);
|
var node = JsonParser.Parse("<source>", source);
|
||||||
return Format(nodes, context);
|
return Format(node, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Format(ISyntaxNode visitee, Context? context = null)
|
public static string Format(IJsonSyntax visitee, Context? context = null)
|
||||||
{
|
{
|
||||||
var ctx = context ?? new();
|
context ??= new();
|
||||||
Visitors[visitee.GetType()](visitee, ctx);
|
Visitors[visitee.GetType()](visitee, context);
|
||||||
return context!.Builder.ToString();
|
return context.Builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context ContextFromArgs(object[] args)
|
private static void PrintArray(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
return (args[0] as Context)!;
|
var array = (JsonArraySyntax)visitee;
|
||||||
}
|
|
||||||
|
|
||||||
private static void PrintArray(object visitee, object[] args)
|
|
||||||
{
|
|
||||||
var context = ContextFromArgs(args);
|
|
||||||
var array = (JsonArray)visitee;
|
|
||||||
|
|
||||||
context.Builder.Append($"[{context.NewLine}");
|
context.Builder.Append($"[{context.NewLine}");
|
||||||
context.IncrementDepth();
|
context.IncrementDepth();
|
||||||
@ -48,7 +42,7 @@ public static partial class JsonFormatter
|
|||||||
{
|
{
|
||||||
var node = array.Elements[i];
|
var node = array.Elements[i];
|
||||||
context.Builder.Append(context.Indent);
|
context.Builder.Append(context.Indent);
|
||||||
Visitors[node.GetType()](node, args);
|
Visitors[node.GetType()](node, context);
|
||||||
if (i < array.Elements.Length - 1) context.Builder.Append($",{context.NewLine}");
|
if (i < array.Elements.Length - 1) context.Builder.Append($",{context.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +50,9 @@ public static partial class JsonFormatter
|
|||||||
context.Builder.Append($"{context.NewLine}{context.Indent}]");
|
context.Builder.Append($"{context.NewLine}{context.Indent}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintObject(object visitee, object[] args)
|
private static void PrintObject(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var context = ContextFromArgs(args);
|
var @object = (JsonObjectSyntax)visitee;
|
||||||
var @object = (JsonObject)visitee;
|
|
||||||
|
|
||||||
context.Builder.Append($"{{{context.NewLine}");
|
context.Builder.Append($"{{{context.NewLine}");
|
||||||
context.IncrementDepth();
|
context.IncrementDepth();
|
||||||
@ -68,8 +61,8 @@ public static partial class JsonFormatter
|
|||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var property = @object.Properties.ElementAt(i);
|
var property = @object.Properties.ElementAt(i);
|
||||||
context.Builder.Append($"{context.Indent}\"{property.Key}\":{context.Space}");
|
context.Builder.Append($"{context.Indent}\"{property.Key.Lexeme}\":{context.Space}");
|
||||||
Visitors[property.Value.GetType()](property.Value, args);
|
Visitors[property.Value.GetType()](property.Value, context);
|
||||||
if (i < count - 1) context.Builder.Append($",{context.NewLine}");
|
if (i < count - 1) context.Builder.Append($",{context.NewLine}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,30 +70,26 @@ public static partial class JsonFormatter
|
|||||||
context.Builder.Append($"{context.NewLine}{context.Indent}}}");
|
context.Builder.Append($"{context.NewLine}{context.Indent}}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintString(object visitee, object[] args)
|
private static void PrintString(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var context = ContextFromArgs(args);
|
var @string = (JsonStringSyntax)visitee;
|
||||||
var @string = (JsonString)visitee;
|
|
||||||
context.Builder.Append($"\"{@string.Token.Lexeme}\"");
|
context.Builder.Append($"\"{@string.Token.Lexeme}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintNumber(object visitee, object[] args)
|
private static void PrintNumber(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var context = ContextFromArgs(args);
|
var number = (JsonNumberSyntax)visitee;
|
||||||
var number = (JsonNumber)visitee;
|
|
||||||
context.Builder.Append($"{number.Value}");
|
context.Builder.Append($"{number.Value}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintBool(object visitee, object[] args)
|
private static void PrintBool(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var context = ContextFromArgs(args);
|
var @bool = (JsonBoolSyntax)visitee;
|
||||||
var @bool = (JsonBool)visitee;
|
|
||||||
context.Builder.Append($"{@bool.Value.ToString().ToLower()}");
|
context.Builder.Append($"{@bool.Value.ToString().ToLower()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintNull(object visitee, object[] args)
|
private static void PrintNull(IJsonSyntax visitee, Context context)
|
||||||
{
|
{
|
||||||
var context = ContextFromArgs(args);
|
|
||||||
context.Builder.Append("null");
|
context.Builder.Append("null");
|
||||||
}
|
}
|
||||||
}
|
}
|
14
DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs
Normal file
14
DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using DevDisciples.Json.Parser.Syntax;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
public static partial class JsonPath
|
||||||
|
{
|
||||||
|
public static partial class Interpreter
|
||||||
|
{
|
||||||
|
public class Context
|
||||||
|
{
|
||||||
|
public IJsonSyntax Target { get; set; } = default!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
DevDisciples.Json.Tools/JsonPath.Interpreter.cs
Normal file
167
DevDisciples.Json.Tools/JsonPath.Interpreter.cs
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
DevDisciples.Json.Tools/JsonPath.Lexer.cs
Normal file
38
DevDisciples.Json.Tools/JsonPath.Lexer.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
public static partial class JsonPath
|
||||||
|
{
|
||||||
|
public class Lexer : Lexer<JsonPathToken>
|
||||||
|
{
|
||||||
|
public static readonly Lexer Default = new();
|
||||||
|
|
||||||
|
protected override JsonPathToken EndOfSource => JsonPathToken.EndOfSource;
|
||||||
|
|
||||||
|
public Lexer()
|
||||||
|
{
|
||||||
|
Rules =
|
||||||
|
[
|
||||||
|
DefaultRule.NewLine,
|
||||||
|
DefaultRule.Number(JsonPathToken.Number),
|
||||||
|
DefaultRule.Identifier(JsonPathToken.Identifier),
|
||||||
|
DefaultRule.DoubleQuoteString(JsonPathToken.String),
|
||||||
|
|
||||||
|
ctx => DefaultRule.IgnoreWhitespace(ctx),
|
||||||
|
ctx => Match(ctx, JsonPathToken.False, "false"),
|
||||||
|
ctx => Match(ctx, JsonPathToken.True, "true"),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Null, "null"),
|
||||||
|
ctx => Match(ctx, JsonPathToken.DotDot, ".."),
|
||||||
|
ctx => Match(ctx, JsonPathToken.DollarSign, '$'),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Asterisk, '*'),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Dot, '.'),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Minus, '-'),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Colon, ':'),
|
||||||
|
ctx => Match(ctx, JsonPathToken.LeftBracket, '['),
|
||||||
|
ctx => Match(ctx, JsonPathToken.Comma, ','),
|
||||||
|
ctx => Match(ctx, JsonPathToken.RightBracket, ']'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
DevDisciples.Json.Tools/JsonPath.Parser.Context.cs
Normal file
16
DevDisciples.Json.Tools/JsonPath.Parser.Context.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
public static partial class JsonPath
|
||||||
|
{
|
||||||
|
public static partial class Parser
|
||||||
|
{
|
||||||
|
public class Context : ParserContext<JsonPathToken>
|
||||||
|
{
|
||||||
|
public Context(Memory<Lexer<JsonPathToken>.Token> tokens) : base(tokens, JsonPathToken.EndOfSource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
110
DevDisciples.Json.Tools/JsonPath.Parser.cs
Normal file
110
DevDisciples.Json.Tools/JsonPath.Parser.cs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
public static partial class JsonPath
|
||||||
|
{
|
||||||
|
public static partial class Parser
|
||||||
|
{
|
||||||
|
public static List<IJsonPathSyntax> Parse(string path)
|
||||||
|
{
|
||||||
|
var pathTokens = Lexer.Default.Lex("<path>", path);
|
||||||
|
var context = new Context(pathTokens.ToArray());
|
||||||
|
|
||||||
|
context.Match(JsonPathToken.DollarSign); // Match the '$' eagerly. This will make it optional by default.
|
||||||
|
|
||||||
|
var syntax = new List<IJsonPathSyntax>();
|
||||||
|
while (!context.Ended())
|
||||||
|
syntax.Add(Expression(context));
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonPathSyntax Expression(Context context)
|
||||||
|
{
|
||||||
|
if (context.Match(JsonPathToken.Dot))
|
||||||
|
return PropertyAccessorExpression(context);
|
||||||
|
|
||||||
|
if (context.Match(JsonPathToken.LeftBracket))
|
||||||
|
return IndexAccessorExpression(context);
|
||||||
|
|
||||||
|
throw context.Error("Invalid expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonPathSyntax PropertyAccessorExpression(Context context)
|
||||||
|
{
|
||||||
|
var token = context.Previous();
|
||||||
|
|
||||||
|
IJsonPathSyntax getter;
|
||||||
|
|
||||||
|
if (context.Match(JsonPathToken.Asterisk))
|
||||||
|
getter = new WildCardSyntax(context.Previous());
|
||||||
|
|
||||||
|
else if (context.Match(JsonPathToken.Identifier))
|
||||||
|
getter = new PropertySyntax(context.Previous());
|
||||||
|
|
||||||
|
else throw context.Error("Expected a getter expression");
|
||||||
|
|
||||||
|
return new PropertyAccessorSyntax(token, getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonPathSyntax IndexAccessorExpression(Context context)
|
||||||
|
{
|
||||||
|
var token = context.Previous();
|
||||||
|
|
||||||
|
IJsonPathSyntax syntax;
|
||||||
|
|
||||||
|
if (context.Match(JsonPathToken.Asterisk))
|
||||||
|
syntax = new WildCardSyntax(context.Previous());
|
||||||
|
|
||||||
|
else if (context.Match(JsonPathToken.Number))
|
||||||
|
syntax = ArrayIndexExpression(token, context);
|
||||||
|
|
||||||
|
else if (context.Match(JsonPathToken.String))
|
||||||
|
syntax = ObjectIndexExpression(token, context);
|
||||||
|
|
||||||
|
else throw context.Error("Expected an index expression.");
|
||||||
|
|
||||||
|
context.Consume(JsonPathToken.RightBracket, "Expected ']' after index expression.");
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonPathSyntax ArrayIndexExpression(Lexer<JsonPathToken>.Token token, Context context)
|
||||||
|
{
|
||||||
|
var index = context.Previous();
|
||||||
|
|
||||||
|
if (!context.Match(JsonPathToken.Comma)) return new ArrayIndexSyntax(token, index);
|
||||||
|
|
||||||
|
var indexes = new List<Lexer<JsonPathToken>.Token> { index };
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
index = context.Consume(JsonPathToken.Number, "Invalid array index.");
|
||||||
|
|
||||||
|
if (!int.TryParse(index.Lexeme, out _)) throw context.Error(index, "Invalid array index.");
|
||||||
|
|
||||||
|
indexes.Add(index);
|
||||||
|
} while (!context.Ended() && context.Match(JsonPathToken.Comma));
|
||||||
|
|
||||||
|
return new ArrayIndexListSyntax(token, indexes.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonPathSyntax ObjectIndexExpression(Lexer<JsonPathToken>.Token token, Context context)
|
||||||
|
{
|
||||||
|
var index = context.Previous();
|
||||||
|
|
||||||
|
if (!context.Match(JsonPathToken.Comma)) return new ObjectIndexSyntax(token, index);
|
||||||
|
|
||||||
|
var indexes = new List<Lexer<JsonPathToken>.Token> { index };
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
index = context.Consume(JsonPathToken.String, "Invalid object index.");
|
||||||
|
indexes.Add(index);
|
||||||
|
} while (!context.Ended() && context.Match(JsonPathToken.Comma));
|
||||||
|
|
||||||
|
return new ObjectIndexListSyntax(token, indexes.ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
DevDisciples.Json.Tools/JsonPath.Syntax.cs
Normal file
96
DevDisciples.Json.Tools/JsonPath.Syntax.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using DevDisciples.Parsing;
|
||||||
|
|
||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
// See https://docs.hevodata.com/sources/engg-analytics/streaming/rest-api/writing-jsonpath-expressions/
|
||||||
|
|
||||||
|
public static partial class JsonPath
|
||||||
|
{
|
||||||
|
|
||||||
|
public interface IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct WildCardSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
|
||||||
|
public WildCardSyntax(Lexer<JsonPathToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ArrayIndexSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
public Lexer<JsonPathToken>.Token Index { get; }
|
||||||
|
public int IndexAsInt => int.Parse(Token.Lexeme);
|
||||||
|
|
||||||
|
public ArrayIndexSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token index)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ArrayIndexListSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
public Lexer<JsonPathToken>.Token[] Indices { get; }
|
||||||
|
public int ValueAt(int index) => int.Parse(Indices[index].Lexeme);
|
||||||
|
|
||||||
|
public ArrayIndexListSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token[] indices)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Indices = indices;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ObjectIndexSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
public Lexer<JsonPathToken>.Token Index { get; }
|
||||||
|
|
||||||
|
public ObjectIndexSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token index)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct ObjectIndexListSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
public Lexer<JsonPathToken>.Token[] Indexes { get; }
|
||||||
|
|
||||||
|
public ObjectIndexListSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token[] indexes)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Indexes = indexes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct PropertyAccessorSyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
public IJsonPathSyntax Getter { get; }
|
||||||
|
|
||||||
|
public PropertyAccessorSyntax(Lexer<JsonPathToken>.Token token, IJsonPathSyntax getter)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
Getter = getter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct PropertySyntax : IJsonPathSyntax
|
||||||
|
{
|
||||||
|
public Lexer<JsonPathToken>.Token Token { get; }
|
||||||
|
|
||||||
|
public PropertySyntax(Lexer<JsonPathToken>.Token token)
|
||||||
|
{
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
DevDisciples.Json.Tools/JsonPathToken.cs
Normal file
21
DevDisciples.Json.Tools/JsonPathToken.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace DevDisciples.Json.Tools;
|
||||||
|
|
||||||
|
public enum JsonPathToken
|
||||||
|
{
|
||||||
|
DollarSign,
|
||||||
|
Asterisk,
|
||||||
|
Dot,
|
||||||
|
DotDot,
|
||||||
|
LeftBracket,
|
||||||
|
RightBracket,
|
||||||
|
Identifier,
|
||||||
|
String,
|
||||||
|
Minus,
|
||||||
|
Number,
|
||||||
|
Comma,
|
||||||
|
Colon,
|
||||||
|
Null,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
EndOfSource,
|
||||||
|
}
|
10
DevDisciples.Parsing/Extensions/CastingExtensions.cs
Normal file
10
DevDisciples.Parsing/Extensions/CastingExtensions.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace DevDisciples.Parsing.Extensions;
|
||||||
|
|
||||||
|
public static class CastingExtensions
|
||||||
|
{
|
||||||
|
public static T As<T>(this object @object)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(@object);
|
||||||
|
return (T)@object;
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +0,0 @@
|
|||||||
namespace DevDisciples.Parsing;
|
|
||||||
|
|
||||||
public interface ISyntaxNode { }
|
|
@ -187,10 +187,8 @@ public abstract partial class Lexer<TToken> where TToken : Enum
|
|||||||
|
|
||||||
ProcessDigits(src);
|
ProcessDigits(src);
|
||||||
|
|
||||||
if (src.Peek() == '.' && IsDigit(src.Peek(1)))
|
if (IsDigit(src.Peek(1)) && src.Match('.'))
|
||||||
{
|
{
|
||||||
// Consume the "."
|
|
||||||
src.Advance();
|
|
||||||
ProcessDigits(src);
|
ProcessDigits(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,11 @@ public abstract class ParsableStream<T>
|
|||||||
|
|
||||||
protected ReadOnlySpan<T> Tokens => _tokens.Span;
|
protected ReadOnlySpan<T> Tokens => _tokens.Span;
|
||||||
|
|
||||||
public int Position { get; set; }
|
protected int Position { get; set; }
|
||||||
|
|
||||||
public T Current => Position < Tokens.Length ? Tokens[Position] : default!;
|
public T Current => Position < Tokens.Length ? Tokens[Position] : default!;
|
||||||
|
|
||||||
public ParsableStream(ReadOnlyMemory<T> tokens)
|
protected ParsableStream(ReadOnlyMemory<T> tokens)
|
||||||
{
|
{
|
||||||
_tokens = tokens;
|
_tokens = tokens;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where TToken : Enum
|
public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where TToken : Enum
|
||||||
{
|
{
|
||||||
protected readonly TToken _endOfSource;
|
private readonly TToken _endOfSource;
|
||||||
|
|
||||||
public ParserContext(Memory<Lexer<TToken>.Token> tokens, TToken endOfSource) : base(tokens)
|
protected ParserContext(Memory<Lexer<TToken>.Token> tokens, TToken endOfSource) : base(tokens)
|
||||||
{
|
{
|
||||||
_endOfSource = endOfSource;
|
_endOfSource = endOfSource;
|
||||||
}
|
}
|
||||||
@ -98,9 +98,4 @@ public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where T
|
|||||||
{
|
{
|
||||||
return new ParsingException(Report.FormatMessage(token, message));
|
return new ParsingException(Report.FormatMessage(token, message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Halt(Lexer<TToken>.Token token, string message)
|
|
||||||
{
|
|
||||||
throw new ParsingException(Report.FormatMessage(token, message));
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
public static class Report
|
public static class Report
|
||||||
{
|
{
|
||||||
public static Exception Error(ISourceLocation token, string message)
|
public static ParsingException Error(ISourceLocation token, string message)
|
||||||
{
|
{
|
||||||
return new ParsingException(FormatMessage(token, message));
|
return new ParsingException(FormatMessage(token, message));
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
namespace DevDisciples.Parsing;
|
namespace DevDisciples.Parsing;
|
||||||
|
|
||||||
public static class Visitor
|
public static class Visitor
|
||||||
{
|
{
|
||||||
public delegate void Visit(object visitee, params object[] args);
|
public delegate void Visit<TVisitee>(TVisitee visitee);
|
||||||
public delegate TOut Visit<TOut>(object visitee, params object[] args);
|
|
||||||
public delegate TOut Visit<TIn, TOut>(TIn visitee, params object[] args);
|
public delegate void Visit<TVisitee, TContext>(TVisitee visitee, TContext context);
|
||||||
|
|
||||||
|
public delegate TOut Visit<TVisitee, TContext, TOut>(TVisitee visitee, TContext context);
|
||||||
}
|
}
|
@ -1,42 +1,40 @@
|
|||||||
namespace DevDisciples.Parsing;
|
namespace DevDisciples.Parsing;
|
||||||
|
|
||||||
public class VisitorContainer
|
public class VisitorContainer<TBase>
|
||||||
{
|
{
|
||||||
private Dictionary<Type, Visitor.Visit> Visitors { get; } = new();
|
private Dictionary<Type, Visitor.Visit<TBase>> Visitors { get; } = new();
|
||||||
private Visitor.Visit Default { get; set; } = default!;
|
private Visitor.Visit<TBase> Default { get; set; } = default!;
|
||||||
|
|
||||||
public void Register<TVisitee>(Visitor.Visit visitor)
|
public void Register<T>(Visitor.Visit<TBase> visitor)
|
||||||
{
|
{
|
||||||
Visitors[typeof(TVisitee)] = visitor;
|
Visitors[typeof(T)] = visitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
public Visitor.Visit<TBase> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VisitorContainer<T>
|
public class VisitorContainer<TBase, TContext>
|
||||||
{
|
{
|
||||||
protected Dictionary<Type, Visitor.Visit<T>> Visitors { get; } = new();
|
private Dictionary<Type, Visitor.Visit<TBase, TContext>> Visitors { get; } = new();
|
||||||
public Visitor.Visit<T> Default { get; } = default!;
|
private Visitor.Visit<TBase, TContext> Default { get; set; } = default!;
|
||||||
|
|
||||||
|
public void Register<T>(Visitor.Visit<TBase, TContext> visitor)
|
||||||
public VisitorContainer<T> Register<TVisitee>(Visitor.Visit<T> visitor)
|
|
||||||
{
|
{
|
||||||
Visitors[typeof(TVisitee)] = visitor;
|
Visitors[typeof(T)] = visitor;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visitor.Visit<T> this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default;
|
public Visitor.Visit<TBase, TContext> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class VisitorContainer<TIn, TOut>
|
public class VisitorContainer<TBase, TContext, TResult>
|
||||||
{
|
{
|
||||||
protected Dictionary<Type, Visitor.Visit<TIn, TOut>> Visitors { get; } = new();
|
private Dictionary<Type, Visitor.Visit<TBase, TContext, TResult>> Visitors { get; } = new();
|
||||||
public Visitor.Visit<TIn, TOut> Default { get; } = default!;
|
private Visitor.Visit<TBase, TContext, TResult> Default { get; set; } = default!;
|
||||||
|
|
||||||
public void Register<TVisitee>(Visitor.Visit<TIn, TOut> visitor)
|
public void Register<T>(Visitor.Visit<TBase, TContext, TResult> visitor)
|
||||||
{
|
{
|
||||||
Visitors[typeof(TVisitee)] = visitor;
|
Visitors[typeof(T)] = visitor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Visitor.Visit<TIn, TOut> this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default;
|
public Visitor.Visit<TBase, TContext, TResult> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user