From 82a59eebd2f9195857951d134b998e9fc44d7a2d Mon Sep 17 00:00:00 2001 From: mdnapo Date: Sun, 22 Sep 2024 15:25:38 +0200 Subject: [PATCH] - Refactored code - Implemented basic JSON Path interpreter - Clean up --- .../JsonParserTests.cs | 60 ++++--- DevDisciples.Json.Parser/JsonArray.cs | 15 -- DevDisciples.Json.Parser/JsonBool.cs | 14 -- DevDisciples.Json.Parser/JsonNull.cs | 13 -- DevDisciples.Json.Parser/JsonNumber.cs | 14 -- .../JsonObject.Property.cs | 18 -- DevDisciples.Json.Parser/JsonObject.cs | 15 -- DevDisciples.Json.Parser/JsonParser.cs | 47 ++--- DevDisciples.Json.Parser/JsonString.cs | 14 -- .../Syntax/IJsonSyntax.cs | 8 + .../Syntax/JsonArraySyntax.cs | 15 ++ .../Syntax/JsonBoolSyntax.cs | 14 ++ .../Syntax/JsonNullSyntax.cs | 13 ++ .../Syntax/JsonNumberSyntax.cs | 14 ++ .../Syntax/JsonObjectSyntax.cs | 15 ++ .../Syntax/JsonPropertySyntax.cs | 16 ++ .../Syntax/JsonStringSyntax.cs | 14 ++ .../Controllers/TransformController.cs | 7 + .../Requests/Json2CsharpRequest.cs | 4 +- .../Requests/JsonPathRequest.cs | 6 + .../Requests/PrettifyRequest.cs | 2 +- .../src/app/app.routes.ts | 3 +- .../src/app/defaults.ts | 52 +++--- .../app/json-path/json-path.component.html | 13 ++ .../app/json-path/json-path.component.scss | 3 + .../app/json-path/json-path.component.spec.ts | 23 +++ .../src/app/json-path/json-path.component.ts | 69 ++++++++ .../src/app/json-transform.service.ts | 4 + .../json2csharp/json2-csharp.component.html | 2 +- .../app/json2csharp/json2-csharp.component.ts | 7 +- .../src/app/layout/layout.component.html | 1 + .../src/app/layout/layout.component.scss | 1 + .../src/app/prettify/prettify.component.html | 2 +- .../src/app/prettify/prettify.component.ts | 8 +- .../src/app/uglify/uglify.component.html | 2 +- .../src/app/uglify/uglify.component.ts | 6 +- .../Json2CSharpTranslator.Context.cs | 4 +- ...on2CSharpTranslator.JsonArrayTranslator.cs | 63 ------- ...son2CSharpTranslator.JsonBoolTranslator.cs | 18 -- ...son2CSharpTranslator.JsonNullTranslator.cs | 18 -- ...n2CSharpTranslator.JsonNumberTranslator.cs | 19 -- ...n2CSharpTranslator.JsonObjectTranslator.cs | 42 ----- ...n2CSharpTranslator.JsonStringTranslator.cs | 22 --- .../Json2CSharpTranslator.cs | 147 +++++++++++++-- DevDisciples.Json.Tools/JsonFormatter.cs | 67 +++---- .../JsonPath.Interpreter.Context.cs | 14 ++ .../JsonPath.Interpreter.cs | 167 ++++++++++++++++++ DevDisciples.Json.Tools/JsonPath.Lexer.cs | 38 ++++ .../JsonPath.Parser.Context.cs | 16 ++ DevDisciples.Json.Tools/JsonPath.Parser.cs | 110 ++++++++++++ DevDisciples.Json.Tools/JsonPath.Syntax.cs | 96 ++++++++++ DevDisciples.Json.Tools/JsonPathToken.cs | 21 +++ .../Extensions/CastingExtensions.cs | 10 ++ DevDisciples.Parsing/ISyntaxNode.cs | 3 - DevDisciples.Parsing/Lexer.Rule.cs | 4 +- DevDisciples.Parsing/ParsableStream.cs | 4 +- DevDisciples.Parsing/ParserContext.cs | 9 +- DevDisciples.Parsing/Report.cs | 2 +- DevDisciples.Parsing/Visitor.cs | 10 +- DevDisciples.Parsing/VisitorContainer.cs | 40 ++--- 60 files changed, 1003 insertions(+), 475 deletions(-) delete mode 100644 DevDisciples.Json.Parser/JsonArray.cs delete mode 100644 DevDisciples.Json.Parser/JsonBool.cs delete mode 100644 DevDisciples.Json.Parser/JsonNull.cs delete mode 100644 DevDisciples.Json.Parser/JsonNumber.cs delete mode 100644 DevDisciples.Json.Parser/JsonObject.Property.cs delete mode 100644 DevDisciples.Json.Parser/JsonObject.cs delete mode 100644 DevDisciples.Json.Parser/JsonString.cs create mode 100644 DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs create mode 100644 DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs create mode 100644 DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs create mode 100644 DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.html create mode 100644 DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.scss create mode 100644 DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.spec.ts create mode 100644 DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.ts delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonArrayTranslator.cs delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonBoolTranslator.cs delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNullTranslator.cs delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNumberTranslator.cs delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonObjectTranslator.cs delete mode 100644 DevDisciples.Json.Tools/Json2CSharpTranslator.JsonStringTranslator.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Interpreter.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Lexer.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Parser.Context.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Parser.cs create mode 100644 DevDisciples.Json.Tools/JsonPath.Syntax.cs create mode 100644 DevDisciples.Json.Tools/JsonPathToken.cs create mode 100644 DevDisciples.Parsing/Extensions/CastingExtensions.cs delete mode 100644 DevDisciples.Parsing/ISyntaxNode.cs diff --git a/DevDisciples.Json.Parser.Tests/JsonParserTests.cs b/DevDisciples.Json.Parser.Tests/JsonParserTests.cs index 5663ebe..8c9bdf8 100644 --- a/DevDisciples.Json.Parser.Tests/JsonParserTests.cs +++ b/DevDisciples.Json.Parser.Tests/JsonParserTests.cs @@ -1,4 +1,6 @@ -namespace DevDisciples.Json.Parser.Tests; +using DevDisciples.Json.Parser.Syntax; + +namespace DevDisciples.Json.Parser.Tests; public class JsonParserTests { @@ -12,7 +14,7 @@ public class JsonParserTests { const string source = "null"; var node = JsonParser.Parse(nameof(Can_parse_null), source); - Assert.That(node is JsonNull); + Assert.That(node is JsonNullSyntax); } [Test] @@ -25,8 +27,8 @@ public class JsonParserTests Assert.Multiple(() => { - Assert.That(trueNode is JsonBool { Value: true }); - Assert.That(falseNode is JsonBool { Value: false }); + Assert.That(trueNode is JsonBoolSyntax { Value: true }); + Assert.That(falseNode is JsonBoolSyntax { Value: false }); }); } @@ -35,15 +37,15 @@ public class JsonParserTests { const string source = "1"; 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] - public void Can_lex_a_decimal() + public void Can_parse_a_decimal() { const string source = "1.0"; - var node = JsonParser.Parse(nameof(Can_lex_a_decimal), source); - Assert.That(node is JsonNumber { Value: 1.0 }); + var node = JsonParser.Parse(nameof(Can_parse_a_decimal), source); + Assert.That(node is JsonNumberSyntax { Value: 1.0 }); } [Test] @@ -51,7 +53,7 @@ public class JsonParserTests { const string source = "\"Hello world!\""; 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] @@ -59,7 +61,7 @@ public class JsonParserTests { const string source = "[]"; var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_array), source); - Assert.That(tokens is JsonArray); + Assert.That(tokens is JsonArraySyntax); } [Test] @@ -68,9 +70,9 @@ public class JsonParserTests const string source = "[1]"; 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]"; var node = JsonParser.Parse(nameof(Can_parse_an_array_with_multiple_items), source); - Assert.That(node is JsonArray + Assert.That(node is JsonArraySyntax { Elements: [ - JsonNumber { Value: 1 }, - JsonBool { Value: true }, - JsonObject { Properties.Length: 0 }, - JsonArray { Elements.Length: 0 }, - JsonNull + JsonNumberSyntax { Value: 1 }, + JsonBoolSyntax { Value: true }, + JsonObjectSyntax { Properties.Length: 0 }, + JsonArraySyntax { Elements.Length: 0 }, + JsonNullSyntax ] }); } @@ -98,7 +100,7 @@ public class JsonParserTests { const string 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] @@ -107,10 +109,10 @@ public class JsonParserTests const string source = "{\"first_name\":\"John\"}"; var node = JsonParser.Parse(nameof(Can_parse_an_object_with_one_entry), source); - Assert.That(node is JsonObject { Properties.Length: 1 }); - var @object = (JsonObject)node; - Assert.That(@object.Properties.Any(property => property.Key == "first_name")); - Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" }); + Assert.That(node is JsonObjectSyntax { Properties.Length: 1 }); + var @object = (JsonObjectSyntax)node; + Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name")); + Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" }); } [Test] @@ -121,12 +123,12 @@ public class JsonParserTests Assert.Multiple(() => { - Assert.That(node is JsonObject { Properties.Length: 2 }); - var @object = (JsonObject)node; - Assert.That(@object.Properties.Any(property => property.Key == "first_name")); - Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" }); - Assert.That(@object.Properties.Any(property => property.Key == "last_name")); - Assert.That(@object.Properties.First(property => property.Key == "last_name").Value is JsonString { Value: "Doe" }); + Assert.That(node is JsonObjectSyntax { Properties.Length: 2 }); + var @object = (JsonObjectSyntax)node; + Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name")); + Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" }); + Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "last_name")); + Assert.That(@object.Properties.First(property => property.Key.Lexeme == "last_name").Value is JsonStringSyntax { Value: "Doe" }); }); } } \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonArray.cs b/DevDisciples.Json.Parser/JsonArray.cs deleted file mode 100644 index c534136..0000000 --- a/DevDisciples.Json.Parser/JsonArray.cs +++ /dev/null @@ -1,15 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly struct JsonArray : ISyntaxNode -{ - public Lexer.Token Token { get; } - public ISyntaxNode[] Elements { get; } - - public JsonArray(Lexer.Token token, ISyntaxNode[] elements) - { - Token = token; - Elements = elements; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonBool.cs b/DevDisciples.Json.Parser/JsonBool.cs deleted file mode 100644 index 9dfff08..0000000 --- a/DevDisciples.Json.Parser/JsonBool.cs +++ /dev/null @@ -1,14 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly struct JsonBool : ISyntaxNode -{ - public Lexer.Token Token { get; } - public bool Value => bool.TryParse(Token.Lexeme, out var val) && val; - - public JsonBool(Lexer.Token token) - { - Token = token; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonNull.cs b/DevDisciples.Json.Parser/JsonNull.cs deleted file mode 100644 index fa662c3..0000000 --- a/DevDisciples.Json.Parser/JsonNull.cs +++ /dev/null @@ -1,13 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly struct JsonNull : ISyntaxNode -{ - public Lexer.Token Token { get; } - - public JsonNull(Lexer.Token token) - { - Token = token; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonNumber.cs b/DevDisciples.Json.Parser/JsonNumber.cs deleted file mode 100644 index bc8e7cc..0000000 --- a/DevDisciples.Json.Parser/JsonNumber.cs +++ /dev/null @@ -1,14 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly struct JsonNumber : ISyntaxNode -{ - public Lexer.Token Token { get; } - public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default; - - public JsonNumber(Lexer.Token token) - { - Token = token; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonObject.Property.cs b/DevDisciples.Json.Parser/JsonObject.Property.cs deleted file mode 100644 index d6981f7..0000000 --- a/DevDisciples.Json.Parser/JsonObject.Property.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonObject.cs b/DevDisciples.Json.Parser/JsonObject.cs deleted file mode 100644 index 6b42d25..0000000 --- a/DevDisciples.Json.Parser/JsonObject.cs +++ /dev/null @@ -1,15 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly partial struct JsonObject : ISyntaxNode -{ - public Lexer.Token Token { get; } - public Property[] Properties { get; } - - public JsonObject(Lexer.Token token, Property[] properties) - { - Token = token; - Properties = properties; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonParser.cs b/DevDisciples.Json.Parser/JsonParser.cs index e9f8872..83f3110 100644 --- a/DevDisciples.Json.Parser/JsonParser.cs +++ b/DevDisciples.Json.Parser/JsonParser.cs @@ -1,10 +1,11 @@ -using DevDisciples.Parsing; +using DevDisciples.Json.Parser.Syntax; +using DevDisciples.Parsing; namespace DevDisciples.Json.Parser; 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 context = new Context(tokens); @@ -12,33 +13,33 @@ public static partial class JsonParser return nodes; } - private static ISyntaxNode Expression(ParserContext ctx) + private static IJsonSyntax Expression(ParserContext ctx) { if (ctx.Match(JsonToken.LeftBracket)) - return Array(ctx); + return ArrayExpression(ctx); if (ctx.Match(JsonToken.LeftBrace)) - return Object(ctx); + return ObjectExpression(ctx); if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number)) - return Number(ctx); + return NumberExpression(ctx); if (ctx.Match(JsonToken.String)) - return String(ctx); + return StringExpression(ctx); if (ctx.Match(JsonToken.Null)) - return Null(ctx); + return NullExpression(ctx); 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}'"); } - private static ISyntaxNode Array(ParserContext ctx) + private static IJsonSyntax ArrayExpression(ParserContext ctx) { var previous = ctx.Previous(); - List? elements = null; + List? elements = null; if (!ctx.Check(JsonToken.RightBracket)) { @@ -51,13 +52,13 @@ public static partial class JsonParser ctx.Consume(JsonToken.RightBracket, "Expected ']'"); - return new JsonArray(previous, elements?.ToArray() ?? System.Array.Empty()); + return new JsonArraySyntax(previous, elements?.ToArray() ?? System.Array.Empty()); } - private static ISyntaxNode Object(ParserContext ctx) + private static IJsonSyntax ObjectExpression(ParserContext ctx) { var previous = ctx.Previous(); - Dictionary? properties = null; + Dictionary.Token, IJsonSyntax>? properties = null; if (!ctx.Check(JsonToken.RightBrace)) { @@ -66,32 +67,32 @@ public static partial class JsonParser var key = ctx.Consume(JsonToken.String, "Expected property name"); ctx.Consume(JsonToken.Colon, "Expected ':' after property name"); properties ??= new(); - properties[key.Lexeme] = Expression(ctx); + properties[key] = Expression(ctx); } while (ctx.Match(JsonToken.Comma)); } 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()); + return new JsonObjectSyntax(previous, propertiesArray ?? System.Array.Empty()); } - private static ISyntaxNode Number(ParserContext ctx) + private static IJsonSyntax NumberExpression(ParserContext 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 number = ctx.Consume(JsonToken.Number, "Expected a number after '-'."); - return new JsonNumber( + return new JsonNumberSyntax( new Lexer.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column) ); } - private static ISyntaxNode String(ParserContext ctx) => new JsonString(ctx.Previous()); + private static IJsonSyntax StringExpression(ParserContext ctx) => new JsonStringSyntax(ctx.Previous()); - private static ISyntaxNode Null(ParserContext ctx) => new JsonNull(ctx.Previous()); + private static IJsonSyntax NullExpression(ParserContext ctx) => new JsonNullSyntax(ctx.Previous()); - private static ISyntaxNode Bool(ParserContext ctx) => new JsonBool(ctx.Previous()); + private static IJsonSyntax BoolExpression(ParserContext ctx) => new JsonBoolSyntax(ctx.Previous()); } \ No newline at end of file diff --git a/DevDisciples.Json.Parser/JsonString.cs b/DevDisciples.Json.Parser/JsonString.cs deleted file mode 100644 index 9541644..0000000 --- a/DevDisciples.Json.Parser/JsonString.cs +++ /dev/null @@ -1,14 +0,0 @@ -using DevDisciples.Parsing; - -namespace DevDisciples.Json.Parser; - -public readonly struct JsonString : ISyntaxNode -{ - public Lexer.Token Token { get; } - public string Value => Token.Lexeme; - - public JsonString(Lexer.Token token) - { - Token = token; - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs b/DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs new file mode 100644 index 0000000..55ea254 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs @@ -0,0 +1,8 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public interface IJsonSyntax +{ + public Lexer.Token Token { get; } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs new file mode 100644 index 0000000..a837094 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs @@ -0,0 +1,15 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonArraySyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + public IJsonSyntax[] Elements { get; } + + public JsonArraySyntax(Lexer.Token token, IJsonSyntax[] elements) + { + Token = token; + Elements = elements; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs new file mode 100644 index 0000000..e0cbb77 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs @@ -0,0 +1,14 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonBoolSyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + public bool Value => bool.TryParse(Token.Lexeme, out var val) && val; + + public JsonBoolSyntax(Lexer.Token token) + { + Token = token; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs new file mode 100644 index 0000000..9e0cf9b --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs @@ -0,0 +1,13 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonNullSyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + + public JsonNullSyntax(Lexer.Token token) + { + Token = token; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs new file mode 100644 index 0000000..f62f3f8 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs @@ -0,0 +1,14 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonNumberSyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default; + + public JsonNumberSyntax(Lexer.Token token) + { + Token = token; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs new file mode 100644 index 0000000..0e555d1 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs @@ -0,0 +1,15 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly partial struct JsonObjectSyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + public JsonPropertySyntax[] Properties { get; } + + public JsonObjectSyntax(Lexer.Token token, JsonPropertySyntax[] properties) + { + Token = token; + Properties = properties; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs new file mode 100644 index 0000000..a0a2951 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs @@ -0,0 +1,16 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonPropertySyntax : IJsonSyntax +{ + public Lexer.Token Token => Key; + public Lexer.Token Key { get; } + public IJsonSyntax Value { get; } + + public JsonPropertySyntax(Lexer.Token key, IJsonSyntax value) + { + Key = key; + Value = value; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs b/DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs new file mode 100644 index 0000000..fccf945 --- /dev/null +++ b/DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs @@ -0,0 +1,14 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Parser.Syntax; + +public readonly struct JsonStringSyntax : IJsonSyntax +{ + public Lexer.Token Token { get; } + public string Value => Token.Lexeme; + + public JsonStringSyntax(Lexer.Token token) + { + Token = token; + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools.API/Controllers/TransformController.cs b/DevDisciples.Json.Tools.API/Controllers/TransformController.cs index 8578190..1d0ac0f 100644 --- a/DevDisciples.Json.Tools.API/Controllers/TransformController.cs +++ b/DevDisciples.Json.Tools.API/Controllers/TransformController.cs @@ -40,4 +40,11 @@ public class TransformController : ControllerBase 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 }) }); + } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.API/Requests/Json2CsharpRequest.cs b/DevDisciples.Json.Tools.API/Requests/Json2CsharpRequest.cs index 96dee83..5a2408e 100644 --- a/DevDisciples.Json.Tools.API/Requests/Json2CsharpRequest.cs +++ b/DevDisciples.Json.Tools.API/Requests/Json2CsharpRequest.cs @@ -2,6 +2,6 @@ public class Json2CsharpRequest : TransformRequest { - public string RootClassName { get; } = Json2CSharpTranslator.Context.DefaultRootClassName; - public string Namespace { get; } = Json2CSharpTranslator.Context.DefaultNamespace; + public string RootClassName { get; init; } = Json2CSharpTranslator.Context.DefaultRootClassName; + public string Namespace { get; init; } = Json2CSharpTranslator.Context.DefaultNamespace; } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs b/DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs new file mode 100644 index 0000000..ca2fea7 --- /dev/null +++ b/DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs @@ -0,0 +1,6 @@ +namespace DevDisciples.Json.Tools.API.Requests; + +public class JsonPathRequest : TransformRequest +{ + public string Path { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs b/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs index f973d0f..dbd8875 100644 --- a/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs +++ b/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs @@ -2,5 +2,5 @@ public class PrettifyRequest : TransformRequest { - public int IndentSize { get; } = JsonFormatter.Context.DefaultIndentSize; + public int IndentSize { get; init; } = JsonFormatter.Context.DefaultIndentSize; } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.App/src/app/app.routes.ts b/DevDisciples.Json.Tools.App/src/app/app.routes.ts index 3c83f97..132ba24 100644 --- a/DevDisciples.Json.Tools.App/src/app/app.routes.ts +++ b/DevDisciples.Json.Tools.App/src/app/app.routes.ts @@ -1,13 +1,14 @@ import { Routes } from '@angular/router'; -import {AppComponent} from "./app.component"; import {HomeComponent} from "./home/home.component"; import {PrettifyComponent} from "./prettify/prettify.component"; import {UglifyComponent} from "./uglify/uglify.component"; import {Json2CsharpComponent} from "./json2csharp/json2-csharp.component"; +import {JsonPathComponent} from "./json-path/json-path.component"; export const routes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'prettify', component: PrettifyComponent }, { path: 'uglify', component: UglifyComponent }, { path: 'json2csharp', component: Json2CsharpComponent }, + { path: 'jsonpath', component: JsonPathComponent }, ]; diff --git a/DevDisciples.Json.Tools.App/src/app/defaults.ts b/DevDisciples.Json.Tools.App/src/app/defaults.ts index 61a22bb..a4c526a 100644 --- a/DevDisciples.Json.Tools.App/src/app/defaults.ts +++ b/DevDisciples.Json.Tools.App/src/app/defaults.ts @@ -1,26 +1,26 @@ -export const MonacoJsonConfig = { - theme: 'vs-dark', - language: 'json', - readOnly: false, - automaticLayout: true -}; - -export const ReadOnlyMonacoJsonConfig = { - theme: 'vs-dark', - language: 'json', - readOnly: true, - automaticLayout: true -}; - -export const ReadOnlyMonacoCSharpConfig = { - theme: 'vs-dark', - language: 'csharp', - readOnly: false, - automaticLayout: true -}; - -export const GenerateDefaultJsonObjectString = (space: number = 0): string => { - return JSON.stringify({first_name: "John", last_name: "Doe"}, null, space); -} - -export const DebounceTime: number = 750; +export const MonacoJsonConfig = { + theme: 'vs-dark', + language: 'json', + readOnly: false, + automaticLayout: true +}; + +export const ReadOnlyMonacoJsonConfig = { + theme: 'vs-dark', + language: 'json', + readOnly: true, + automaticLayout: true +}; + +export const ReadOnlyMonacoCSharpConfig = { + theme: 'vs-dark', + language: 'csharp', + readOnly: false, + automaticLayout: true +}; + +export const GenerateDefaultJsonObjectString = (space: number = 0): string => { + return JSON.stringify({first_name: "John", last_name: "Doe"}, null, space); +} + +export const DebounceTime: number = 750; diff --git a/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.html b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.html new file mode 100644 index 0000000..50ae559 --- /dev/null +++ b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.html @@ -0,0 +1,13 @@ +

JSON to C#

+ + + Path + + + + + diff --git a/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.scss b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.scss new file mode 100644 index 0000000..84de0ab --- /dev/null +++ b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} diff --git a/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.spec.ts b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.spec.ts new file mode 100644 index 0000000..b6754c7 --- /dev/null +++ b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.spec.ts @@ -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; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [JsonPathComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(JsonPathComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.ts b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.ts new file mode 100644 index 0000000..817f547 --- /dev/null +++ b/DevDisciples.Json.Tools.App/src/app/json-path/json-path.component.ts @@ -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 = new BehaviorSubject("$.*"); + $input: BehaviorSubject = new BehaviorSubject(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); + } +} diff --git a/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts b/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts index 35d86a6..5f8dcae 100644 --- a/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts +++ b/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts @@ -22,4 +22,8 @@ export class JsonTransformService { public json2csharp(source: string): Observable> { return this.http.post(`${this.url}/api/transform/json2csharp`, {Source: source}, {observe: "response"}); } + + public jsonPath(source: string, path: string): Observable> { + return this.http.post(`${this.url}/api/transform/jsonpath`, {source: source, path: path}, {observe: "response"}); + } } diff --git a/DevDisciples.Json.Tools.App/src/app/json2csharp/json2-csharp.component.html b/DevDisciples.Json.Tools.App/src/app/json2csharp/json2-csharp.component.html index 671f43c..302bc6d 100644 --- a/DevDisciples.Json.Tools.App/src/app/json2csharp/json2-csharp.component.html +++ b/DevDisciples.Json.Tools.App/src/app/json2csharp/json2-csharp.component.html @@ -1,6 +1,6 @@

JSON to C#

- = new Subject(); + $input: BehaviorSubject = new BehaviorSubject(GenerateDefaultJsonObjectString(2)); inputOptions = MonacoJsonConfig; output: string = ""; outputOptions = ReadOnlyMonacoCSharpConfig; @@ -33,7 +32,7 @@ export class Json2CsharpComponent implements OnInit { .pipe(debounceTime(DebounceTime)) .subscribe(input => this.update(input)); - this.update(this.input); + this.update(this.$input.value); } update(input: string): void { diff --git a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html index 95eee7e..32aa4f2 100644 --- a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html +++ b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html @@ -6,6 +6,7 @@ Prettify Uglify JSON to C# + JSON Path diff --git a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.scss b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.scss index f72087b..8ef004f 100644 --- a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.scss +++ b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.scss @@ -12,4 +12,5 @@ mat-sidenav { main { height: 100%; + padding: 20px; } diff --git a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html b/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html index 0695bab..f4491c3 100644 --- a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html +++ b/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html @@ -1,6 +1,6 @@

JSON Prettify

- = new Subject(); + // input: string = ; + $input: BehaviorSubject = new BehaviorSubject(GenerateDefaultJsonObjectString()); inputOptions = MonacoJsonConfig; output: string = GenerateDefaultJsonObjectString(2); outputOptions = ReadOnlyMonacoJsonConfig; - error: string = ""; + // error: string = ""; constructor(private service: JsonTransformService) { } diff --git a/DevDisciples.Json.Tools.App/src/app/uglify/uglify.component.html b/DevDisciples.Json.Tools.App/src/app/uglify/uglify.component.html index deda139..55fa6ab 100644 --- a/DevDisciples.Json.Tools.App/src/app/uglify/uglify.component.html +++ b/DevDisciples.Json.Tools.App/src/app/uglify/uglify.component.html @@ -1,6 +1,6 @@

JSON Uglify

- = new Subject(); + // input: string = ; + $input: BehaviorSubject = new BehaviorSubject(GenerateDefaultJsonObjectString(2)); inputOptions = MonacoJsonConfig; output: string = GenerateDefaultJsonObjectString(); outputOptions = ReadOnlyMonacoJsonConfig; diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.Context.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.Context.cs index 7e7848d..a37b763 100644 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.Context.cs +++ b/DevDisciples.Json.Tools/Json2CSharpTranslator.Context.cs @@ -8,9 +8,11 @@ public static partial class Json2CSharpTranslator { public const string DefaultRootClassName = "Root"; public const string DefaultNamespace = "My.Namespace"; - + public string RootClassName { get; init; } = DefaultRootClassName; public string Namespace { get; init; } = DefaultNamespace; + public string CurrentName { get; set; } = string.Empty; + public List Classes { get; } = new(); public readonly StringBuilder Builder = new(); } diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonArrayTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonArrayTranslator.cs deleted file mode 100644 index 604fb90..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonArrayTranslator.cs +++ /dev/null @@ -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 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; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonBoolTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonBoolTranslator.cs deleted file mode 100644 index b4b31f6..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonBoolTranslator.cs +++ /dev/null @@ -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), - }; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNullTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNullTranslator.cs deleted file mode 100644 index 1930638..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNullTranslator.cs +++ /dev/null @@ -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), - }; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNumberTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNumberTranslator.cs deleted file mode 100644 index 47abceb..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonNumberTranslator.cs +++ /dev/null @@ -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), - }; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonObjectTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonObjectTranslator.cs deleted file mode 100644 index b070f1e..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonObjectTranslator.cs +++ /dev/null @@ -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; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonStringTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonStringTranslator.cs deleted file mode 100644 index a526d39..0000000 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.JsonStringTranslator.cs +++ /dev/null @@ -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), - }; - } - } -} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.cs index d0eddd4..68a3375 100644 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.cs +++ b/DevDisciples.Json.Tools/Json2CSharpTranslator.cs @@ -1,4 +1,5 @@ using DevDisciples.Json.Parser; +using DevDisciples.Json.Parser.Syntax; using DevDisciples.Parsing; using Humanizer; @@ -6,28 +7,28 @@ namespace DevDisciples.Json.Tools; public static partial class Json2CSharpTranslator { - public static readonly VisitorContainer Visitors; + private static readonly VisitorContainer Visitors = new(); static Json2CSharpTranslator() { - Visitors = new(); - Visitors.Register(JsonObjectTranslator.Translate); - Visitors.Register(JsonArrayTranslator.Translate); - Visitors.Register(JsonStringTranslator.Translate); - Visitors.Register(JsonNumberTranslator.Translate); - Visitors.Register(JsonBoolTranslator.Translate); - Visitors.Register(JsonNullTranslator.Translate); + Visitors.Register(Object); + Visitors.Register(Array); + Visitors.Register(String); + Visitors.Register(Number); + Visitors.Register(Bool); + Visitors.Register(Null); } public static string Translate(string source, Context? context = null) { - if (JsonParser.Parse("", source) is not JsonObject root) + if (JsonParser.Parse("", source) is not JsonObjectSyntax root) throw new ParsingException("Expected a JSON object."); context ??= new(); + context.CurrentName = context.RootClassName; - var visitor = Visitors[typeof(JsonObject)]; - visitor(root, context, context.RootClassName); + var visitor = Visitors[typeof(JsonObjectSyntax)]; + visitor(root, context); context.Builder.Append("//using System;\n"); context.Builder.Append("//using System.Collections.Generic;\n"); @@ -38,7 +39,127 @@ public static partial class Json2CSharpTranslator 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 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; + } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonFormatter.cs b/DevDisciples.Json.Tools/JsonFormatter.cs index 786efd8..5946153 100644 --- a/DevDisciples.Json.Tools/JsonFormatter.cs +++ b/DevDisciples.Json.Tools/JsonFormatter.cs @@ -1,45 +1,39 @@ using DevDisciples.Json.Parser; +using DevDisciples.Json.Parser.Syntax; using DevDisciples.Parsing; namespace DevDisciples.Json.Tools; public static partial class JsonFormatter { - public static VisitorContainer Visitors { get; } + private static VisitorContainer Visitors { get; } = new(); static JsonFormatter() { - Visitors = new(); - Visitors.Register(PrintArray); - Visitors.Register(PrintObject); - Visitors.Register(PrintString); - Visitors.Register(PrintNumber); - Visitors.Register(PrintBool); - Visitors.Register(PrintNull); + Visitors.Register(PrintArray); + Visitors.Register(PrintObject); + Visitors.Register(PrintString); + Visitors.Register(PrintNumber); + Visitors.Register(PrintBool); + Visitors.Register(PrintNull); } public static string Format(string source, Context? context) { - var nodes = JsonParser.Parse("", source); - return Format(nodes, context); + var node = JsonParser.Parse("", source); + 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(); - Visitors[visitee.GetType()](visitee, ctx); - return context!.Builder.ToString(); + context ??= new(); + Visitors[visitee.GetType()](visitee, context); + return context.Builder.ToString(); } - private static Context ContextFromArgs(object[] args) + private static void PrintArray(IJsonSyntax visitee, Context context) { - return (args[0] as Context)!; - } - - private static void PrintArray(object visitee, object[] args) - { - var context = ContextFromArgs(args); - var array = (JsonArray)visitee; + var array = (JsonArraySyntax)visitee; context.Builder.Append($"[{context.NewLine}"); context.IncrementDepth(); @@ -48,7 +42,7 @@ public static partial class JsonFormatter { var node = array.Elements[i]; 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}"); } @@ -56,10 +50,9 @@ public static partial class JsonFormatter 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 = (JsonObject)visitee; + var @object = (JsonObjectSyntax)visitee; context.Builder.Append($"{{{context.NewLine}"); context.IncrementDepth(); @@ -68,8 +61,8 @@ public static partial class JsonFormatter for (var i = 0; i < count; i++) { var property = @object.Properties.ElementAt(i); - context.Builder.Append($"{context.Indent}\"{property.Key}\":{context.Space}"); - Visitors[property.Value.GetType()](property.Value, args); + context.Builder.Append($"{context.Indent}\"{property.Key.Lexeme}\":{context.Space}"); + Visitors[property.Value.GetType()](property.Value, context); 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}}}"); } - private static void PrintString(object visitee, object[] args) + private static void PrintString(IJsonSyntax visitee, Context context) { - var context = ContextFromArgs(args); - var @string = (JsonString)visitee; + var @string = (JsonStringSyntax)visitee; 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 = (JsonNumber)visitee; + var number = (JsonNumberSyntax)visitee; 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 = (JsonBool)visitee; + var @bool = (JsonBoolSyntax)visitee; 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"); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs b/DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs new file mode 100644 index 0000000..3a297fc --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs @@ -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!; + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Interpreter.cs b/DevDisciples.Json.Tools/JsonPath.Interpreter.cs new file mode 100644 index 0000000..511e814 --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Interpreter.cs @@ -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 Visitors = new(); + + static Interpreter() + { + Visitors.Register(WildCardExpression); + Visitors.Register(PropertyAccessorExpression); + Visitors.Register(PropertyExpression); + Visitors.Register(ArrayIndexExpression); + Visitors.Register(ArrayIndexListExpression); + Visitors.Register(ObjectIndexExpression); + Visitors.Register(ObjectIndexListExpression); + } + + public static IJsonSyntax Evaluate(string source, string path) + { + var root = JsonParser.Parse("", 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(); + throw Report.Error(syntax.Token, "Invalid target for '*'."); + } + } + + private static void PropertyAccessorExpression(IJsonPathSyntax visitee, Context context) + { + var accessor = visitee.As(); + Evaluate(accessor.Getter, context); + } + + private static void PropertyExpression(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 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(); + + 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(); + + if (context.Target is not JsonArraySyntax array) + throw Report.Error(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 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(); + + 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(); + + 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()); + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Lexer.cs b/DevDisciples.Json.Tools/JsonPath.Lexer.cs new file mode 100644 index 0000000..b210112 --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Lexer.cs @@ -0,0 +1,38 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Tools; + +public static partial class JsonPath +{ + public class Lexer : Lexer + { + 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, ']'), + ]; + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Parser.Context.cs b/DevDisciples.Json.Tools/JsonPath.Parser.Context.cs new file mode 100644 index 0000000..96e2a71 --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Parser.Context.cs @@ -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 + { + public Context(Memory.Token> tokens) : base(tokens, JsonPathToken.EndOfSource) + { + } + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Parser.cs b/DevDisciples.Json.Tools/JsonPath.Parser.cs new file mode 100644 index 0000000..3126a32 --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Parser.cs @@ -0,0 +1,110 @@ +using DevDisciples.Parsing; + +namespace DevDisciples.Json.Tools; + +public static partial class JsonPath +{ + public static partial class Parser + { + public static List Parse(string path) + { + var pathTokens = Lexer.Default.Lex("", 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(); + 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.Token token, Context context) + { + var index = context.Previous(); + + if (!context.Match(JsonPathToken.Comma)) return new ArrayIndexSyntax(token, index); + + var indexes = new List.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.Token token, Context context) + { + var index = context.Previous(); + + if (!context.Match(JsonPathToken.Comma)) return new ObjectIndexSyntax(token, index); + + var indexes = new List.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()); + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPath.Syntax.cs b/DevDisciples.Json.Tools/JsonPath.Syntax.cs new file mode 100644 index 0000000..d6e281d --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPath.Syntax.cs @@ -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.Token Token { get; } + } + + public readonly struct WildCardSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + + public WildCardSyntax(Lexer.Token token) + { + Token = token; + } + } + + public readonly struct ArrayIndexSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + public Lexer.Token Index { get; } + public int IndexAsInt => int.Parse(Token.Lexeme); + + public ArrayIndexSyntax(Lexer.Token token, Lexer.Token index) + { + Token = token; + Index = index; + } + } + + public readonly struct ArrayIndexListSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + public Lexer.Token[] Indices { get; } + public int ValueAt(int index) => int.Parse(Indices[index].Lexeme); + + public ArrayIndexListSyntax(Lexer.Token token, Lexer.Token[] indices) + { + Token = token; + Indices = indices; + } + } + + public readonly struct ObjectIndexSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + public Lexer.Token Index { get; } + + public ObjectIndexSyntax(Lexer.Token token, Lexer.Token index) + { + Token = token; + Index = index; + } + } + + public readonly struct ObjectIndexListSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + public Lexer.Token[] Indexes { get; } + + public ObjectIndexListSyntax(Lexer.Token token, Lexer.Token[] indexes) + { + Token = token; + Indexes = indexes; + } + } + + public readonly struct PropertyAccessorSyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + public IJsonPathSyntax Getter { get; } + + public PropertyAccessorSyntax(Lexer.Token token, IJsonPathSyntax getter) + { + Token = token; + Getter = getter; + } + } + + public readonly struct PropertySyntax : IJsonPathSyntax + { + public Lexer.Token Token { get; } + + public PropertySyntax(Lexer.Token token) + { + Token = token; + } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools/JsonPathToken.cs b/DevDisciples.Json.Tools/JsonPathToken.cs new file mode 100644 index 0000000..8f80190 --- /dev/null +++ b/DevDisciples.Json.Tools/JsonPathToken.cs @@ -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, +} \ No newline at end of file diff --git a/DevDisciples.Parsing/Extensions/CastingExtensions.cs b/DevDisciples.Parsing/Extensions/CastingExtensions.cs new file mode 100644 index 0000000..ed9f998 --- /dev/null +++ b/DevDisciples.Parsing/Extensions/CastingExtensions.cs @@ -0,0 +1,10 @@ +namespace DevDisciples.Parsing.Extensions; + +public static class CastingExtensions +{ + public static T As(this object @object) + { + ArgumentNullException.ThrowIfNull(@object); + return (T)@object; + } +} \ No newline at end of file diff --git a/DevDisciples.Parsing/ISyntaxNode.cs b/DevDisciples.Parsing/ISyntaxNode.cs deleted file mode 100644 index 8080922..0000000 --- a/DevDisciples.Parsing/ISyntaxNode.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace DevDisciples.Parsing; - -public interface ISyntaxNode { } \ No newline at end of file diff --git a/DevDisciples.Parsing/Lexer.Rule.cs b/DevDisciples.Parsing/Lexer.Rule.cs index f5c4f8f..ca62fa3 100644 --- a/DevDisciples.Parsing/Lexer.Rule.cs +++ b/DevDisciples.Parsing/Lexer.Rule.cs @@ -187,10 +187,8 @@ public abstract partial class Lexer where TToken : Enum ProcessDigits(src); - if (src.Peek() == '.' && IsDigit(src.Peek(1))) + if (IsDigit(src.Peek(1)) && src.Match('.')) { - // Consume the "." - src.Advance(); ProcessDigits(src); } diff --git a/DevDisciples.Parsing/ParsableStream.cs b/DevDisciples.Parsing/ParsableStream.cs index 1fbbc02..063a976 100644 --- a/DevDisciples.Parsing/ParsableStream.cs +++ b/DevDisciples.Parsing/ParsableStream.cs @@ -6,11 +6,11 @@ public abstract class ParsableStream protected ReadOnlySpan Tokens => _tokens.Span; - public int Position { get; set; } + protected int Position { get; set; } public T Current => Position < Tokens.Length ? Tokens[Position] : default!; - public ParsableStream(ReadOnlyMemory tokens) + protected ParsableStream(ReadOnlyMemory tokens) { _tokens = tokens; } diff --git a/DevDisciples.Parsing/ParserContext.cs b/DevDisciples.Parsing/ParserContext.cs index 92c1aae..e8200fd 100644 --- a/DevDisciples.Parsing/ParserContext.cs +++ b/DevDisciples.Parsing/ParserContext.cs @@ -2,9 +2,9 @@ public class ParserContext : ParsableStream.Token> where TToken : Enum { - protected readonly TToken _endOfSource; + private readonly TToken _endOfSource; - public ParserContext(Memory.Token> tokens, TToken endOfSource) : base(tokens) + protected ParserContext(Memory.Token> tokens, TToken endOfSource) : base(tokens) { _endOfSource = endOfSource; } @@ -98,9 +98,4 @@ public class ParserContext : ParsableStream.Token> where T { return new ParsingException(Report.FormatMessage(token, message)); } - - public void Halt(Lexer.Token token, string message) - { - throw new ParsingException(Report.FormatMessage(token, message)); - } } \ No newline at end of file diff --git a/DevDisciples.Parsing/Report.cs b/DevDisciples.Parsing/Report.cs index 74acaf1..3a260a5 100644 --- a/DevDisciples.Parsing/Report.cs +++ b/DevDisciples.Parsing/Report.cs @@ -2,7 +2,7 @@ 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)); } diff --git a/DevDisciples.Parsing/Visitor.cs b/DevDisciples.Parsing/Visitor.cs index b912406..04bd5b9 100644 --- a/DevDisciples.Parsing/Visitor.cs +++ b/DevDisciples.Parsing/Visitor.cs @@ -1,8 +1,10 @@ -namespace DevDisciples.Parsing; +namespace DevDisciples.Parsing; public static class Visitor { - public delegate void Visit(object visitee, params object[] args); - public delegate TOut Visit(object visitee, params object[] args); - public delegate TOut Visit(TIn visitee, params object[] args); + public delegate void Visit(TVisitee visitee); + + public delegate void Visit(TVisitee visitee, TContext context); + + public delegate TOut Visit(TVisitee visitee, TContext context); } \ No newline at end of file diff --git a/DevDisciples.Parsing/VisitorContainer.cs b/DevDisciples.Parsing/VisitorContainer.cs index 3adc725..7559f11 100644 --- a/DevDisciples.Parsing/VisitorContainer.cs +++ b/DevDisciples.Parsing/VisitorContainer.cs @@ -1,42 +1,40 @@ -namespace DevDisciples.Parsing; +namespace DevDisciples.Parsing; -public class VisitorContainer +public class VisitorContainer { - private Dictionary Visitors { get; } = new(); - private Visitor.Visit Default { get; set; } = default!; + private Dictionary> Visitors { get; } = new(); + private Visitor.Visit Default { get; set; } = default!; - public void Register(Visitor.Visit visitor) + public void Register(Visitor.Visit visitor) { - Visitors[typeof(TVisitee)] = visitor; + Visitors[typeof(T)] = visitor; } - public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default); + public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default); } -public class VisitorContainer +public class VisitorContainer { - protected Dictionary> Visitors { get; } = new(); - public Visitor.Visit Default { get; } = default!; + private Dictionary> Visitors { get; } = new(); + private Visitor.Visit Default { get; set; } = default!; - - public VisitorContainer Register(Visitor.Visit visitor) + public void Register(Visitor.Visit visitor) { - Visitors[typeof(TVisitee)] = visitor; - return this; + Visitors[typeof(T)] = visitor; } - public Visitor.Visit this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default; + public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default); } -public class VisitorContainer +public class VisitorContainer { - protected Dictionary> Visitors { get; } = new(); - public Visitor.Visit Default { get; } = default!; + private Dictionary> Visitors { get; } = new(); + private Visitor.Visit Default { get; set; } = default!; - public void Register(Visitor.Visit visitor) + public void Register(Visitor.Visit visitor) { - Visitors[typeof(TVisitee)] = visitor; + Visitors[typeof(T)] = visitor; } - public Visitor.Visit this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default; + public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default); } \ No newline at end of file